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

值初始化:MSVC vs clang

  •  15
  • phimuemue  · 技术社区  · 8 年前
    #include<cstddef>
    
    template<typename T, std::size_t N>
    struct A {
        T m_a[N];
        A() : m_a{} {}
    };
    
    struct S {
        explicit S(int i=4) {}
    };
    
    int main() {
        A<S, 3> an;
    }
    

    上述代码在MSVC(2017)中编译良好,但在clang 3.8.0中编译失败(输出 clang++ --version && clang++ -std=c++14 -Wall -pedantic main.cpp ):

    clang version 3.8.0 (tags/RELEASE_380/final 263969)
    Target: x86_64-unknown-linux-gnu
    Thread model: posix
    InstalledDir: /usr/local/bin
    main.cpp:6:15: error: chosen constructor is explicit in copy-initialization
        A() : m_a{} {}
                  ^
    main.cpp:14:13: note: in instantiation of member function 'A<S, 3>::A' requested here
        A<S, 3> an;
                ^
    main.cpp:10:14: note: constructor declared here
        explicit S(int i=4) {}
                 ^
    main.cpp:6:15: note: in implicit initialization of array element 0 with omitted initializer
        A() : m_a{} {}
                  ^
    1 error generated.
    

    clang 5.0也拒绝编译:

    <source>:6:17: error: expected member name or ';' after declaration specifiers
        A() : m_a{} {}
                    ^
    <source>:6:14: error: expected '('
        A() : m_a{} {}
                 ^
    2 errors generated.
    

    如果我在中使用简单括号 A s构造函数到(即。 A() : m_a() {} ),编译得很好。从…起 cppreference 我会怀疑这两种情况会导致相同的结果(即值初始化)。我是否遗漏了什么,或者这是某个编译器中的一个错误?

    4 回复  |  直到 8 年前
        1
  •  10
  •   songyuanyao    8 年前

    叮当声是正确的。

    您的困惑来自:

    从…起 cppreference 我会怀疑这两种情况会导致相同的结果(即值初始化)。

    不,它们有不同的效果。请注意该页中的注释:

    在所有情况下,如果使用空大括号对{},并且T是聚合类型,则执行聚合初始化,而不是值初始化。

    这意味着当使用带括号的init list初始化时,对于聚合类型,首选执行聚合初始化。具有 A() : m_a{} {} m_a 是一个数组,属于 aggregate type 然后 aggregate initialization 而是执行:

    (重点矿山)

    每个 direct public base, (since C++17) 数组元素或非静态类成员在类定义中按数组下标/外观的顺序为 副本已初始化 来自初始值设定项列表的相应子句。

    如果初始值设定项子句的数目小于成员的数目 and bases (since C++17) 或初始值设定项列表完全为空,其余成员 和基(自C++17以来) 已初始化 by their default initializers, if provided in the class definition, and otherwise (since C++14) 通过空列表,根据通常的列表初始化规则(使用默认构造函数对非类类型和非聚合类执行值初始化,并对聚合执行聚合初始化)。

    也就是说,剩下的元素,即 m\u a 将从空列表中复制初始化;对于空列表,默认构造函数为 S 将被考虑,但声明为 explicit ; 这个 copy-initialization 不会调用 明确的 施工人员:

    复制列表初始化(同时考虑显式和非显式构造函数,但只能调用非显式构造函数)


    另一方面 A() : m_a() {} 执行 value initialization 然后

    3) 如果T是数组类型,则数组的每个元素都是值初始化的;

    然后

    1) 如果T是一个没有默认构造函数的类类型,或者用户提供或删除了默认构造函数,则该对象是默认初始化的;

    然后的默认构造函数 S 调用以初始化 m\u a . 无论是 明确的 不管是不是 default initialization .

        2
  •  3
  •   T.C. Yksisarvinen    8 年前

    对于 m_a{} :

    • [dcl.init]/17.1 将我们发送到 [dcl.init.list] [dcl.init.list]/3.4 表示我们在上执行聚合初始化 m_a [dcl.init.aggr] .

      初始值设定项的语义如下所示。[...]

      • 如果初始值设定项为a(无括号) 大括号初始化列表 或者是 = 大括号初始化列表 ,对象或引用已初始化列表。
      • [...]

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

      • [...]
      • 否则,如果 T 是聚合,则执行聚合初始化。
      • [...]
    • [dcl.init.aggr]/5.2 表示我们复制并初始化 m\u a 从空的初始值设定项列表,即。, {} .

      对于非联合聚合,非显式初始化元素的每个元素的初始化如下所示:

      • [...]
      • 否则,如果元素不是引用,则从空的初始值设定项列表([dcl.init.list])复制初始化元素。
      • [...]
    • 这让我们回到 [dcl初始值]/17.1 对于每个元素的初始化,它再次将我们发送到 [dcl初始列表] .
    • 这次我们击中了 [dcl.init.list]/3.5 ,表示元素已初始化值。

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

      • [...]
      • 否则,如果初始值设定项列表没有元素 T 是具有默认构造函数的类类型,对象是值初始化的。
      • [...]
    • 这让我们想到 [dcl.init]/8.1 ,表示元素默认已初始化。

      值初始化 类型的对象 T 指:

      • 如果 T 是一个(可能是cv限定的)类类型,没有默认构造函数([class.ctor]),或者是用户提供或删除的默认构造函数,则对象默认初始化;
      • [...]
    • 哪个命中 [dcl.init]/7.1 ,这表示我们根据 [over.match.ctor] 并在初始值设定项上执行重载解析 () ;

      默认初始化 类型的对象 T 指:

      • 如果 T 是一种(可能是cv限定的)类类型,考虑构造函数。列举了适用的施工人员 ([over.match.ctor]),以及 初始值设定项 () 是 通过过载分辨率选择。这样选择的构造函数是 使用空参数列表调用以初始化对象。
      • [...]
    • (在match.ctor上)说:

      用于直接初始化 或不在 复制初始化的上下文 ,候选函数都是 正在初始化的对象的类的构造函数。对于 复制初始化,候选函数都在转换 该类构造函数。

    • 此默认初始化 在复制初始化的上下文中,候选函数是“该类的所有转换构造函数”。

    • 显式默认构造函数不是转换构造函数。因此,没有可行的构造函数。因此,重载解析失败,程序格式错误。

    对于 m_a() :

    • 我们击中了 [dcl.init]/17.4 ,表示数组已初始化值。

      初始值设定项的语义如下所示。[...]

      • [...]
      • 如果初始值设定项为 () ,对象为值初始化。
      • [...]
    • 这让我们想到 [dcl.init]/8.3 ,表示每个元素都已初始化值。

      值初始化 类型的对象 T 指:

      • [...]
      • 如果 T 是数组类型,则每个元素都进行值初始化;
      • [...]
    • 这又让我们想到 [dcl初始值]/8.1 ,然后到 [dcl初始值]/7.1 ,因此我们再次枚举每个 [结束匹配ctor] 并在初始值设定项上执行重载解析 () ;

    • 这一次,默认初始化 不是 在复制初始化的上下文中,候选函数是“被初始化对象的类的所有构造函数”。
    • 这一次,显式默认构造函数 通过重载解析选择的候选对象。因此,该计划的形式良好。
        3
  •  2
  •   YSC    8 年前

    这显然是标准的错误形式(但问题是,为什么?)

    m_a{} 列表初始化 S::m_a :

    [dcl.init.list]/1

    列表初始化 是从 大括号初始化列表 . 这样的初始值设定项称为初始值设定项列表,逗号分隔 初始值设定项子句 初始值设定项列表 指定初始值设定项子句 指定初始值设定项列表 称为初始值设定项列表的元素。初始值设定项列表可能为空。列表初始化可以在中进行 直接初始化 复制初始化 上下文; 列表初始化 在一个 直接初始化 调用上下文 直接列表初始化 列表初始化 在一个 复制初始化 调用上下文 复制列表初始化 .

    作为一个数组, A<S, 3>::m_a 是聚合类型( [dcl.init.aggr]/1 ).

    [dcl.init.aggr]/3.3

    1. 当聚合由[dcl.init.list],…]中指定的初始值设定项列表初始化时
      3.3初始值设定项列表必须为 {} ,并且没有显式初始化的元素。

    以下,自 没有显式初始化的元素 :

    [dcl.init.aggr]/5.2

    1. 对于非联合聚合,每个不是显式初始化的元素的初始化如下:[…]
      5.2如果元素不是参考,则元素为 副本已初始化 从空初始值设定项列表( [dcl.init.list] ).

    每个 S 属于 <S、 3>::m\u a 那么, 副本已初始化 :

    [dcl.init]/17.6.3

    1. 初始值设定项的语义如下所示。 这个 目的地类型 是要初始化的对象或引用的类型 源类型 是初始值设定项表达式的类型。 如果初始值设定项不是单个(可能带括号)表达式,则不会定义源类型。[...]
      17.6如果目标类型是(可能是cv限定的)类类型:[…]
      17.6.3否则(即,对于剩余 复制初始化 案例),用户定义 可以转换的转换序列 从源类型到目标类型,或(当使用转换函数时)到其派生类,按[覆盖匹配副本]中所述进行枚举,并通过重载解析选择最佳类型。 如果转换无法完成或不明确,则初始化格式错误。

    自的默认构造函数 S 是明确的 无法从源类型转换为目标类型 ( S ).

    语法使用 m_a() 另一方面,不是 聚合成员初始化 并且不会调用 复制初始化 .

        4
  •  0
  •   Johan    8 年前

    如果我正确理解标准,那么叮当声就是正确的。

    根据【dcl初始聚合】/8.5.1:2

    当聚合由初始值设定项列表初始化时,如指定 在8.5.4中,初始值设定项列表的元素如下 聚合成员的初始值设定项,以递增的下标表示 或会员订单。每个成员都是从 相应的初始值设定项子句。

    在同一条款[dcl.init.aggr]/8.5.1:7中

    如果列表中的初始值设定项子句少于 聚合中的成员,然后每个成员未显式初始化 应使用其大括号或同等初始值设定项进行初始化,或 空初始值设定项列表中没有大括号或等号-初始值设定项

    根据列表初始化规则[覆盖匹配列表]/13.3.1.7

    在复制列表初始化中,如果选择了显式构造函数 初始化格式错误。