how to avoid undefined execution order for the constructors when using std::make_tuple

The trivial solution is not to use std::make_tuple(...) in the first place but to construct a std::tuple<...> directly: The order in which constructors for the members are called is well defined:

template <typename>
std::istream& dummy(std::istream& in) {
    return in;
}
template <typename... T>
std::tuple<T...> parse(std::istream& in) {
    return std::tuple<T...>(dummy<T>(in)...);
}

The function template dummy<T>() is only used to have something to expand on. The order is imposed by construction order of the elements in the std::tuple<T...>:

template <typename... T>
    template <typename... U>
    std::tuple<T...>::tuple(U...&& arg)
        : members_(std::forward<U>(arg)...) { // NOTE: pseudo code - the real code is
    }                                        //       somewhat more complex

Following the discussion below and Xeo’s comment it seems that a better alternative is to use

template <typename... T>
std::tuple<T...> parse(std::istream& in) {
    return std::tuple<T...>{ T(in)... };
}

The use of brace initialization works because the order of evaluation of the arguments in a brace initializer list is the order in which they appear. The semantics of T{...} are described in 12.6.1 [class.explicit.init] paragraph 2 stating that it follows the rules of list initialization semantics (note: this has nothing to do with std::initializer_list which only works with homogenous types). The ordering constraint is in 8.5.4 [dcl.init.list] paragraph 4.

Leave a Comment