Reputation: 816
Consider the following toy code:
class Y
{
public:
Y(int, int) { cout << "Y ctor\n"; }
};
class X
{
public:
//X(initializer_list<int>) { cout << "init\n"; } // 1
X(Y&&) { cout << "Y&&\n"; } // 2
X(int, int) { cout << "param\n"; } // 3
X(const X&) { cout << "X&\n"; } // 4
X(const Y&) { cout << "Y&\n"; } // 5
X(Y) { cout << "Y\n"; } // 6
X(X&&) { cout << "X&&\n"; } // 7
};
void f(const X&) { }
void f(const Y&) { }
void f(Y) { }
int main()
{
X x({1,2});
//f({ 1,2 }); // 8
}
I'm trying to understand how the compiler uses {1,2}
in X x({1,2})
. Followings are my understandings:
Uncomment line 1
In such case, I think X x({1,2})
is an explicit call for X(initializer_list<int>)
. That is, when compiler sees {}
-list, I think it will first see it as std::initializer_list
and a constructor that takes an argument of initializer_list<T>
will be the better match than all the others.
Comment out line 6
, 7
In such case, I think {1,2}
is used to construct an object of Y
which is bound with an rvalue reference (i.e. compiler chooses to call X(Y&&)
). My guess here is that the compiler treats {1,2}
as some kind of rvalue when there is no constructor that takes an argument of initializer_list<T>
, and thus a constructor that takes an rvalue reference is preferred (in this case, X(Y&&)
is the only constructor of this kind).
Update: I found that the behavior I described in this bullet point is actually a bug of MSVC. gcc and clang will report ambiguity, not calling X(Y&&)
Comment out line 7
Ambiguity arises. Compiler reports that both X(Y&&)
and X(Y)
match. I think this is because although X(Y&&)
is a better match than X(const Y&)
and X(const X&)
, it is indistinguishable to X(Y)
. (I think the logic here is quite twisted)
Possible reference.
Comment out line 2
,7
The code compiles and prints param
. I think this time X(const X&)
is chosen but I'm not sure why. My guess for this is that, when all the viable matches (X(const X&)
, X(const Y&)
and X(Y)
) are indistinguishable, the compiler chooses X(const X&)
because X x({1,2});
is constructing an X
.
Update: this can also be a MSVC bug. gcc and clang will report ambiguity, not calling X(const X&)
Comment out line 2
,7
and uncomment line 8
I think this is the case where all the viable matches are indistinguishable and f
is not a constructor of X
, hence f(const X&)
is not treated specially, and ambiguity arises.
May I ask if my understandings are correct? (I highly doubt they are accurate)
I find that it is quite tedious to read out what constructor is invoked by things like X x({1,2});
, so I guess in real code we should try to avoid such shorthand and write code in a more explicit way...
On the other hand, if we use the same settings for class X
and Y
as above, define and call a function g
as follows:
X g()
{
return { 1,2 };
}
int main()
{
g();
}
The results always seem to be equivalent as initializing the returned temporary by X{1,2}
(i.e. call X(initializer_list<int>)
when it exists, and in all the other cases call X(int, int)
). May I ask if this is true?
Upvotes: 2
Views: 97
Reputation: 39758
X x({1,2})
can’t be an “explicit call for” a std::initializer_list
: a braced-init-list has no type. However, there is a rule saying that one is a better match for a specialization of that template than for any other class type.X(Y&&)
is used because X(const X&)
isn’t viable. It would be considered a second user-defined conversion even though {…}
→Y
→X
isn’t (because of the two layers of initialization).Q
and Q&&
are often ambiguous like that. Consider that C++17 prvalues don’t even move to initialize either.X
are disqualified as above, and X(const Y&)
is a worse match because the const
must be added to the non-const list-initialized prvalue. (All user-defined conversions are otherwise indistinguishable.)f(Y)
is better than the other two regardless of X
’s constructors for the same const
reason.As for g
, yes copy-list-initialization is much the same as direct-list-initialization in the absence of explicit constructors. Since it is list-initialization, rather than direct initialization from an “argument” that is a braced-init-list, it does prefer an initializer-list constructor over all others.
Upvotes: 1