6

В следующей программе аргументы шаблона определяются без проблем 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'

dIm0n
  • 407
  • Мне кажется кланг прав. Не могу найти никаких указаний, что CTAD должен работать для initializer_list. – HolyBlackCat Aug 06 '20 at 11:13
  • Какая разница? =) Стандарт не регламентирует абсолютно всё. Часть реализации остаётся на усмотрение разработчиков компилятора. Нужно идти от обратного, если что-то не запрещено -- значит оно разрешено, так что тут нет правых и неправых. – megorit Aug 06 '20 at 11:19
  • @Πανμέτρονάριστον так если это implementation defined, то так и должно быть написано в стандарте. Если так и написано, то да — оба будут правы – dIm0n Aug 06 '20 at 11:21
  • @dIm0n, если ничего не написано, то это уже implementation defined =) – megorit Aug 06 '20 at 11:22
  • 1
    @Πανμέτρονάριστον Это так не работает. Во первых, "не написано" = неопределенное поведение, а не implementation defined (насколько я помню), а во вторых надо еще убедиться, что оно там не написано. Вдруг там после перечисления случаев, в которых работает CTAD, написно "если ни один пункт не подходит, то программа - ill-formed", или что-то в этом духе. – HolyBlackCat Aug 06 '20 at 11:48
  • @HolyBlackCat, я в русском языке не знаю как указать разницу между UB и ID. Так что я надеялся, что итак понятно. – megorit Aug 06 '20 at 12:00
  • @Πανμέτρονάριστον Так вы ж ниже прямым текстом написали, что имели в виду implementation defined. – HolyBlackCat Aug 06 '20 at 12:01
  • @Πανμέτρονάριστον по поводу различий UB, ID есть такой вопросик. – αλεχολυτ Aug 09 '20 at 18:05
  • @αλεχολυτ, я знаю в чем разница, просто по-русски не всегда понятно как выразиться. – megorit Aug 09 '20 at 18:06

2 Answers2

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.

αλεχολυτ
  • 28,987
  • 13
  • 60
  • 119
2

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

Andrey Sv
  • 1,031
  • 1
    Здесь есть пара вопросов: разрешены ли вообще пользовательские гайды для std типов и должна ли запись вида std::initializer_list x{1,2,3}; интерпретироваться как вызов копирующего конструктора (всё же это list-initialization). – αλεχολυτ Aug 15 '20 at 09:56
  • @αλεχολυτ Гайд для демонстрации поведения компилятора и никак не руководство к действию. И, да, запись подобного вида для нетривиальных объектов интерпретируется как вызов конструктора. "For copy-initialization (including default initialization in the context of copy-initialization), the candidate functions are all the converting constructors ([class.conv.ctor]) of that class" В продолжение. Там в принципе есть перекрестные ссылки. – Andrey Sv Aug 15 '20 at 12:30
  • Так а почему это должно быть copy-initialization? Я вот считаю, что это direct-initialization. Например, вот здесь никакого копирования объекта нет, а есть прямая инициализация. – αλεχολυτ Aug 15 '20 at 14:04
  • @αλεχολυτ Да, если это возможно для тривиального объекта, но если мы допишем подходящий конструктор, то вызовется он https://godbolt.org/z/zsaTfx. Вообще это уже совсем другой вопрос. – Andrey Sv Aug 15 '20 at 14:11