ABu
ABu

Reputation: 12259

Brace-init-list and assignments

#include <iostream>

struct int_wrapper
{
    int i;

    int_wrapper(int i = 0) : i(i) {}
};

struct A
{
    int_wrapper i;
    int_wrapper j;

    A(int_wrapper i = 0, int_wrapper j = 0) : i(i), j(j) {}
};

int main()
{
    A a;

    a = {3};

    // error: no match for ‘operator=’ (operand types are ‘A’ and ‘int’)
    // a = 3;

    std::cout << a.i.i << std::endl;

    return 0;
}

I know that isn't allowed more than one user-conversion when doing an implicit conversion, but, why with a brace-init-list the double user-conversion works?

Upvotes: 6

Views: 3863

Answers (1)

Nicol Bolas
Nicol Bolas

Reputation: 473407

Remember: using a braced-init-list means "initialize an object". What you are getting is an implicit conversion followed by an object initialization. You're getting "double user-conversion" because that's what you asked for.

When you do a = <something>;, what this does is the effective equivalent of a.operator=(<something>).

When that something is a braced-init-list, this means that it will do a.operator=({3}). That will pick an operator= overload, based on initializing their first parameter from a braced-init-list. The overload that will be called will be the one that takes a type which can be initialized by the values in the braced-init-list.

There are two overloads of that operator. Namely, the copy-assignment and move-assignment. Since this braced-init-list initializes a prvalue, the preferred function to call will be the move-assignment operator (not that this matters, since it all leads to the same thing). The parameter of the move-assignment is A&&, so the braced-init-list will attempt to initialize an A. And it will do so via the rules of copy-list-initialization.

Having selected the operator= function to call, we now initialize A. Since the constructor of A is not explicit, copy-list-initialization is free to call it. Since that constructor has 2 default parameters, it can be called with only a single parameter. And the type of the first parameter in A's constructor, int_wrapper, is implicitly convertible from the type of the first value in the braced-init-list, int.

So, you get an implicit conversion to int_wrapper, which is used by copy-list-initialization to initialize a prvalue temporary object, which is used to assign to an existing object of type A via move-assignment.

By contrast, a.operator=(3) attempts to implicitly convert from 3 to A directly. Which of course requires two conversion steps and therefore is not allowed.

Just remember that braced-init-lists mean "initialize an object".

Upvotes: 7

Related Questions