Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does parameter pack expansion work differently with different C++ compilers?

Parameter pack expansion is reversed by the VS2015 compiler.

I have the following code:

#include <iostream>
#include <vector>


template <typename... T>
void f_Swallow(T &&...)
{
}

template <typename... T>
std::vector<int> f(T ...arg)
{
    std::vector<int> result;
    f_Swallow
    (
        [&]()
        {

            result.push_back(arg);
            return true;
        }
        ()...
    ) ;
    return result;
}


using namespace std;
int main()
{
    auto vec = f(1,2,3,4);

    for (size_t i = 0; i < vec.size(); ++i)
        cout << vec[i] << endl;
}

When I run this code in XCode (clang-700.1.81), I get this result:

1
2
3
4

But the same code run in VS2015 produces this output:

4
3
2
1

Why are the parameter packs expanded differently depending on the compiler? Is there a way to fix it without checking the platform and compiler version? Doesn't the standard guarantee anything about expansion order?

like image 612
ALEXANDER KONSTANTINOV Avatar asked Feb 29 '16 14:02

ALEXANDER KONSTANTINOV


2 Answers

It's not the order of parameter pack expansion which is different, it's the order of function argument evaluation.

f_Swallow
(
    [&]()
    {

        result.push_back(arg);
        return true;
    }
    ()...
) ;

For sake of brevity, lets just give that lambda the name funcN where N is the parameter number. Given four arguments, the parameter pack will be expanded by any conforming compiler into this:

f_Swallow(func1(), func2(), func3, func4()) ;

The order of evaluation of function arguments is unspecified in C++. The compiler could evaluate them in-order (like your version of Clang), in reverse order (like your version of MSVC), or in any order it likes. You cannot count on the evaluation order.

To get what you want, you could put the expressions into a context in which the order of evaluation is specified. For example:

template <typename... T>
std::vector<int> f(T ...arg)
{
    std::vector<int> result;
    (void)std::initializer_list<int> { (result.push_back(arg), 0)... };
    return result;
}

In C++17, you'll be able to do the following with fold expressions:

template <typename... T>
std::vector<int> f(T ...arg)
{
    std::vector<int> result;
    (result.push_back(arg), ...);
    return result;
}
like image 78
TartanLlama Avatar answered Nov 03 '22 20:11

TartanLlama


I figured that it could be also written just like this:

template <typename... T>
std::vector<int> f(T ...arg)
{
    std::vector<int> result{ arg... };
    return result;
}

No need to create dummy std::initializer_list

like image 25
ALEXANDER KONSTANTINOV Avatar answered Nov 03 '22 20:11

ALEXANDER KONSTANTINOV