Определение шаблонных аргументов 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.

→ Ссылка
Автор решения: Andrey Sv

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 ошибка.

→ Ссылка