代码之家  ›  专栏  ›  技术社区  ›  6502

谁在“std::initializer_list”上?叮当还是叮当?[副本]

  •  4
  • 6502  · 技术社区  · 6 年前

    基于此代码

    struct Foo 
    {
       Foo() 
       {
           cout << "default ctor" << endl;
       }
    
       Foo(std::initializer_list<Foo> ilist) 
       {
           cout << "initializer list" << endl;
       }
    
       Foo(const Foo& copy)
       {
           cout << "copy ctor" << endl;
       }
    };
    
    int main()
    {
    
       Foo a;
       Foo b(a); 
    
       // This calls the copy constructor again! 
       //Shouldn't this call the initializer_list constructor?
       Foo c{b}; 
    
    
    
       _getch();
       return 0;
    }
    

    输出为:

    默认ctor

    拷贝构造函数

    在第三种情况下,我将b放入括号初始化中,它应该调用初始值设定项列表构造函数。

    相反,复制构造函数占据了主导地位。

    你们谁能告诉我这是怎么回事为什么?

    0 回复  |  直到 8 年前
        1
  •  19
  •   jaggedSpire    8 年前

    正如Nicol Bolas指出的,这个答案的原始版本是不正确的:在编写本文时,cppreference错误地记录了在列表初始化中考虑构造函数的顺序。下面是使用规则的答案,因为它们存在于标准的N4140草案中,这非常接近于官方的C++ 14标准。

    原答案的正文仍包括在内,以供记录。


    更新的答案

    g++ -std=c++14 -Wall -pedantic -pthread main.cpp && ./a.out
    default ctor
    copy ctor
    copy ctor
    initializer list
    
    
    clang++ -std=c++14 -Wall -pedantic -pthread main.cpp && ./a.out
    default ctor
    copy ctor
    copy ctor
    

    gcc是正确的。

    n4140[dcl.init.list]/1

    列表初始化是从带括号的init列表初始化对象或引用。

    你在那里使用列表初始化 c

    [dcl.init.list]/3:

    1. 如果 T 是一个集合。。。
    2. 否则,如果初始值设定项列表没有元素。。。
    3. 否则,如果 std::initializer_list<E> ...

    查看目前的列表:

    1. Foo 不是聚合。
    2. 它有一个元素。
    3. 不是 std::初始值设定项列表<E> .

    然后点击[dcl.init.list]/3.4:

    否则,如果 是类类型,将考虑构造函数。列举了适用的构造函数,并通过重载解析(13.3、13.3.1.7)选择了最佳的构造函数。如果需要进行变窄转换(见下文)来转换任何参数,则程序格式错误。

    按列表初始化
    当非聚合类类型的对象 T型 列表初始化(8.5.4)后,重载解析分两个阶段选择构造函数:

    1. T型 参数列表由作为单个参数的初始值设定项列表组成。
    2. 如果找不到可行的初始值设定项列表构造函数,则再次执行重载解析,其中候选函数是类的所有构造函数 参数列表由初始值设定项列表的元素组成。

    因此,只考虑复制构造函数 之后 初始化器在重载解析的第二阶段列出构造函数。此处应使用初始值设定项列表构造函数。

    值得注意的是,[over.match.list]然后继续:

    如果初始值设定项列表没有元素并且 T型

    在[dcl.init.list]/3之后。 处理单元素列表初始化:

    否则,如果初始值设定项列表有一个类型为 E 或者 T型 E类 ,对象或引用将从该元素初始化;如果需要进行缩小转换(请参见下文),则将该元素转换为 ,程序格式不正确。

    这就解释了cppreference在哪里得到了用于单元素列表初始化的特例,尽管它们的顺序比应该的高。


    原始答案

    cppreference :

    类型对象的列表初始化的效果 是:

    如果 T型 相同的或派生的类型(可能是cv限定的),对象是 复制列表初始化,或通过直接初始化 直接列表初始化)。(从c++14开始)

    Foo c{b} 满足所有这些要求。

        2
  •  6
  •   Nicol Bolas    8 年前

    让我们来看看C++ 14规范中关于列表初始化的内容。[dcl.init.list]3有一系列要应用的规则:

    Foo 不是聚合。

    3.2不适用,因为列表不是空的。

    不是 initializer_list .

    3.4确实适用,因为 是类类型。它说要根据[over.match.list]考虑具有重载分辨率的构造函数。那条规则说要检查 初始值设定项列表 构造器 第一 initilaizer_list 构造函数,编译器 必须 初始值设定项列表 这就是所谓的 .

    简而言之,海合会是正确的 是错误的 .

    没有什么 关于这个。它有一个新的第3.1节,对单值列表有特殊的措辞,但是 . 不是聚合,因此不适用。