#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?
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.
initializer_list constructorsThe initializer_list Class represents a list of objects of a specified type that can be used in a constructor, and in other contexts. You can construct an initializer_list by using brace initialization: C++ Copy. initializer_list<int> int_list{5, 6, 7}; Important.
Initializer List is used in initializing the data members of a class. The list of members to be initialized is indicated with constructor as a comma-separated list followed by a colon. Following is an example that uses the initializer list to initialize x and y of Point class.
When a compiler sees an initializer list, it automatically converts it into an object of type std::initializer_list. Therefore, if we create a constructor that takes a std::initializer_list parameter, we can create objects using the initializer list as an input.
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".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With