clang和gcc对于相同的代码产生不同的逻辑。哪个是对的?

时间:2019-02-01 09:30:45

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

我发现gcc-8和clang-6产生的逻辑之间存在差异。

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

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

简介

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

A初始化A&&时:

  • clang选择move-constructor
  • 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

1 个答案:

答案 0 :(得分:6)

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

  

列表初始化是对来自括号初始化列表的对象或引用的初始化。 ...

     

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

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

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

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

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

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

  •   
  • 否则,如果T是std::initializer_­list<E>的特化 [不适用]

  •   
  • 否则,如果T是类类型,则考虑构造函数。   列举了适用的构造函数,并通过重载决议 [applies]

  • 选择了最佳的构造函数   
  • ...

  •   

[over.match.list]

  

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

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

  •   
  • 如果找不到可行的初始值设定项列表构造函数,则会再次执行重载解析,其中候选函数是T类的所有构造函数,并且参数列表由以下元素组成:初始化程序列表。

  •   
     

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

返回[dcl.init.list]找出initializer-list constructor是什么:

  

如果构造函数的第一个参数的类型为std::initializer_­list<E>或对于某些类型E的引用可能是经过cv限定的std::initializer_­list<E>,则该构造函数为初始化列表构造器,并且没有其他参数或所有其他参数其他参数具有默认参数([dcl.fct.default])。

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

  

注意:在列表初始化中,与其他构造函数相比,初始化列表构造函数更受青睐

我的结论:

应该首先考虑初始化列表构造器候选对象,并在有效时使用。由于thing隐式转换为thing_ref,因此它应该有效。在我看来,GCC符合要求。

如果您要初始化具有一个initializer-list构造函数的类型的对象,但又不想使用该构造函数,则不要使用列表初始化,即不要使用brace-init-list。 / p>