【问题标题】:Different logic produced by clang and gcc for same code. Which is correct?clang 和 gcc 为相同的代码生成不同的逻辑。哪个是对的?
【发布时间】:2019-02-01 09:30:44
【问题描述】:

我发现 gcc-8 和 clang-6 产生的逻辑有差异。

这发生在一个真实的代码库中,当我使用 clang 开发时,我使用 gcc 部署。

请告知哪个编译器有错误,以便我可以适当地提交错误。

概要

A 可隐式转换为 B A 可以从 A(复制/移动)和 std::initializer_list<B> 构造。

A&& 初始化A 时:

  • clang 选择移动构造函数
  • gcc 选择 initializer_list 构造函数。

现场演示:https://coliru.stacked-crooked.com/a/bc50bd8f040d6476

MCVE

#include <initializer_list>
#include <utility>
#include <iostream>

struct thing;

struct thing_ref
{
    thing_ref(thing&& other) : ref_(other) {}
    thing_ref(thing& other) : ref_(other) {}

    thing& ref_;
};

struct thing
{
    thing() {}

    thing(std::initializer_list<thing_ref> things)
    {
        std::cout << "initializer_list path\n";
    }

    thing(thing&& other)
    {
        std::cout << "move path\n";
    }

    thing(thing const& other)
    {
        std::cout << "copy path\n";
    }
};

struct foo
{
    foo(thing t) : mything { std::move(t) } {}
    thing mything;
};

int main()
{
    thing t;

    auto f = foo { std::move(t) };
}

编译器设置:

没有什么特别的,根据 coliru 链接:-std=c++17 -O2

【问题讨论】:

  • 你用什么标准设置编译?
  • 我认为两者都没有错,因为 std::move 是对编译器的建议。 std::move 实际上是一个右值转换。 rvalue 在初始化列表中使用,所以这是正确的。同时它可以移动,所以这也是正确的。不过,我希望这是一个举措。此外,如果您从 foo 中的统一初始化更改,如 mything(std::move(t)) 中那样,则两者都使用移动构造函数。
  • @bartop 根据 coliru 链接,-std=c++17 -O2,但 -O0 也是如此。为了清楚起见,我会更新问题。
  • @Ashkan 编辑代码以删除统一初始化是通过错误修复(经过一段时间追踪错误原因...)
  • @RichardHodges 我的观点是,将统一初始化中的右值视为 initializer_list 是合理的。这可能不是最聪明的,但它是正确的。

标签: c++ language-lawyer c++17


【解决方案1】:

标准草案(Tthing[dcl.init.list]

列表初始化是从一个花括号初始化列表初始化一个对象或引用。 ...

类型 T 的对象或引用的列表初始化定义如下:

  • 如果花括号初始化列表包含指定初始化列表 [不适用]

  • 如果 T 是一个聚合类并且 [不适用]

  • 否则,如果 T 是字符数组 [不适用]

  • 否则,如果 T 是一个聚合[不适用]

  • 否则,如果初始化列表没有元素[不适用]

  • 否则,如果 T 是 std::initializer_­list&lt;E&gt; 的特化 [不适用]

  • 否则,如果 T 是类类型,则考虑构造函数。 枚举适用的构造函数,并通过重载解析选择最佳构造函数[applies]

  • ...

[over.match.list]:

当非聚合类类型 T 的对象被列表初始化,使得 [dcl.init.list] 指定根据本节中的规则执行重载解析时,重载解析分两个阶段选择构造函数:

  • 最初,候选函数是类 T 的初始化列表构造函数 ([dcl.init.list]),参数列表由初始化列表作为单个参数组成. [适用]

  • 如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中候选函数是类 T 的所有构造函数,参数列表由初始化列表。

如果初始化列表没有元素并且 T 有默认构造函数,则省略第一阶段。 [不适用]

返回 [dcl.init.list] 以了解 initializer-list constructor 是什么:

如果构造函数的第一个参数是 std::initializer_­list&lt;E&gt; 类型或对某些类型 E 的可能有 cv 限定的 std::initializer_­list&lt;E&gt; 的引用,并且要么没有其他参数,要么所有其他参数都具有默认值,则该构造函数是初始值设定项列表构造函数参数([dcl.fct.default])。

还有一个方便的注释,重申了结论:

注意:初始化列表构造函数在列表初始化中优于其他构造函数

我的结论:

应首先考虑初始化列表构造函数候选者,并在其有效时使用。由于thing 隐式转换为thing_ref,它应该是有效的。在我看来,GCC 符合要求。

如果您想初始化具有初始化列表构造函数的类型的对象,但不想使用该构造函数,则不要使用列表初始化,即不要使用大括号初始化列表。

【讨论】:

  • DR 2137 呢?
  • @rustyx 嗯,如果我没看错,Clangs 的行为在更改之前是正确的。
  • 感谢您提供全面(如果相当令人沮丧)的答案。在我看来,从逻辑上讲,从逻辑上讲,复制/移动构造函数应该比另一种类型的 initializer_list 更可取。
猜你喜欢
  • 2023-02-14
  • 1970-01-01
  • 2015-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-12
  • 1970-01-01
  • 2018-01-12
相关资源
最近更新 更多