Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - страница 14
Они отличаются только в одном. Начнем с наблюдения, что если вы хотите объявить >int
с начальным значением 27, С++98 предоставляет вам две синтаксические возможности:
>int x1 = 27;
>int x2(27);
С++11, поддерживая старые варианты инициализации, добавляет собственные:
>int x3 = { 27 };
>int x4{ 27 };
Таким образом, у нас есть четыре разных синтаксиса, но результат один: переменная типа >int
со значением 27.
Но, как поясняется в разделе 2.1, объявление переменных с использованием ключевого слова >auto
вместо фиксированных типов обладает определенными преимуществами, поэтому в приведенных выше объявлениях имеет смысл заменить >int
на >auto
. Простая замена текста приводит к следующему коду:
>auto x1 = 27;
>auto x2(27);
>auto x3 = { 27 };
>auto x4{ 27 };
Все эти объявления компилируются, но их смысл оказывается не тем же, что и у объявлений, которые они заменяют. Первые две инструкции в действительности объявляют переменную типа >int
со значением 27. Вторые две, однако, определяют переменную типа >std::initializer_list
, содержащую единственный элемент со значением 27!
>auto x1 = 27; // Тип int, значение - 27
>auto x2(27); // То же самое
>auto x3 = { 27 }; // std::initializer_list
>auto x4{ 27 }; // То же самое
Это объясняется специальным правилом вывода типа для >auto
. Когда инициализатор для переменной, объявленной как >auto
, заключен в фигурные скобки, выведенный тип — >std::initializer_list
. Если такой тип не может быть выведен (например, из-за того, что значения в фигурных скобках относятся к разным типам), код будет отвергнут:
>auto x5 = { 1, 2, 3.0 }; // Ошибка! Невозможно вывести Т
> // для std::initializer_list
Как указано в комментарии, в этом случае вывод типа будет неудачным, но важно понимать, что на самом деле здесь имеют место два вывода типа. Один из них вытекает из применения ключевого слова >auto
: тип >x5
должен быть выведен. Поскольку инициализатор >x5
находится в фигурных скобках, тип >x5
должен быть выведен как >std::initializer_list
. Но >std::initializer_list
— это шаблон. Конкретизация представляет собой создание >std::initializer_list
с некоторым типом >Т
, а это означает, что тип >Т
также должен быть выведен. Такой вывод относится ко второй разновидности вывода типов — выводу типа шаблона. В данном примере этот второй вывод неудачен, поскольку значения в фигурных скобках не относятся к одному и тому же типу.
Рассмотрение инициализаторов в фигурных скобках является единственным отличием вывода типа >auto
от вывода типа шаблона. Когда объявленная с использованием ключевого слова >auto
переменная инициализируется с помощью инициализатора в фигурных скобках, выведенный тип представляет собой конкретизацию >std::initializer_list
. Но если тот же инициализатор передается шаблону, вывод типа оказывается неудачным, и код отвергается:
>auto x = { 11, 23, 9 }; // Тип x – std::initializer_list
>template
>void f(Т param); // эквивалентно объявлению x
>f({ 11, 23, 9 }); // Ошибка вывода типа для Т
Однако, если вы укажете в шаблоне, что >param
представляет собой >std::initializer_list
для некоторого неизвестного >Т
, вывод типа шаблона сможет определить, чем является >Т
:
>template
>void f(std::initializer_list<T> initList);
>f({ 11, 23, 9 }); // Вывод int в качестве типа Т, а тип
> // initList - std::initializer_list
Таким образом, единственное реальное различие между выводом типа >auto
и выводом типа шаблона заключается в том, что >auto
предполагает, что инициализатор в фигурных скобках представляет собой >std::initializer_list
, в то время как вывод типа шаблона этого не делает.
Вы можете удивиться, почему вывод типа >auto
имеет специальное правило для инициализаторов в фигурных скобках, в то время как вывод типа шаблона такого правила не имеет. Но я и сам удивлен. Увы, я не в состоянии найти убедительное объяснение. Но “закон есть закон”, и это означает, что вы должны помнить, что если вы объявляете переменную с использованием ключевого слова