Магия пустых фигурных скобок в списках инициализаторов

Рассмотрим следующий минимальный пример:

#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

У меня нет вопросов по а, б и в, все достаточно понятно

Но я не могу понять, почему д работает

Что означает эта дополнительная пара фигурных скобок в d? Я посмотрел стандарт С++ 20, но не могу легко найти ответ. И clang, и gcc согласны с этим кодом, так что это я что-то упускаю

Я не уверен, но это может быть копирование/перемещение из другого временного объекта, инициализированного с помощью внутренних фигурных скобок (и, конечно, на самом деле это не будет копирование/перемещение из-за правил С++ 17).

chris 15.12.2020 02:42

@chris Я запустил его с C++ 14 и -fno-elide-constructors, и конструкторы не вызывались.

Kostas 15.12.2020 02:46
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
14
2
1 581
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Хороший трюк, чтобы получить информацию о том, что делает компилятор, состоит в том, чтобы скомпилировать все ошибки: -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}};!

Это показывает нам все, что нам нужно. Дополнительная скобка предназначена для построения списка инициализаторов.

Примечание. Если вы хотите добавить дополнительные скобки, вы можете вызвать конструктор копирования/перемещения (или исключить его), но компиляторы 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, потому что вы не можете инициализировать int из {{}}. Поэтому мы возвращаемся к использованию конструкторов. Теперь один из конструкторов принимает initializer_list, так что это похоже на начало, за исключением того, что мы уже сняли один слой фигурных скобок.


Вот почему, например, vector<int>{{1, 2, 3}} тоже работает, а не только vector<int>{1, 2, 3}. Но вроде... нет.

Спасибо, я уже принял первый ответ, но это также отличное объяснение. Кстати, я нашел еще более интересный пример безумия скобок: std::vector<std::string> vs = {{{{{}}}}}; Здесь, следуя вашему объяснению, строка построена из {{}} и вектора из {{s}} с унифицированной инициализацией {v}, вау...

Konstantin Vladimirov 15.12.2020 11:29

Другие вопросы по теме