问题
there are some codes here
#include <iostream>
struct A {
A(int) {}
};
struct B {
B(A) {
std::cout<<"0"<<std::endl;
}
B(B const&) {
std::cout << "1" << std::endl;
}
B(B&&) {
std::cout << "2" << std::endl;
}
};
int main() {
B b0{{0}}; // this is ok #1
B b( {0} ); //this is error #2
}
g++ report:
main.cpp: In function ‘int main()’:
main.cpp:17:11: error: call of overloaded ‘B(<brace-enclosed initializer list>)’ is ambiguous
B b({ 0 });
^
main.cpp:12:2: note: candidate: B::B(B&&)
B(B&&) {
^
main.cpp:9:2: note: candidate: B::B(const B&)
B(B const&) {
^
main.cpp:6:2: note: candidate: B::B(A)
B(A) {
clang report:
main.cpp:17:4: error: call to constructor of 'B' is ambiguous
B b({ 0 });
^ ~~~~~
main.cpp:6:2: note: candidate constructor
B(A) {
^
main.cpp:12:2: note: candidate constructor
B(B&&) {
^
main.cpp:9:2: note: candidate constructor
B(B const&) {
^
1 error generated.
{0} will convert to temporary object A and the contructor B(A) will be selected, #1 and #2 are all the "direct-constructor" form ,why #1 is ok ,#2 has three candidate constructor and is ambiguous?
回答1:
Because for #1, the copy and move constructors are disallowed by [over.best.ics]/4 (emphasized mine):
However, if the target is
- the first parameter of a constructor or
- the implicit object parameter of a user-defined conversion function
and the constructor or user-defined conversion function is a candidate by
[over.match.ctor], when the argument is the temporary in the second step of a class copy-initialization,
[over.match.copy], [over.match.conv], or [over.match.ref] (in all cases), or
the second phase of [over.match.list] when the initializer list has exactly one element that is itself an initializer list, and the target is the first parameter of a constructor of class X, and the conversion is to X or reference to cv X,
user-defined conversion sequences are not considered. [ Note: These rules prevent more than one user-defined conversion from being applied during overload resolution, thereby avoiding infinite recursion. — end note ]
So it is the language rule that distinguishes ({...})
and {{...}}
. Note the ({...})
case falls into [over.match.ctor] but the argument is NOT the temporary in the second step of a class copy-initialization, so the first bullet does not apply.
You can further read Issue 2076 to see it is intended to disallow the copy and move constructors for the inner brace in the {{...}}
case:
The resolution of issue 1467 made some plausible constructs ill-formed. For example,
struct A { A(int); }; struct B { B(A); }; B b{{0}};
This is now ambiguous, because the text disallowing user-defined conversions for B's copy and move constructors was removed from 16.3.3.1 [over.best.ics] paragraph 4...
回答2:
Why B b( {0} ); does not work?
A a { 0 };
B b { 0 };
Those are both valid. So, in B b( { 0 })
, what does { 0 }
stand for? It could be an instance of A
or B
. Nobody knows, and that's why it's ambiguous.
As your compiler states: there are 3 candidates:
B::B(A)
B::B(const B&)
B::B(B&&)
Why does B b0 { {0} }; work?
Because you simply call the constructor B::B(A)
. The copy/move constructors are not called here, only the constructor is. A copy constructor would only be invoked if you initialize the instance with an lvalue
of another instance:
B b0 {};
B b1 { b0 }; // copy constructor
The same is valid for the move constructor:
B b0 {};
B b1 { std::move(b0) }; // move constructor
Now, if you pass an instance as a prvalue
, it will call the constructor, because the instance can be constructed on the fly, that is: the prvalue
does not need to be copied or moved, it can be directly constructed into b0
. Example:
B b0 { {0} };
Here, {0}
can be interpreted as a prvalue
of type B
, or of type A
, but in both cases, the constructor is called.
来源:https://stackoverflow.com/questions/58930321/what-are-the-overload-resolution-rules-of-list-initialization