【问题标题】:Why can I not brace initialize a struct derived from another struct?为什么我不能大括号初始化从另一个结构派生的结构?
【发布时间】:2013-06-03 17:27:37
【问题描述】:

当我运行这段代码时:

struct X {
    int a;
};

struct Y : public X {};

X x = {0};
Y Y = {0};

我明白了:

error: could not convert ‘{0}’ from ‘<brace-enclosed initializer list>’ to ‘Y’

为什么大括号初始化适用于基类而不适用于派生类?

【问题讨论】:

    标签: c++ c++11 struct list-initialization


    【解决方案1】:

    对 C++17 之前的 C++ 标准版本的回答:

    您的问题与 aggregate initialization 有关:struct X 是一个聚合,而 struct Y 不是。这是关于聚合 (8.5.1) 的标准引用:

    聚合是一个数组或一个类(第 9 条),没有用户提供的构造函数(12.1),没有用于非静态数据成员的大括号或等号初始化器(9.2),没有私有或受保护的非静态数据成员(第 11 条),没有基类(第 10 条),也没有虚函数(10.3)。

    此子句指定如果class 具有基类,则它不是聚合。这里,struct Ystruct X 作为基类,因此不能是聚合类型。

    关于您遇到的特定问题,请从标准中获取以下条款:

    当聚合由初始化列表初始化时,如 8.5.4 中所指定,初始化列表的元素被视为聚合成员的初始化,按递增的下标或成员顺序。每个成员都是从相应的初始化子句复制初始化的。如果 initializer-clause 是一个表达式,并且需要一个窄化转换 (8.5.4) 来转换该表达式,则该程序是非良构的。

    当你做X x = {0}时,聚合初始化用于将a初始化为0。但是,当您执行Y y = {0} 时,由于struct Y 不是聚合类型,编译器将寻找合适的构造函数。由于没有一个隐式生成的构造函数(默认、复制和移动)可以对单个整数执行任何操作,因此编译器会拒绝您的代码。


    关于此构造函数查找,来自 clang++ 的错误消息更明确地说明了编译器实际尝试执行的操作 (online example):

    Y Y = {0};
      ^   ~~~
    
    main.cpp:5:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'int' to 'const Y &' for 1st argument
    
    struct Y : public X {};
           ^
    
    main.cpp:5:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'int' to 'Y &&' for 1st argument
    
    struct Y : public X {};
           ^
    
    main.cpp:5:8: note: candidate constructor (the implicit default constructor) not viable: requires 0 arguments, but 1 was provided
    

    请注意,a proposal 可以扩展聚合初始化以支持您的用例,并且它已进入 C++17。如果我阅读正确,它会使您的示例对您期望的语义有效。所以...您只需要等待符合 C++17 的编译器即可。

    【讨论】:

    • clang++ -std=c++1z 版本 4.0.1 编译,g++ -std=c++17 版本 7.2.1 编译,但 Visual Studio 2017 cl 版本 19.12.25816 还没有
    • 自今年夏天 VS 2017 15.7 起应为 now supported
    • c++14 用户最干净的替代方案是什么?
    【解决方案2】:

    使用 C++17 及更高版本,您可以大括号初始化您的派生结构。

    您的代码现在compiles (GodBolt)。

    您确实会收到有关使用大括号的警告。因此,初始化Y 的推荐方法是:

    Y y = { {0} };
    

    而不是;

    Y y = { 0 };
    

    这是因为这种继承结构现在被认为是“聚合类型”(支持这种类型的聚合初始化)。请参阅 2015 年的 this paper

    【讨论】:

      【解决方案3】:

      这个问题仍然在谷歌搜索结果中,所以我会在这里说。 在 C++17 中是可能的。 只有无构造器,无虚拟,无私有/受保护,但对于派生的 PODS,现在是可能的。

      注意:今年 (2021) eclipse for STM32 (CubeIDE) 仍然不提供 c++17 的菜单选择,但可以手动设置,例如 -std=gnu++17

      【讨论】:

        【解决方案4】:

        C++14 及更低版本应在构造函数中使用默认初始化程序。

        struct Base
        {
          int member1{-54}; 
          std::string member2{"Base"};  
        };  
        
        
        struct OtherType : Base
        {
          OtherType = default;
          OtherType(int i,std::string s, int j) : Base{i,s}, member3{j} {}
                
          int member3{5};
        };
        
        int main()
        {
        
          OtherType ot{ 45, "other", 13} ;
        
          OtherType ot = { 13, "heynow", -1};
        
          OtherType ot;
        
          std::cout << "{" << ot.member1
                << ", " << ot.member2 
                << ", " << ot.member3 << "}\n";
        
          return 0;
        }
        

        C++17 -20 最适合:

        struct Base
            {
              int member1{-54}; 
              std::string member2{"Base"};  
            };
        
        struct OtherType : Base
            {       
              int member3{5};
            };
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-07-13
          • 1970-01-01
          • 2014-07-31
          • 1970-01-01
          • 1970-01-01
          • 2016-02-25
          • 2013-09-16
          相关资源
          最近更新 更多