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
будет объявленным типом переменной [...]. Если заполнителем является auto
type-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 пример]