С++ std:: initializer_list конструктор

В этом коде:

#include <iostream> 
#include <initializer_list>
#include <string>

struct A 
{
  A() { std::cout << "2" << std::endl; }
  A(int a) { std::cout << "0" << std::endl; }
  A(std::initializer_list<std::string> s) { std::cout << "3" << std::endl; }
  A(std::initializer_list<int> l) { std::cout << "1" << std::endl; } 
};

int main() 
{ 
 A a1{{}}; 
} 

Почему он вызывает конструкцию конструктора std::initializer_list<int>? Он будет генерировать ошибку компиляции неоднозначности, если мы определим, например, конструктор с std::initializer_list<double>. Каковы правила такого построения и почему это так специфично в отношении std::initializer_list с номером в качестве аргумента шаблона?

Ответ 1

{} для скалярного типа (например, int, double, char* и т.д.) является преобразованием идентичности.

{} к типу класса, отличному от специализации std::initializer_list (например, std::string), является определяемым пользователем преобразованием.

Бывший бьет последний.

Ответ 2

Если класс имеет конструктор списка инициализаторов, то {whatever goes here} означает передать {whatevergoeshere} в качестве аргумента существующим конструкторам (если нет конструкторов списка инициализаторов, тогда whatever goes here передаются как аргументы).

Итак, упростите настройку и проигнорируйте другие конструкторы, потому что, по-видимому, компиляторы не заботятся о них

void f(std::initializer_list<std::string> s);
void f(std::initializer_list<int> l); 

В f({{}}) мы имеем это правило

В противном случае, если тип параметра - std:: initializer_list, и все элементы списка инициализаторов могут быть неявно преобразованы в X, неявная последовательность преобразований является наихудшим преобразованием, необходимым для преобразования элемента списка в X, или если в списке инициализаторов нет элементов, преобразование идентичности. Это преобразование может быть пользовательским преобразованием даже в контексте вызова конструктора списка инициализаторов.

Здесь у нас есть один элемент {}, и для инициализации std::string требуется определенное преобразование пользователя, а для int нет конверсии (identity). Поэтому выбирается int.

Для f({{{}}}) элемент {{}}. Может ли он быть преобразован в int? Правило

  • если в списке инициализаторов есть один элемент, который сам по себе не является списком инициализаторов, неявная последовательность преобразования является той, которая требуется для преобразования элемента в тип параметра
  • ...
  • Во всех случаях, кроме перечисленных выше, преобразование невозможно.

Можно ли преобразовать его в std::string? Да, потому что он имеет конструктор списка инициализаторов, который имеет параметр std::initializer_list<char> init. Поэтому на этот раз выбирается std::string.


Разница в A a3({}) заключается в том, что в этом случае она не перечисляет инициализацию, а "нормальную" инициализацию с аргументом {} (обратите внимание, что одно меньше вложенности из-за отсутствующих внешних фигурных скобок). Здесь две наши функции f вызываются с помощью {}. И поскольку в обоих списках нет элементов, для обоих мы имеем конверсии идентичности и, следовательно, двусмысленность.

В этом случае компилятор также рассмотрит f(int) и получит связь с двумя другими функциями. Но будет применяться тай-брейк, который объявит int -параметр хуже параметров initializer_list. Таким образом, у вас есть частичный порядок {int} < {initializer_list<string>, initializer_list<int>}, что является причиной двусмысленности, поскольку лучшая группа последовательностей преобразования не содержит одного кандидата, а две.