【问题标题】:Why do gcc and clang each produce different output for this program? (conversion operator vs constructor)为什么 gcc 和 clang 都会为这个程序产生不同的输出? (转换运算符与构造函数)
【发布时间】:2017-10-18 21:38:12
【问题描述】:

程序:

#include <stdio.h>

struct bar_t {
    int value;
    template<typename T>
    bar_t (const T& t) : value { t } {}

    // edit: You can uncomment these if your compiler supports
    //       guaranteed copy elision (c++17). Either way, it 
    //       doesn't affect the output.

    // bar_t () = delete;
    // bar_t (bar_t&&) = delete;
    // bar_t (const bar_t&) = delete;
    // bar_t& operator = (bar_t&&) = delete;
    // bar_t& operator = (const bar_t&) = delete;
};

struct foo_t {
    operator int   () const { return 1; }
    operator bar_t () const { return 2; }
};

int main ()
{
    foo_t foo {};
    bar_t a { foo };
    bar_t b = static_cast<bar_t>(foo);

    printf("%d,%d\n", a.value, b.value);
}

gcc 7/8 的输出:

2,2

clang 4/5 的输出(也适用于 gcc 6.3)

1,1

在创建bar_t的实例时似乎发生了以下情况:

对于gcc,它是calls foo_t::operator bar_t,然后是constructs bar_t with T = int

对于clang,它是constructs bar_t with T = foo_t,然后是calls foo_t::operator int

这里哪个编译器是正确的? (或者如果这是某种形式的未定义行为,它们可能都是正确的)

【问题讨论】:

  • 对我来说,似乎因为bar_t 仍将具有bar_t(const bar_t&amp;) 形式的默认构造函数,并且由于常规函数击败模板函数,因为匹配所有事物都相同,因此正确的转换顺序在这两种情况都是 gcc 7,8 之后的情况。我看不出clang 4/5是正确的任何可能的方式。充其量是模棱两可的。
  • @isanae 从 C++17 开始,在强制复制省略的情况下不需要构造函数。
  • 呃,如果你不能提前说construct的效果是什么,那就不要使用它。三个编译器都对此意见不一,逃命吧。 ;-)
  • MSVC 没有实现 C++17 保证省略。 Clang 是正确的。
  • @T.C.它确实在 VS2017 的最新 (3) 更新中实现了它:blogs.msdn.microsoft.com/vcblog/2017/05/10/…

标签: c++ gcc clang language-lawyer compiler-bug


【解决方案1】:

我相信clang的结果是正确的。

bar_t a { foo } direct-list-initialization 和用户定义类型之间的 static_cast 中,目标类型的构造函数在源类型上的用户定义转换运算符之前被考虑(C++14 [dcl.init.list] /3 [expr.static.cast]/4)。如果重载解析找到合适的构造函数,则使用它。

当进行重载解析时,bar_t::bar_t&lt;foo_t&gt;(const foo_t&amp;) 是可行的,并且会比与此模板的任何实例化更好的匹配,从而导致在 foo 上使用强制转换运算符。它也比任何默认声明的构造函数都要好,因为它们采用 foo_t 以外的东西,所以使用了 bar_t::bar_t&lt;foo_t&gt;


当前编写的代码依赖于 C++17 保证复制省略;如果您在没有 C++17 保证复制省略的情况下进行编译(例如 -std=c++14),那么由于 bar_t b = static_cast&lt;bar_t&gt;(foo); 中的复制初始化,clang 会拒绝此代码。

【讨论】:

  • 看起来这是较新版本的 gcc (7+) 中的错误。旧版本(6.3.1)与 clang 一致。不过我得说,我认为我更喜欢有漏洞的版本,因为它提供了一个额外的配置点(转换运算符)来控制一种类型是如何从另一种构造出来的。
猜你喜欢
  • 1970-01-01
  • 2013-03-10
  • 1970-01-01
  • 2021-12-04
  • 1970-01-01
  • 1970-01-01
  • 2021-10-07
  • 2020-02-09
  • 2016-06-29
相关资源
最近更新 更多