Определение шаблонных аргументов std::initializer_list
В следующей программе аргументы шаблона определяются без проблем gcc и clang.
#include <vector>
int main() {
std::vector x{1,2,3};
}
Однако здесь почему-то clang не может их определить, а gcc может (godbolt):
#include <initializer_list>
int main() {
std::initializer_list x{1,2,3};
}
Почему так? Кто прав?
Ошибка компиляции у clang:
error: no viable constructor or deduction guide for deduction of template arguments of 'initializer_list'
Ответы (2 шт):
Class template argument deduction (CTAD) работает на основании существующих (специальных) правил вывода типа, а также имеющихся конструкторов класса. Собственно об этом как раз говорит текст ошибки, приведённый в вопросе:
error: no viable constructor or deduction guide for deduction of template arguments of 'initializer_list'
Для std::vector вывод основан на конструкторе (9):
vector(std::initializer_list<T> init, const Allocator& alloc = Allocator());
У std::initializer_list специальных правил вывода типа нет, а конструктор имеется только один - для создания пустого списка. Таким образом, оснований для вывода типа в записи вида std::initializer_list x{1,2,3}; нет.
Здесь, как я понимаю, сыграло роль то, что тип std::initializer_list в языке довольно особенный и создаётся неявно самим языком при наличии фигурных скобок в определённых контекстах. Но вот отдельного правила для вывода типа для него не предусмотрено. То, что gcc и msvc выводят тип может быть основано на том, что внутренне запись
std::initializer_list x{1,2,3};
интерпретируется как
std::initializer_list x = std::initializer_list<int>{1,2,3};
И тогда, конечно, вывод типа работает.
P.S. Некоторые идеи получены из ответа на enSO.
std::vector имеет конструктор, который принимает на вход std::initializer_list, поэтому поведение как gcc так и clang никого не удивляет.
Но, давайте рассмотрим такой пример:
template <class T>
struct S {
S(){}
S(T, int, long){}
};
S<int> s1; //#1
S s2(s1); //#2
Обратите внимание на инициализацию #2. В данном случае выведение типа используется для конструктора копирования, сгенерированного компилятором по умолчанию. С этим кодом спокойно работает как gcc так и clang, так как, на сколько я вижу, все тут соответствует стандарту, и конструктор по умолчанию и CTAD.
Теперь применительно к std::initializer_list. Он также имеет конструктор копирования сгенерированный компилятором и соответственно может принимать в качестве параметра std::initializer_list. Поэтому инициализация std::initializer_listничем не отличается от инициализации любого класса, который имеет конструктор с таким параметром. И тут также нет ничего противоречащего стандарту, по крайней мере я не увидел. С этим согласны большинство компиляторов кроме clang.
Данный код в clang не работает:
#include <initializer_list>
int main() {
std::initializer_list x{1,2,3};
}
Но, если указать тип вручную
std::initializer_list<int> x{1,2,3};
то clang тут же обнаружит нужный конструктор. Также работоспособности можно добиться указав гайд (только для демонстрации поведения компилятора, использование где либо еще не рекомендуется):
namespace std {
template<class T> initializer_list(const std::initializer_list<T>&) -> initializer_list<T>;
}
после этого CTAD начнет работать и в clang. Взято из комментариев к этому ответу Посмотреть как это работает можно здесь
Вывод такой, что clang необоснованно не выводит тип в конструкторе копирования std::initializer_list. Т.е. в clang ошибка.