问题描述
struct A {A(int);};
struct B {explicit B(A); B(const B&);};
B b({0});
gcc 5.1.0出现错误
/ dev / fd / 63:3:8:error:调用重载的'B(<括号括起始化列表>)'
是不明确的
/ dev / fd / 63:3:8:注意:候选人是:
/ dev / fd / 63:2:27:note:B :: B(const B&)
/ dev / fd / 63:2:21:note:B :: B(A)
铛铛3.6.0成功。
哪一个是正确的?为什么?
对于gcc 5.1.0:
对于clang 3.6.0:
这可能类似于适用。不同的是,在你的情况下,
B(const B&);
不再是Clang可接受的,因为{0} - & B
转换将面临两种可能性:显式构造函数或第二次递归使用复制构造函数。第一个选项,如上所述,不会被clang考虑,这次@Columbo的解释适用,并且复制构造函数不能第二次使用,因为这将需要一个用户定义的转换,因为我们有一个单一的元素(这里,0
)。因此,在摘要中,只有第一个构造函数成功并被采用。
由于我理解问题是关于奇怪的重载分辨率规则和一些可能无法跟随,这里有一个更直观的解释。有效的规则为了
-
b({0})
表示goto ,然后从那里,这是我们的第一个OR上下文。枚举的两个构造函数是B(A);
和B(const B&)
> {0} 。
-
对于
B(A)
转换。 -
对于
B(const B&)
,我们需要初始化const B&
,其中包含,然后到(借助于否则,如果T是引用类型,T引用的类型的prvalue临时是copy-list-initialized ...),然后到。结果OR上下文具有候选B(A);
和B(const B&)
c $ c> 0 。这是我们的第二个OR上下文,并且是由13.3.1.7(根据over.ics.ref#2和dcl.init.list-3的要求)的复制列表初始化。
-
对于
B(A)
,构造函数是显式的,因此被Clang忽略(与规范相矛盾) (因此是模糊性)。 -
对于
B(const B&)
,这是@Columbo处理的场景,因此禁止将需要的用户定义转换。较新的草稿没有这个规则(但可能会被添加回来)。但是因为0
到const B&
是正常的用户定义转换(不是列表初始化)将忽略转换所需的显式构造函数(对于复制构造函数的这种潜在的第二次使用),因此用户定义的转换将不可能,并且该规则比我在写
-
-
因此对于GCC,它可以直接使用显式构造函数,此外还可以单独使用复制构造函数。对于clang,它只考虑直接使用显式构造函数,并且它不会使用像GCC那样的副本构造函数通过副本列表初始化间接使用它。两者不会再考虑使用复制构造函数,这里不相关。
struct A { A(int);};
struct B { explicit B(A); B(const B&);};
B b({0});
gcc 5.1.0 gives the error
/dev/fd/63:3:8: error: call of overloaded 'B(<brace-enclosed initializer list>)'
is ambiguous
/dev/fd/63:3:8: note: candidates are:
/dev/fd/63:2:27: note: B::B(const B&)
/dev/fd/63:2:21: note: B::B(A)
while clang 3.6.0 succeeds.
Which one is right? Why?
For gcc 5.1.0: http://melpon.org/wandbox/permlink/pVe9eyXgu26NEX6X
For clang 3.6.0: http://melpon.org/wandbox/permlink/WOi1md2dc519SPW0
This may be similar to Direct list initialization compiles successfully, but normal direct initialization fails, why? which gcc and clang get same result.
But this is a different question. B(A)
is explicit here. gcc and clang get different results.
The difference can be reduced to
struct A { explicit A(int); };
struct B { B(int); };
void f(A);
void f(B);
int main() {
f({ 1 });
}
On GCC this fails, in accordance to the Standard (which says that for list initialization, explicit constructors are considered - so they can yield an ambiguity - but they just are not allowed to be selected). Clang accepts it and calls the second function.
In your case, what @Columbo says in his answer to Direct list initialization compiles successfully, but normal direct initialization fails, why? applies. With the difference that in your case, B(const B&);
is not anymore acceptable to Clang because the {0} -> B
conversion would be faced with two possibilities: the explicit constructor or using the copy constructor recursively a second time. The first option, as explained above, will not be considered by clang and this time the explanation by @Columbo applies and the copy constructor cannot be used a second time because that would need a user defined conversion as we have a single element (here, 0
). So in the summary, only the first constructor succeeds and is taken.
Since I understand the issue is about weird overload resolution rules and some might not be able to follow, here's a more intuitive explanation. The rules that are active are, in order
b({0})
means goto http://eel.is/c++draft/dcl.init#17 and from there to http://eel.is/c++draft/over.match.ctor which is our first OR context . The two constructors enumerated areB(A);
andB(const B&)
with argument{0}
.For
B(A)
it works with a single user defined conversion.For
B(const B&)
, we need to initialize aconst B&
which brings us to http://eel.is/c++draft/over.ics.list#8 then to http://eel.is/c++draft/over.ics.ref#2 (by help of http://eel.is/c++draft/dcl.init#dcl.init.list-3 "Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is copy-list-initialized ...") then to http://eel.is/c++draft/over.best.ics#over.ics.list-6. The resulting OR context has candidatesB(A);
andB(const B&)
, with the argument0
. This is our second OR context and is copy-list-initialization by 13.3.1.7 (as required by over.ics.ref#2 and dcl.init.list-3).For
B(A)
, the constructor is explicit and therefore ignored by Clang (in contradiction with the spec) but accepted by GCC (hence the ambiguity).For
B(const B&)
, this is the scenario handled by @Columbo and therefore the user-defined conversion which would be needed is forbidden. Newer drafts don't have this rule anymore (but probably it will be added back). But because the0
toconst B&
would be a normal user-defined conversion (not a list initialization), it would ignore the explicit constructor needed for the conversion anyway (for this potential second use of the copy constructor), and therefore the user-defined conversion wouldn't be possible anyway and the rule is of much less significance than I thought when I wrote the above shorter summary.
Therefore for GCC it can use the explicit constructor directly, and in addition by a single use of the copy constructor. For clang, it only considers using the explicit constructor directly, and it won't use it indirectly by a copy-list-initialization using the copy constructor like GCC does. Both won't consider using the copy constructor a second time, and it's irrelevant here.
这篇关于过载解析在gcc和clang之间得到不同的结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!