С++ 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>}
, что является причиной двусмысленности, поскольку лучшая группа последовательностей преобразования не содержит одного кандидата, а две.