过载解析在gcc和clang之间得到不同的结果 [英] Overload resolution gets different result between gcc and clang

查看:220
本文介绍了过载解析在gcc和clang之间得到不同的结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  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: http: //melpon.org/wandbox/permlink/pVe9eyXgu26NEX6X



对于clang 3.6.0: http://melpon.org/wandbox/permlink/WOi1md2dc519SPW0



这可能类似于直接列表初始化成功编译,但正常直接初始化失败,为什么? a> which gcc and clang get same result。



但这是一个不同的问题。 B(A)在这里是显式的。 gcc和clang得到不同的结果。

解决方案

差异可以减少到

  struct A {explicit A(int); }; 
struct B {B(int); };
void f(A);
void f(B);

int main(){
f({1});
}

在GCC上,这会失败,根据标准初始化,显式构造函数被考虑 - 所以他们可以产生歧义 - 但他们只是不允许选择)。 Clang接受它并调用第二个函数。



在你的情况下,@Columbo在他的回答直接列表初始化成功编译,但正常直接初始化失败,为什么?适用。不同的是,在你的情况下, B(const B&); 不再是Clang可接受的,因为 {0} - & B 转换将面临两种可能性:显式构造函数或第二次递归使用复制构造函数。第一个选项,如上所述,不会被clang考虑,这次@Columbo的解释适用,并且复制构造函数不能第二次使用,因为这将需要一个用户定义的转换,因为我们有一个单一的元素(这里, 0 )。因此,在摘要中,只有第一个构造函数成功并被采用。






由于我理解问题是关于奇怪的重载分辨率规则和一些可能无法跟随,这里有一个更直观的解释。有效的规则为了




  • b({0})表示goto http://eel.is/c++draft/dcl。 init#17 ,然后从那里 http://eel.is/c ++ draft / over.match.ctor ,这是我们的第一个OR上下文。枚举的两个构造函数是 B(A); B(const B&) > {0} 。




    • 对于 B(A)转换。


    • 对于 B(const B&),我们需要初始化 const B& ,其中包含 http:// eel.is/c++draft/over.ics.list#8 ,然后到 http://eel.is/c++draft/over.ics.ref#2 (借助于 http://eel.is/c++draft/dcl.init#dcl.init.list-3 否则,如果T是引用类型,T引用的类型的prvalue临时是copy-list-initialized ...),然后到 http://eel.is/c++draft/over.best.ics#over.ics.list-6 。结果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 are B(A); and B(const B&) with argument {0}.

    • For B(A) it works with a single user defined conversion.

    • For B(const B&), we need to initialize a const 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 candidates B(A); and B(const B&), with the argument 0. 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 the 0 to const 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之间得到不同的结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆