mr_T
mr_T

Reputation: 2621

Why can a std::tuple not be assigned with an initializer list?

I wonder why this choice is made. It would allow to write many functions in a very clear and neat way.. for instance:

int greatestCommonDivisor(int a, int b)
{
    if (b > a)
        std::tie(a, b) = { b, a };
    while (b > 0)
        std::tie(a, b) = { b, a % b };
    return a;
}

Upvotes: 5

Views: 1284

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275500

Why does

std::tie(a,b) = {b, a};

not compile?

{} on the right hand side of an assignment can only call a non-explicit constructor of an argument of operator=.

The operator= overloads available are:

tuple& operator=( const tuple& other );
tuple& operator=( tuple&& other );
template< class... UTypes >
tuple& operator=( const tuple<UTypes...>& other );
template< class... UTypes >
tuple& operator=( tuple<UTypes...>&& other );
template< class U1, class U2 >
tuple& operator=( const pair<U1,U2>& p );
template< class U1, class U2 >
tuple& operator=( pair<U1,U2>&& p );

The template operator overloads cannot deduce their types from {} (note: this may change in C++17), leaving:

tuple& operator=( const tuple& other );
tuple& operator=( tuple&& other );

where tuple is std::tuple<int&, int&> in this case.

The tuple constructor for tuple<Ts...> that perfect forwards element-wise construction is explicit (#3 on that list). {} will not call an explicit constructor.

The conditionally non-explicit constructor takes Ts const&...; it does not exist if the Ts are non-copyable, and int& is non-copyable.

So there are no viable types to construct from {int&, int&}, and overload resolution fails.


Why does the standard not fix this? Well, we can do it ourselves!

In order to fix this, we'd have to add a special (Ts...) non-explicit constructor to tuple that only exists if the Ts types are all references.

If we write a toy tuple:

struct toy {
  std::tuple<int&, int&> data;
  toy( int& a, int& b ):data(a,b) {} // note, non-explicit!
};
toy toy_tie( int& a, int& b ) { return {a,b}; }

and use it, you'll notice that

    std::tie(a, b) = {b, a};

compiles and runs.

However,

    std::tie(a, b) = { b, a % b };

does not, as a%b cannot be bound to int&.

We can then augment toy with:

template<class...>
toy& operator=( std::tuple<int, int> o ) {
    data = o;
    return *this;
}

(+ defaulted special member functions. template<class...> ensures it has lower priority than the special member functions, as it should).

This which lets assign-from {int,int}. We then run it and... get the wrong result. The gcd of 5,20 is 20. What went wrong?

  toy_tie(a, b) = std::tie( b, a );

with both a and b bound to references is not safe code, and that is what

  toy_tie(a, b) = { b, a };

does.

In short, doing this right is tricky. In this case, you need to take a copy of the right hand side before assigning to be safe. Knowing when to take a copy and when not to is also tricky.

Having this work implicitly looks error prone. So it is, in a sense, accidental that it doesn't work, but fixing it (while possible) looks like a bad idea.

live example.

Upvotes: 4

Vittorio Romeo
Vittorio Romeo

Reputation: 93294

std::initializer_list is a homogeneous collection of items, while std::tuple is heterogeneous. The only case where it makes sense to define a std::tuple::operator= for std::initializer_list is when the tuple is homogeneous and has the same size as the initializer list, which is a rare occurrence.

(Additional information in this question.)


Solution/workaround: you can use std::make_tuple instead:

int greatestCommonDivisor(int a, int b)
{
    if (b > a)
        std::tie(a, b) = std::make_tuple(b, a);
    while (b > 0)
        std::tie(a, b) = std::make_tuple(b, a % b);
    return a;
}

...or std::tuple's constructor in C++17 (thanks to Template argument deduction for class templates):

int greatestCommonDivisor(int a, int b)
{
    if (b > a)
        std::tie(a, b) = std::tuple{b, a};
    while (b > 0)
        std::tie(a, b) = std::tuple{b, a % b};
    return a;
}

Upvotes: 7

Related Questions