首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++17:显式转换函数与显式构造函数+隐式转换-规则改变了吗?

C++17:显式转换函数与显式构造函数+隐式转换-规则改变了吗?
EN

Stack Overflow用户
提问于 2018-11-01 12:17:31
回答 1查看 1.1K关注 0票数 15

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也拒绝它:

代码语言:javascript
复制
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()

拒绝它的编译器一致认为,在以下几个方面之间存在着模糊性:

  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打印相同的错误:

代码语言:javascript
复制
<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接受代码):

代码语言:javascript
复制
<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.
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 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类类型,选择候选函数如下:

  • 考虑了S及其基类的转换函数。那些非显式转换函数不隐藏在S和屈服类型“lvalue引用到cv2 T2”(初始化lvalue引用或对函数的rvalue引用时)或“cv2 T2”或“对cv2 T2的rvalue引用”(当初始化rvalue引用或对函数的lvalue引用时),其中“cv1 T”是与“cv2 T2”兼容的引用(dcl.init.ref)。对于直接初始化,那些未隐藏在S中的显式转换函数和屈服类型“lvalue引用cv2 T2”或“cv2 T2”或“rvalue引用cv2 T2”,其中T2与T的类型相同或可以转换为带有限定转换(conv.qual)的类型,也是候选函数。

唯一能够产生Foo类型的glvalue或prvalue的转换函数是您指定的显式转换函数模板的专门化。但是,由于函数参数的初始化不是直接初始化,所以不能考虑显式转换函数。因此,我们不能在重载解析中调用复制或移动构造函数。这就只剩下构造函数接受int了。所以过载解决方案是成功的,应该是这样的。

那么,为什么一些编译器发现它不明确,或者调用模板转换运算符呢?既然有保证的复制省略被引入到标准中,就会注意到(CWG第2327期)用户定义的转换函数也应该有助于复制省略。今天,按照干涩的字母的标准,他们没有。但我们真的希望他们这么做。虽然具体应如何做的措辞仍在拟订之中,但似乎有些编译器已经开始着手并试图加以实现。

你看到的就是这个实现。这是扩大复制省略的反作用力,干扰了这里的过载解析。

票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/53101121

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档