Problem with order of evaluation in parameter pack expansion

838 views Asked by At

I wrote a template that takes an istream& and a function and is supposed to extract all parameters for this function from the istream, call the function with these parameters and return the result. It all works fine, except for the order of evaluation of function parameters. See code, further details and final question below:

#include <iostream>
#include <vector>

void Foo(int i, std::string s)
{
    std::cout << "input was " << i << " and " << s << '\n';
}

template<typename T>
T Parse(std::istream &s)
{
    T res;
    
    s >> res;
    
    return res;
}

template<typename TR, typename ... TArgs>
TR Bar(std::istream &s, TR f(TArgs...) )
{
    return f(Parse<TArgs>(s)...);
}

int main()
{
    Bar(std::cin, Foo);
}

Input:

1 2

Expected output:

input was 1 and 2

Actual output:

input was 2 and 1

I know that the evaluation of function parameters is implementation specific and obviously here the last parameter got evaluated first and read the first input.

How do I to fix this code and force a specific order of evaluation on the parameters? Maybe evaluate them seperately before calling the function? Is it even possible without violating the standard and/or relying on a specific implementation or compiler?

1

There are 1 answers

0
Evg On BEST ANSWER

For example, we can use an intermediate std::tuple to force the order of evaluation:

template<typename TR, typename ... TArgs>
TR Bar(std::istream &s, TR f(TArgs...) )
{
    std::tuple<TArgs...> args{Parse<TArgs>(s)...};
    return std::apply(f, std::move(args));
}

In contrast to function arguments, the order of evaluation of arguments in a braced list is fixed by their order in that list, [dcl.init.list/4]:

Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions, are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list. [ Note: This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call. ]