Clang 6、clang 7和gcc 7.1、7.2和7.3都同意以下是有效的C++17代码,但在C++14和C++11下是不明确的。MSVC 2015和2017也接受它。然而,即使在c++17模式下,gcc-8.1和8.2也拒绝它:
struct Foo
{
explicit Foo(int ptr);
};
template<class T>
struct Bar
{
operator T() const;
template<typename T2>
explicit operator T2() const;
};
Foo foo(Bar<char> x)
{
return (Foo)x;
}接受它的编译器选择模板化的显式转换函数Bar::operator T2()。
拒绝它的编译器一致认为,在以下几个方面之间存在着模糊性:
Bar<char>到char的隐式用户定义转换,然后使用从char到int的隐式内建转换,然后使用显式构造函数Foo(int)。那么,哪个编译器是对的?C++14和C++17的相关标准有什么不同?
附录:实际错误消息
这是gcc-8.2 -std=c++17的错误。gcc-7.2 -std=c++14打印相同的错误:
<source>: In function 'Foo foo(Bar<char>)':
<source>:17:17: error: call of overloaded 'Foo(Bar<char>&)' is ambiguous
return (Foo)x;
^
<source>:3:14: note: candidate: 'Foo::Foo(int)'
explicit Foo(int ptr);
^~~
<source>:1:8: note: candidate: 'constexpr Foo::Foo(const Foo&)'
struct Foo
^~~
<source>:1:8: note: candidate: 'constexpr Foo::Foo(Foo&&)'下面是来自clang-7 -std=c++14的错误(clang-7 -std=c++17接受代码):
<source>:17:12: error: ambiguous conversion for C-style cast from 'Bar<char>' to 'Foo'
return (Foo)x;
^~~~~~
<source>:1:8: note: candidate constructor (the implicit move constructor)
struct Foo
^
<source>:1:8: note: candidate constructor (the implicit copy constructor)
<source>:3:14: note: candidate constructor
explicit Foo(int ptr);
^
1 error generated.发布于 2018-11-01 12:49:31
这里有几种力量在起作用。为了了解正在发生的事情,让我们检查一下(Foo)x应该把我们带到哪里。首先,在这种特殊情况下,c样式的强制转换等同于static_cast。静态转换的语义是直接初始化结果对象。由于结果对象是类类型的,所以[dcl.init]/17.6.2告诉我们它是按以下方式初始化的:
否则,如果初始化是直接初始化,或者是复制初始化,源类型的cv非限定版本与目标类或目标类的派生类相同,则考虑构造函数。列举了适用的构造函数(over.match.ctor),并通过过载解析选择了最优的构造函数。这样选择的构造函数被调用来初始化对象,初始化器表达式或表达式列表作为它的参数。如果不应用构造函数,或者重载解析不明确,则初始化格式不正确.
所以重载解析来选择Foo的构造函数来调用。如果过载解析失败,程序就是错误的.在这种情况下,它不应该失败,即使我们有3个候选构造函数。他们是Foo(int),Foo(Foo const&)和Foo(Foo&&)。
首先,我们需要将初始化int作为参数复制到构造函数,这意味着查找从Bar<char>到int的隐式转换序列。因为您从Bar<char>到char提供的用户定义的转换操作符并不是显式的,所以我们可以使用它从隐式会话序列Bar<char> -> char -> int。
对于另外两个构造函数,我们需要绑定到一个Foo的引用。然而,我们不能这样做。根据[over.match.ref]/1的说法:
在dcl.init.ref中指定的条件下,引用可以直接绑定到一个glvalue或类prvalue,这是将转换函数应用于初始化器表达式的结果。重载解析用于选择要调用的转换函数。假设“cv1 T”是正在初始化的引用的基础类型,而“cv1”是初始化器表达式的类型,对于S类类型,选择候选函数如下:
唯一能够产生Foo类型的glvalue或prvalue的转换函数是您指定的显式转换函数模板的专门化。但是,由于函数参数的初始化不是直接初始化,所以不能考虑显式转换函数。因此,我们不能在重载解析中调用复制或移动构造函数。这就只剩下构造函数接受int了。所以过载解决方案是成功的,应该是这样的。
那么,为什么一些编译器发现它不明确,或者调用模板转换运算符呢?既然有保证的复制省略被引入到标准中,就会注意到(CWG第2327期)用户定义的转换函数也应该有助于复制省略。今天,按照干涩的字母的标准,他们没有。但我们真的希望他们这么做。虽然具体应如何做的措辞仍在拟订之中,但似乎有些编译器已经开始着手并试图加以实现。
你看到的就是这个实现。这是扩大复制省略的反作用力,干扰了这里的过载解析。
https://stackoverflow.com/questions/53101121
复制相似问题