Reputation: 1397
To understand the question please read this answer first.
I checked different historic make_tuple
implementations (including clang versions of 2012). Before C++17 I would have expected them to return {list of values ... }
but they all construct the tuple before returning it. They are all along the lines of the very simplified current cppreference example:
template <class... Types>
auto make_tuple(Types&&... args)
{
return std::tuple<special_decay_t<Types>...>(std::forward<Types>(args)...);
}
Its not wrong but the point of returning brace initialization is to construct the returned object directly. Before C++17 there was no guaranteed copy elision which removes the temporaries even conceptually. But even with C++17 I would not necessarily expect the curly braces to disappear in this example.
Why no curly braces here in any of the C++11/14 implementations? In other words, why not
template <class... Types>
std::tuple<special_decay_t<Types>...> make_tuple(Types&&... args)
{
return {std::forward<Types>(args)...};
}
Upvotes: 2
Views: 861
Reputation: 303127
The advantage you're talking about simply doesn't apply in this case. Yes, you can construct a non-movable type this way:
struct Nonmovable {
Nonmovable(int );
Nonmovable(Nonmovable&& ) = delete;
};
Nonmovable foo() { return {42}; } // ok
But in the context of make_tuple
, all the elements have to be movable or copyable anyway, because you're going to be moving or copying them into the actual tuple
that you're constructing:
std::tuple<Nonmovable> make_tuple(Nonmovable&& m) {
return {std::move(m)}; // still an error
}
So there's not really an advantage to to:
template <class... Types>
std::tuple<special_decay_t<Types>...> make_tuple(Types&&... args)
{
return {std::forward<Types>(args)...};
}
over
template <class... Types>
auto make_tuple(Types&&... args)
{
return std::tuple<special_decay_t<Types>...>(std::forward<Types>(args)...);
}
in that sense.
Yes, by the language of the standard, one of these implies constructing directly into the return object and the other involves a temporary. But in practice, every compiler will optimize this away. The one case where it couldn't we already know doesn't apply - our tuple
must be movable.
There is at least one weird downside to using {...}
here, which is that on the off-chance a type has an explicit
move or copy constructor, returning a braced-init-list doesn't work.
More importantly, as T.C. points out, until Improving pair
and tuple
was adopted, the constructor for std::tuple
was always explicit
.
// in C++11
explicit tuple( const Types&... args );
// in C++14
explicit constexpr tuple( const Types&... args );
// in C++17, onwards
/*EXPLICIT*/ constexpr tuple( const Types&... args );
Which made an implementation returning a braced-init-list impossible. So every library would have already had a make_tuple()
implementation before C++17, which could not have used a braced-init-list, and there's no benefit to changing it - so we are where we are today.
Upvotes: 6