【问题标题】:C++17: explicit conversion function vs explicit constructor + implicit conversions - have the rules changed?C++17:显式转换函数 vs 显式构造函数 + 隐式转换——规则改变了吗?
【发布时间】:2019-04-05 16:02:05
【问题描述】:

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()

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

  1. 显式转换函数 Bar::operator int()
  2. 首先使用从Bar&lt;char&gt;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&&)'

这是来自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.

【问题讨论】:

  • clang++ 6.0.1 给了我一个链接器错误。命令:clang++ -fsanitize=undefined -std=c++17 -Wall -Wextra -Wshadow -Weffc++ -pedantic -pedantic-errors -Wc++14-compat -Wc++17-compat -o explicit explicit.cpp 输出:/tmp/explicit-71c2bd.o: In function foo(Bar)':explicit.cpp:(.text+0x15): undefined reference to Bar&lt;char&gt;::operator Foo&lt;Foo&gt;() const' clang-6.0: error: linker command failed with exit code 1 (use -v to see invocation)
  • @ted 自然。我已将示例简化为不包含函数的任何实现,因为这在这里无关紧要。用 -c 编译,如果你愿意的话,用 -S 来查看汇编输出。
  • 如果您使用正确的 C++ 强制转换会发生什么?
  • static_cast 或函数样式转换显示相同的行为,声明一个新变量并对其进行初始化:Foo y(x);Foo z{x};

标签: c++ c++14 language-lawyer c++17 implicit-conversion


【解决方案1】:

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

否则,如果初始化是直接初始化,或者如果是 复制初始化,其中源的 cv 不合格版本 type 与该类的类相同或派生类 目的地,构造函数被考虑。适用的构造函数 被枚举([over.match.ctor]),并通过 重载决议。如此选择的构造函数被调用到 使用初始化表达式或初始化对象 表达式列表作为其参数。如果没有构造函数适用,或者 重载决议不明确,初始化格式不正确。

所以重载决议来选择Foo的构造函数来调用。如果重载决议失败,则程序格式错误。在这种情况下,它不应该失败,即使我们有 3 个候选构造函数。这些是Foo(int)Foo(Foo const&amp;)Foo(Foo&amp;&amp;)

首先,我们需要复制初始化int 作为构造函数的参数,这意味着找到从Bar&lt;char&gt;int 的隐式转换序列。由于您提供的从Bar&lt;char&gt;char 的用户定义转换运算符不是显式的,我们可以使用它来从隐式对话序列Bar&lt;char&gt; -&gt; char -&gt; int 中生成。

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

在[dcl.init.ref]中指定的条件下,引用可以是 直接绑定到作为结果的泛左值或类纯右值 将转换函数应用于初始化表达式。超载 分辨率用于选择要调用的转换函数。 假设“cv1 T”是引用的基础类型 已初始化,“cv S”是初始化表达式的类型, S为类类型,候选函数选择如下:

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

唯一可以产生Foo 类型的glvalue 或prvalue 的转换函数是您指定的显式转换函数模板的特化。但是,由于函数参数的初始化不是直接初始化,我们不能考虑显式转换函数。所以我们不能在重载决议中调用复制或移动构造函数。剩下的只有构造函数采用int。所以重载决议是成功的,应该就是这样。

那么为什么有些编译器会觉得它模棱两可,或者调用模板化转换运算符呢?好吧,由于标准中引入了保证复制省略,因此注意到 (CWG issue 2327) 用户定义的转换函数也应该有助于复制省略。今天,根据标准的干信,他们没有。但我们真的很希望他们这样做。虽然具体应该如何完成的措辞仍在制定中,但似乎一些编译器已经开始尝试实现它。

你看到的就是那个实现。在这里,扩展复制省略的反作用力会干扰重载解析。

【讨论】:

  • 对接受它的编译器生成的程序集的检查表明,没有标记歧义的编译器都调用了显式转换函数,而不是构造函数+隐式转换链。
  • @wolfgang - 是的,他们尝试实施 2327。然后他们改变了实施方式的想法。你想要一个语言律师的答案,不是吗?
  • 确实,这正是我所要求的。我需要一段时间才能完全弄清楚,但这些绝对是标准的相关部分,事实上,它们已经改变了,虽然不是我预期的方向:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-11-02
  • 2016-02-22
  • 2011-10-29
  • 2014-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多