С. Мейерс - Эффективный и современный C++ (1114942), страница 7
Текст из файла (страница 7)
Когда инициализатордля переменной, объявленной как a u t o , заключен в фигурные скобки, выведенныйтип - std : : ini t ial i zer_ 1 ist. Если такой тип не может быть выведен (например, из-затого, что значения в фигурных скобках относятся к разным типам), код будет отвергнут:auto х5 = { 1 , 2 , 3 . 0 } ; / / Ошибка ! Невозможно вывести Т// для std: : initializer_l ist<T>Как указано в комментарии, в этом случае вывод типа будет неудачным, но важно понимать, что на самом деле здесь имеют место два вывода типа. Один из них вытекает из применения ключевого слова auto: тип х5 должен быть выведен.
Посколькуинициализатор х 5 находится в фигурных скобках, тип х 5 должен быть выведен какs t d : : i n i t i a l i z e r_l i s t . Но s t d : : i n i t i a l i z e r_l i s t - это шаблон. Конкретизацияпредставляет собой создание st d : : i n i t i a l i z e r_ l i s t <T> с некоторым типом Т, а этоозначает, что тип Т также должен быть выведен.
Такой вывод относится ко второй разновидности вывода типов - выводу типа шаблона. В данном примере этот второй выводнеудачен, поскольку значения в фигурных скобках не относятся к одному и тому же типу.Рассмотрение инициализаторов в фигурных скобках является единственным отличием вывода типа auto от вывода типа шаблона. Когда объявленная с использованием ключевого слова auto переменная инициализируется с помощью инициализатора в фигурныхскобках, выведенный тип представляет собой конкретизацию s t d : : ini t i a l i z er_ l i s t .Но если тот же инициализатор передается шаблону, вывод типа оказывается неудачным,и код отвергается:auto х={ 11 , 23 , 9 } ; / / Тип х-std : : initializer list<int>templa te<typename Т>void f (Т param) ;// Объявление шаблона с параметром// эквивалентно объявлению хf ( { 11 , 23// Ошибка вывода типа для Т19 }) ;Однако, если вы укажете в шаблоне, что раrаm представляет собой std: : i ni t i a l i zer_ list<T>для некоторого неизвестного т, вывод типа шаблона сможет определить, чем является Т:template<typename Т>void f ( std: : initializer_list<T> initList ) ;34Глава 1 .
Вывод типовf ( { 11 , 23 , 9 } ) ; / / Вывод int в качестве типа Т, а тип1 1 initList - std : : ini tiali zer l i s t<int>Таким образом, единственное реальное различие между выводом типа auto и выводомтипа шаблона заключается в том, что auto предполагает, что инициализатор в фигурныхскобках представляет собой std : : ini t i a l i zer_l i st, в то время как вывод типа шаблонаэтого не делает.Вы можете удивиться, почему вывод типа auto имеет специальное правило для инициализаторов в фигурных скобках, в то время как вывод типа шаблона такого правила не имеет. Но я и сам удивлен.
Увы, я не в состоянии найти убедительное объяснение. Но "закон есть закон", и это означает, что вы должны помнить, что если вы объявляете переменную с использованием ключевого слова a u t o и инициализируете еес помощью инициализатора в фигурных скобках, то выводимым типом всегда будетstd : : i n i t i a l i z e r_l i st . Особенно важно иметь это в виду, если вы приверженец философии унифицированной инициализации - заключения и нициализирующих значений в фигурные скобки как само собой разумеющегося стиля.
Классической ошибкойв С++ 1 1 является случайное объявление переменной std : : i n i t i a l i ze r_ l i s t там, где вынамеревались объявить нечто иное. Эта ловушка является одной из причин, по которымнекоторые разработчики используют фигурные скобки в инициализаторах только тогда, когда обязаны это делать.
(Когда именно вы обязаны так поступать, мы рассмотримв разделе 3. 1 .)Что касается С++ 1 1 , то на этом история заканчивается, но для С++ 14 это еще не конец. С++ 14 допускает применение auto для указания того, что возвращаемый тип функции должен быть выведен (см. раздел 1 .3), а кроме того, лямбда-выражения С++ 14 могутиспользовать auto в объявлениях параметров.
Однако такое применение auto использует вывод типа шаблона, а не вывод типа auto. Таким образом, функция с возвращаемымтипом auto, которая возвращает инициализатор в фигурных скобках, компилироватьсяне будет:auto createinitLi s t ( )return { 1 , 2 , 3 ) ; / / Ошибка : невозможно вывести/ / ТИП ДЛЯ { 1 , 2, 3 )То же самое справедливо и тогда, когда auto используется в спецификации типа параметра в лямбда-выражении С++ 14:s td : : vector<int> v ;auto resetV =[ &v] ( cons t auto& newValue ) { vresetV ( { 1 , 2 , З } ) ;newValue; ) ; 11 C++l4/ / Ошибка : невозможно вывести/ / ТИП ДЛЯ { 1 , 2, 3 )1 .2.Вывод типа auto35Сnедует запомнить•Вывод типа auto обычно такой же, как и вывод типа шаблона, но вывод типа auto,в отличие от вывода типа шаблона, предполагает, что инициализатор в фигурныхскобках представляет s td : : i n i t i a l i ze r_ l i s t .•auto в возвращаемом типе функции или параметре лямбда-выражения влечетприменение вывода типа шаблона, а не вывода типа auto.1 .3 .
Знакомство с decl typed e c l t ypeсоздание странное. Для данного имени или выражения dec l t ype сообщает вам тип этого имени или выражения. Обычно то, что сообщает decl t ype,этоименно то, что вы предсказываете. Однако иногда он дает результаты, которые заставляют вас чесать в затылке и обращаться к справочникам или сайтам.Мы начнем с типичных случаев, в которых нет никаких подводных камней.
В отличиеот того, что происходит в процессе вывода типов для шаблонов и auto (см. разделы 1 . 1и 1 .2), decl t ype обычно попугайничает, возвращая точный тип имени или выражения,которое вы передаете ему:--const int i=О;! / decltype ( i ) - const intbool f ( const Widge t & w) ; / / decltype (w) - const Widget&11 decltype ( f ) - bool ( const Widget & )struct Point {int х, у;11};11decltype ( Point : : x ) - intdecltype ( Point : : y ) - intWidget w;11decltype (w) - Widgetif (f (w) ) ."11decltype ( f {w ) ) - booltemplate<typename Т>class vector {puЫic :11Упрощенная версия std : : vectorТ& operator [ ] ( std : : si ze_t index ) ;};vector<int> v ;if (v[O]==0 ) ".Видите� Никаких сюрпризов.36Глава 1 .
В ывод типов11decltype (v) - vector<int>/ / decltype (v [ O ] ) - int&Пожалуй, основное применение decl t уре в С++ 1 1объявление шаблонов функций,в которых возвращаемый тип функции зависит от типов ее параметров. Предположим,например, что мы хотим написать функцию, получающую контейнер, который поддерживает индексацию с помощью квадратных скобок (т.е. с использованием " [ ] ") с индексом, а затем аутентифицирует пользователя перед тем как вернуть результат операциииндексации. Возвращаемый тип функции должен быть тем же, что и тип, возвращаемыйоперацией индексации.ope r a t o r [ ] для контейнера объектов типа Т обычно возвращает Т & .
Например, это так в случае s t d : : deque и почти всегда - в случае s t d : : vect or. Однакодля std : : vect or<bool> оператор operator [ ] не возвращает boo l & . Вместо этого он возвращает новый объект. Все "почему" и "как" данной ситуации рассматриваются в разделе 2.2, но главное здесь то, что возвращаемый оператором ope rator [ ] контейнера типзависит от самого контейнера.de cltype упрощает выражение этой зависимости. Вот пример, показывающий применение decl t уре для вычисления возвращаемого типа. Этот шаблон требует уточнения,но пока что мы его отложим.-template<typename Container, typename Index> / / Работает, ноauthAndAccess (Container& с , Index i )1 1 требуетauto- > decltype ( c [ i ] )11уточненияauthenticateUser ( J ;return c ( i ] ;Использование auto перед именем функции не имеет ничего общего с выводом типа. Насамом деле оно указывает, что использован синтаксис С++ 1 1завершающий возвращаемый тип (trailing return type), т.е.
что возвращаемый тип функции будет объявлен после списка параметров (после "->"). Завершающий возвращаемый тип обладает тем преимуществом, что в спецификации возвращаемого типа могут использоваться параметрыфункции. В authAndAccess, например, мы указываем возвращаемый тип с использованием с и i.
Если бы возвращаемый тип, как обычно, предшествовал имени функции, с и iбыли бы в нем недоступны, поскольку в этот момент они еще не были объявлены.При таком объявлении aut hAndAc c e s s возвращает тот тип, который возвращаетope rator [ ] при применении к переданному контейнеру, в точности как мы и хотели.С++ 1 1 разрешает вывод возвращаемых типов лямбда-выражений из одной инструкции, а С++ 1 4 расширяет эту возможность на все лямбда-выражения и все функции,включая состоящие из множества инструкций. В случае authAndAcce s s это означает, чтов С++ 1 4 мы можем опустить завершающий возвращаемый тип, оставляя только одноведущее ключевое слово auto.
При таком объявлении auto означает, что имеет местовывод типа. В частности, это означает, что компиляторы будут выводить возвращаемыйтип функции из ее реализации:-template<typename Container, typename Index> // С++ 1 4 ;auto authAndAccess ( Container& с , Index i )1 1 Не совсем1 .3. Знакомство с decltype371 1 корректноauthenticateUser ( ) ;return c [ i ] ;/ / Возвращаемый тип выводится из c [ i ]В разделе 1 .2 поясняется, что для функций с аutо-спецификацией возвращаемого типакомпиляторы применяют вывод типа шаблона.
В данном случае это оказывается проблематичным. Как уже говорилось, operator [ ] для большинства контейнеров с объектамитипа Т возвращает Т&, но в разделе 1 . 1 поясняется, что в процессе вывода типа шаблона"ссылочность" инициализирующего выражения игнорируется. Рассмотрим, что это означает для следующего клиентского кода:s t d : : deque<int> d;authAndAcces s (d, 5)10;/ / Аутентифицирует пользователя, воз// вращает d [ 5 ] , затем присваивает ему1 1 значение 1 0 . Код не компилируется !Здесь d [ 5 ] возвращает i n t & , но вывод возвращаемого типа auto для authAndAccess отбрасывает ссылку, тем самым давая возвращаемый тип i n t . Этот int, будучи возвращаемым значением функции, является rvalue, так что приведенный выше код пытаетсяприсвоить этому rvalue типа int значение IO.
Это запрещено в С++, так что данный кодне компилируется.Чтобы заставить authAndAccess работать так, как мы хотим, нам надо использоватьдля ее возвращаемого типа вывод типа decltype, т.е. указать, что authAndAccess должнавозвращать в точности тот же тип, что и выражение с [ i ] . Защитники С++, предвидя необходимость использования в некоторых случаях правил вывода типа decl t уре, сделали этовозможным в С++ \ 4 с помощью спецификатора decltype ( auto ) .