c++c++17language-lawyervariadic-templatesparameter-pack

correct order in template parameter pack expansion


I want to fill a tuple<Ts...> from a vector of variants. (NB: This question follows up on Creating a tuple from a folding expression return values , see 2nd comment to the answer )

Expanding the pack in the template argument list for the tuple (the return type) works as expected. But expansion of the pack in the function argument list (of make_tuple() in this case) seems to reverse the order of types - when compiled with gcc.

Pls consider the following mre (also on compiler explorer) - after studying cppreference I'd expect in_order to work and reversed to fail:

#include <iostream>
#include <tuple>
#include <typeinfo>
#include <variant>
#include <vector>

using DATA=std::vector<std::variant<int,double>>;

template <typename... Ts> std::tuple<Ts...> in_order(DATA const &args) {
    try {
        auto arg_iter = args.cbegin();
        return std::make_tuple(std::get<Ts>(*arg_iter++)...);
    } catch(std::bad_variant_access const &e) {
        std::cout << "in order fails\n";
        return std::make_tuple(Ts{0}...);
    }
}

template <typename... Ts> std::tuple<Ts...> reversed(DATA const &args) {
    try {
        auto arg_iter = args.cbegin();
        std::advance(arg_iter,args.size());
        return std::make_tuple(std::get<Ts>(*(--arg_iter))...);
    } catch(std::bad_variant_access const &e) {
        std::cout << "reversed fails\n";
        return std::make_tuple(Ts{0}...);
    }
}

int main(int argc,char **argv) {
    DATA data;
    data.push_back(3.3);
    data.push_back(7);
    auto const &[d0,i0] = in_order<double,int>(data);
    std::cout << "in order: " << typeid(decltype(d0)).name() << d0 << " , " << typeid(decltype(i0)).name() << i0 << "\n";
    auto const &[d1,i1] = reversed<double,int>(data);
    std::cout << "reversed: " << typeid(decltype(d1)).name() << d1 << " , " << typeid(decltype(i1)).name() << i1 << "\n";
}

When compiled with clang on compiler explorer, the behaviour is exactly as expected. But when using gcc, it's the other way around: in_order fails while reversed returns the desired result. As we use gcc in our project, I need gcc the behave correctly.

My question is: which behaviour is the correct one,

  1. clang is correct
  2. gcc is correct
  3. both are correct, it's not defined

Or, phrased slightly different: is one of the compilers buggy in the case?


Solution

  • When you do

    return std::make_tuple(std::get<Ts>(*arg_iter++)...);
    

    you are calling a function and the order of evaluation for function parameters is not specified so both compilers are correct.

    To get both compilers to do the same thing we can just directly construct the tuple from the pack expansion like

    return std::tuple{std::get<Ts>(*(--arg_iter))...};
    

    This works because the order of initializers is specified and goes from left to right. You can see both compilers give the same results in this live example.