初始化列表中的空括号魔法

Empty braces magic in initializer lists

考虑以下最小示例:

#include <iostream>

struct X {
  X() { std::cout << "Default-ctor" << std::endl; }
  X(std::initializer_list<int> l) { 
      std::cout << "Ilist-ctor: " << l.size() << std::endl; 
  }
};

int main() {
    X a{};
    X b({}); // reads as construct from {}
    X c{{}}; // reads as construct from {0}
    X d{{{}}}; // reads as construct from what?
    // X e{{{{}}}}; // fails as expected
}

Godbolt example

我对a、b、c没有问题,一切都比较清楚了

但我不明白为什么 d 有效

d 中这对额外的大括号代表什么?我查阅了 C++20 标准,但我无法轻易找到答案。 clang 和 gcc 都同意这段代码,所以是我错过了一些东西

获取有关编译器功能的信息的一个好技巧是使用所有错误进行编译: -Weverything。让我们在这里查看输出(仅限 d):

9.cpp:16:6: warning: constructor call from initializer list is incompatible with C++98                                                                                            
      [-Wc++98-compat]                                                                                                                                                            
  X d{{{}}}; // reads as construct from what?                                                                                                                                     
     ^~~~~~                           

X::X(std::initializer_list) 被调用。

9.cpp:16:8: warning: scalar initialized from empty initializer list is incompatible with                                                                                          
      C++98 [-Wc++98-compat]                                                                                                                                                      
  X d{{{}}}; // reads as construct from what?                                                                                                                                     
       ^~                               

标量 (int) 在内部 {} 中初始化。所以我们有 X d{{0}}.

9.cpp:16:7: warning: initialization of initializer_list object is incompatible with                                                                                               
      C++98 [-Wc++98-compat]                                                                                                                                                      
  X d{{{}}}; // reads as construct from what?                                                                                                                                     
      ^~~~                                                                                                                                                                        
5 warnings generated.                                                                                                                                                             

std::initializer_list{0} 初始化。所以我们有 X d{std::initializer_list<int>{0}};!

这向我们展示了我们需要的一切。额外的括号用于构造初始化列表。

注意:如果你想添加额外的括号,你可以通过调用 copy/move 构造函数(或省略它),但 C++ 编译器不会隐式地这样做为您防止错误:

X d{X{{{}}}}; // OK
X e{{{{}}}}; // ERROR

以为我只是举例说明:

X d{               {                       {}        }};
   |               |                       |
   construct an    |                       |
   `X` from ...    an initializer_list     |
                   containing...           int{}

列表初始化的规则是找到一个 initializer_list<T> 构造函数并尽可能使用它,否则...枚举构造函数并执行正常操作。

X{{}},即列表初始化:最外层的{}initializer_list,其中包含一个元素:{},即0。足够直截了当(虽然含糊)。

但是对于 X{{{}}},这不再适用于使用最外层的 {} 作为 initializer_list,因为您无法从 [=21] 初始化 int =].所以我们回退到使用构造函数。现在,其中一个构造函数采用 initializer_list,所以这有点像重新开始,只是我们已经剥掉了一层括号。


这就是为什么 vector<int>{{1, 2, 3}} 也有效,而不仅仅是 vector<int>{1, 2, 3}。但是喜欢...不要。