C++ 17:显式转换函数与显式构造函数+隐式转换 - 规则是否已更改?

wol*_*ang 13 c++ language-lawyer implicit-conversion c++14 c++17

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;
}
Run Code Online (Sandbox Code Playgroud)

接受它的编译器选择模板显式转换函数,Bar::operator T2().

拒绝它的编译器同意以下两者之间存在歧义:

  1. 显式转换函数Bar :: operator int()
  2. 首先使用隐式用户定义的转换从Bar<char>char,然后从所述隐式内置转换charint,然后将显式构造的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&&)'
Run Code Online (Sandbox Code Playgroud)

这是来自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.
Run Code Online (Sandbox Code Playgroud)

Sto*_*ica 9

这里有几种力量在起作用.要了解正在发生的事情,让我们来看看(Foo)x应该引导我们的地方.首先,static_cast在这种特殊情况下,c风格的演员阵容相当于a .静态强制转换的语义是直接初始化结果对象.由于结果对象是类类型,[dcl.init] /17.6.2告诉我们它的初始化如下:

否则,如果初始化是直接初始化,或者如果它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同的类或派生类,则考虑构造函数.枚举适用的构造函数([over.match.ctor]),并通过重载决策选择最佳构造函数.调用所选的构造函数来初始化对象,初始化表达式或表达式列表作为其参数.如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的.

所以重载决议选择Foo要调用的构造函数.如果重载解析失败,程序就会形成错误.在这种情况下,它应该不会失败,即使我们有3个候选构造函数.那些是Foo(int),Foo(Foo const&)Foo(Foo&&).

对于第一个,我们需要复制初始化int作为参数的构造,这意味着找到的隐式转换序列Bar<char>int.自您提供的用户定义的转换操作符Bar<char>char不明确的,我们可以从一个隐含的谈话顺序用它来Bar<char> -> char -> int.

对于其他两个构造函数,我们需要绑定对a的引用Foo.但是,我们不能这样做.根据[over.match.ref]/1:

在[dcl.init.ref]中指定的条件下,引用可以直接绑定到glvalue或类prvalue,它是将转换函数应用于初始化表达式的结果.重载分辨率用于选择要调用的转换函数.假设"cv1 T"是被初始化的引用的基础类型,并且"cv S"是初始化表达式的类型,其中S是类类型,候选函数被选择如下:

  • 考虑S及其基类的转换函数.那些未隐藏在S中的非显式转换函数和yield类型"对cv2 T2的左值引用"(初始化左值引用或对函数的右值引用时)或"cv2 T2"或"对cv2 T2的rvalue引用"(当初始化rvalue引用或对函数的左值引用,其中"cv1 T"与"cv2 T2"是参考兼容的([dcl.init.ref]),是候选函数.对于直接初始化,那些未隐藏在S中的显式转换函数并且分别产生类型"对cv2 T2的左值引用"或"cv2 T2"或"对cv2 T2的rvalue引用",其中T2与T的类型相同或者可以通过限定转换([conv.qual])转换为类型T,也是候选函数.

唯一可以产生glvalue或prvalue类型Foo的转换函数是您指定的显式转换函数模板的特化.但是,由于函数参数的初始化不是直接初始化,我们不能考虑显式转换函数.所以我们不能在重载决策中调用副本或移动构造函数.这让我们只有构造函数采取int.因此,重载决策是成功的,应该是它.

那么为什么有些编译器会发现它不明确,或者调用模板化转换运算符呢?好吧,由于保证复制省略被引入标准,因此注意到(CWG问题2327)用户定义的转换函数也应该有助于复制省略.今天,根据标准的干信,他们没有.但我们真的很喜欢他们.尽管有关如何完成的措辞仍在制定中,但似乎有些编制者已经开始尝试实施它.

这就是你看到的实现.这是延伸复制省略的反对力量,干扰了过载分辨率.