2

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)...};
    }
Patrick Fromberg
  • 1,313
  • 11
  • 37
  • You can't do that with return type `auto`. – aschepler Jan 31 '18 at 01:29
  • @aschepler, yes, I know, but why is that a problem? – Patrick Fromberg Jan 31 '18 at 01:33
  • A braced-init-list has no type, so the return type cannot be deduced – Praetorian Jan 31 '18 at 01:38
  • @Praetorian, yes, I know, the return type cannot be auto, why must it be auto? – Patrick Fromberg Jan 31 '18 at 01:40
  • Because a braced initializer list alone is not assumed to be an initializer for a `std::tuple`. Could you make a rule that says that a braced initialization list for an `auto` results in a `std::tuple`? You could, but there is no such rule in C++. – Sam Varshavchik Jan 31 '18 at 01:43
  • Oh, you're asking why `auto` isn't replaced with `std::tuple...>` and then a braced-init-list used in the return statement. You should edit the question to clarify that. – Praetorian Jan 31 '18 at 01:43
  • @Praetorian, I just did, but this is why I started of with please read . . . which should have made it clear. – Patrick Fromberg Jan 31 '18 at 01:44
  • @Praetorian, "I did, and it wasn't". I just do not understand that comment. – Patrick Fromberg Jan 31 '18 at 01:47
  • I read the question you linked to, and it still wasn't clear to me what you were asking – Praetorian Jan 31 '18 at 01:50
  • I think your writing isn't emphasizing the *combinations* enough since it doesn't establish this idea to your readers in early paragraphs. The second paragraph should've established the mention of the combination of "`auto` and `return tuple...`" and the combination of "`tuple` and `return {...}`", and subsequent paragraphs could refer to the combinations. –  Jan 31 '18 at 01:56

1 Answers1

6

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.

Barry
  • 286,269
  • 29
  • 621
  • 977