Const auto std:: разность initializer_list между Clang и GCC

Я пытаюсь понять, каково должно быть правильное поведение С++ 11 при объединении списков инициализации и const auto. Я получаю различное поведение между GCC и Clang для следующего кода и хотел бы знать, какой из них правильный:

#include <iostream>
#include <typeinfo>
#include <vector>

int main()
{
    const std::initializer_list<int> l1 = { 1, 2, 3 };
    const auto l2 = { 1, 2, 3 };

    std::cout << "explicit: " << typeid(l1).name() << std::endl;
    std::cout << "auto:     " << typeid(l2).name() << std::endl;
}

Скомпилированный с g++ вывод:

explicit: St16initializer_listIiE
auto:     St16initializer_listIKiE

В то время как скомпилированная версия clang++ производит:

explicit: St16initializer_listIiE
auto:     St16initializer_listIiE

Кажется, что GCC поворачивает линию auto в std::initializer_list<const int>, в то время как Clang производит std::initializer_list<int>. Версия GCC создает проблему, когда я использую ее для инициализации std::vector. Таким образом, следующее работает под Clang, но создает ошибку компилятора для GCC.

// Compiles under clang but fails for GCC because l4
std::vector<int> v2 { l2 };

Если GCC создает правильную версию, то, по-видимому, предполагается, что различные контейнеры STL должны быть расширены, чтобы включить перегрузку инициализатора списка для этих случаев.

Примечание. Такое поведение кажется последовательным в нескольких версиях GCC (4.8, 4.9, 5.2) и Clang (3.4 и 3.6).

Ответ 1

Ошибка GCC. [dcl.spec.auto]/p7 (цитирование N4527):

Когда переменная, объявленная с использованием типа заполнителя, инициализируется, [...] выводимый тип возвращаемого типа или тип переменной определяется из тип его инициализатора. [...] В противном случае пусть T будет объявленным типом переменной [...]. Если заполнителем является autotype-specifier, выводимый тип определяется с использованием правил для вычитания аргумента шаблона. Если инициализация direct-list-initialization [...]. [...] В противном случае получите P из T, заменив вхождения auto либо новым изобретенным тип шаблона U или, если инициализация copy-list-initialization, с std::initializer_list<U>. Выведите значение для U, используя правила вывода аргумента шаблона из вызов функции (14.8.2.1), где P - параметр шаблона функции тип и соответствующий аргумент - это инициализатор [...]. Если декларация не соответствует действительности, декларация плохо сформирована. В противном случае тип выведенный для переменной или возвращаемого типа, получается путем подстановки выведенный U в P.

Таким образом, в const auto l2 = { 1, 2, 3 }; вывод выполняется так, как если бы для шаблона функции

template<class U> void meow(const std::initializer_list<U>);

при вызове meow({1, 2, 3}).

Теперь рассмотрим случай const-less auto l3 = { 1, 2, 3 }; (который GCC правильно выводит как std::initializer_list<int>). Вычет в этом случае выполняется так, как если бы для шаблона функции

template<class U> void purr(std::initializer_list<U>);

при вызове purr({1, 2, 3}).

Поскольку верхний уровень cv-квалификации параметров функции игнорируется, должно быть очевидно, что два вывода должны давать один и тот же тип.


[temp.deduct.call]/р1:

Вывод аргумента шаблона производится путем сравнения каждой функции тип параметра шаблона (назовите его P) с типом соответствующий аргумент вызова (назовите его A), как описано ниже. Если P является зависимым типом, удаляя ссылки и cv-квалификаторы из P дает std::initializer_list<P'> [...] для некоторого P' [...] и аргумент - это непустой список инициализаторов (8.5.4), затем вывод выполняется для каждого элемента списка инициализаторов, принимая P' как тип параметра шаблона функции и элемент инициализатора как его аргумент.

Вывод P' (который равен U) против 1, 2 или 3, все литералы типа int, очевидно, дают int.

Ответ 2

Существует отчет gcc bug неправильный автоматический вывод из файла braced-init-list об этом и подобных случаях, и Ричард Смит указывает, что это gcc ошибка:

Еще проще:

#include <initializer_list>
const auto r = { 1, 2, 3 };
using X = decltype(r);
using X = const std::initializer_list<int>;

терпит неудачу, потому что decltype(r) выводится как const std::initializer_list<const int>, а не const std::initializer_list<int>.

В разделе проекта стандарта С++ будет раздел 7.1.6.4 [dcl.spec.auto], в котором говорится:

Когда переменная, объявленная с использованием типа заполнителя, инициализируется, или оператор возврата встречается в функции объявленный с типом возврата, который содержит тип заполнителя, тип возвращаемого возврата или тип переменной определяется по типу его инициализатора. [...] Пусть T - объявленный тип переменной или возвращаемый тип функции. Если placeholder - это автоматический тип-спецификатор, выводимый тип определяется с использованием правил для аргумента шаблона вычет. [...] В противном случае получить P из T, заменив вхождения auto либо новый шаблонный шаблон шаблона U или, если инициализатор является скоординированным списком инициализации, с std:: initializer_- список. Выведите значение для U, используя правила вывода аргумента шаблона из вызова функции (14.8.2.1), где P - тип параметра шаблона функции, а инициализатор - соответствующий аргумент [...] [Пример:

auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int>
auto x2 = { 1, 2.0 }; // error: cannot deduce element type

-end example] [Пример:

const auto &i = expr;

Тип я - это выведенный тип параметра u в вызове f (expr) следующего изобретенного шаблона функции:

template <class U> void f(const U& u);

-end пример]