С. Мейерс - Эффективный и современный C++ (1114942), страница 6
Текст из файла (страница 6)
Вывод типовtemplate<typeпame Т>void f ( T param) ; / / Шаблон , получающий параметр по значениюf (пame ) ;// Какой тип Т и param будет выведен?Начнем с наблюдения, что не существует такой вещи, как параметр функции, являющийся массивом. Да, да - приведенный далее синтаксис корректен:void myFuпc ( iпt param [ ] ) ;Однако объявление массива рассматривается как объявление указателя, а это означает,что функция myFuпc может быть эквивалентно объявлена какvoid myFuпc ( iпt* param) ; // Та же функция , что и ранееЭта эквивалентность параметров, представляющих собой массив и указатель, образно говоря, представляет собой немного листвы от корней С на дереве С++ и способствует возникновению иллюзии, что типы массивов и указателей представляют собой одно и то же.Поскольку объявление параметра-массива рассматривается так, как если бы это былообъявление параметра-указателя, тип массива, передаваемого в шаблонную функциюпо значению, выводится как тип указателя.
Это означает, что в вызове шаблонной функции f ее параметр типа Т выводится как const cha r * :f (пame ) ; // паmе - массив, н о Т - coпst char*А вот теперь начинаются настоящие хитрости. Хотя функции не могут объявлять параметры как истинные массивы, они могут объявлять параметры, являющиеся ссьткамина массивы! Так что если мы изменим шаблон f так, чтобы он получал свой аргументпо ссылке,template<typeпame Т>void f ( T& param) ; / / Шаблон с передачей параметра по ссылкеи передадим ему массивf (пame ) ;/ / Передача массива функции fто тип, выведенный для Т, будет в действительности типом массива! Этот тип включаетразмер массива, так что в нашем примере т выводится как const char [ l 3 ] , а типомпараметра f (ссылки на этот массив) является const char ( & ) [ 1 3 ] .
Да, выглядит этотсинтаксис как наркотический бред, но знание его прибавит вам веса в глазах понимающих людей.Интересно, что возможность объявлять ссылки на массивы позволяет создать шаблон, который выводит количество элементов, содержащихся в массиве:// Возвращает размер массива как константу времени компиляции .// Параметр не имеет имени, поскольку, кроме количества// содержащихся в нем элементов, нас ничто не интересует .template<typeпame Т, s td : : s ize_t N>coпstexpr std: : size t arraySize ( T (&) [N] ) поехсерt1.1.Вывод типа шаб л она29returп N ;Как поясняется в разделе 3.9, объявление этой функции как coпstexpr делает ее результат доступным во время компиляции.
Это позволяет объявить, например, массивс таким же количеством элементов, как и у второго массива, размер которого вычисляется из инициализатора в фигурных скобках:11 keyVa ls содержит 7 элементов :iпt keyVa l s [ ] = { 1 , 3 , 7 , 9, 1 1 , 2 2 , 35 };iпt rnappedVal s [arraySize (keyVals) ] ; // rnappedVal s-тожеКонечно, как разработчик на современном С++ вы, естественно, предпочтете std: : arrayвстроенному массиву:11 Размер mappedVal s равен 7std : : array<iпt , arraySize (keyVals) > mappedVal s ;Что касается объявления arrayS i ze как поехсерt, то это помогает компилятору генерировать лучший код.
Детальнее этот вопрос рассматривается в разделе 3.8.Ар rументы-функцииМассивы - не единственные сущности в С++, которые могут превращаться в указатели. Типы функций могут превращаться в указатели на функции, и все, что мы говорилио выводе типов для массивов, применимо к выводу типов для функций и их преобразованию в указатели на функции.
В результате получаем следующее:void someFunc ( iпt, douЬl e ) ; / / s omeFuпc - функция;11 ее тип - void ( iп t , douЬle)template<typeпame Т>11 В f l param передается по значениюvoid f1 ( Т param} ;template<typeпame Т>void f2 ( T & param) ;11 В f2 param передается по ссылкеf1 ( s omeFuпc} ;11 param выводится как указатель на11 функцию; тип - void ( * ) ( iпt , douЬle)f2 ( s omeFuпc) ;11 param выводится как ссыпка на11 функцию; тип - void ( & ) ( iпt, douЬle )Это редко приводит к каким-то отличиям н а практике, но если в ы знаете о преобразовании массивов в указатели, то разберетесь и в преобразовании функций в указатели.Итак, у нас есть правила для вывода типов шаблонов, связанные с auto.
В начале я заметил, что они достаточно просты, и по большей части так оно и есть. Немного усложняет жизнь отдельное рассмотрение согласованных lvalue при выводе типов30Глава 1 . Вывод типовдля универсальных ссылок, да еще несколько "мутят воду" правила преобразованияв указатели для массивов и функций. Иногда так и хочется, разозлившись, схватить компилятор и вытрясти из него "А скажи-ка, любезный, какой же тип ты выводишь?" Когда это произойдет, обратитесь к разделу 1 .4, поскольку он посвящен тому, как уговоритькомпилятор это сделать.-Сл едует запомнить•В процессе вывода типа шаблона аргументы, являющиеся ссылками, рассматриваются как ссылками не являющиеся, т.е.
их "ссылочность" игнорируется.•При выводе типов для параметров, являющихся универсальными ссылками, lvalueapryмeнты рассматриваются специальным образом.•При выводе типов для параметров, передаваемых по значению, аргументы, объявленные как const и/или volat i le, рассматриваются как не являющиеся ни const,ни volat i le.•В процессе вывода типа шаблона аргументы, являющиеся именами массивовили функций, преобразуются в указатели, если только они не использованыдля инициализации ссылок.1 .2. Вывод типа autoЕсли вы прочли раздел 1 . 1 о выводе типов шаблонов, вы знаете почти все, что следуетзнать о выводе типа auto, поскольку за одним любопытным исключением вывод типаauto представляет собой вывод типа шаблона. Но как это может быть? Вывод типа шаблона работает с шаблонами, функциями и параметрами, а auto не имеет дела ни с однойиз этих сущностей.Да, это так, но это не имеет значения.
Существует прямая взаимосвязь между выводом типа шаблона и выводом типа auto. Существует буквальное алгоритмическое преобразование одного в другой.В разделе 1 . 1 вывод типа шаблона пояснялся с использованием обобщенного шаблонафункцииtemplate<typename Т>void f (Paraш7YPe param) ;и обобщенного вызоваf ( expr) ; // Вызов f с некоторым выражениемПри вызове f компиляторы используют expr для вывода типов т и ParamType.Когда переменная объявлена с использованием ключевого слова auto, оно играетроль Т в шаблоне, а спецификатор типа переменной действует как ParamType.
Это прощепоказать, чем описать, так что рассмотрим следующий пример:autoх =27 ;1 .2 .Вывод типа auto31Здесь спецификатором типа для х является auto само по себе. С другой стороны, в объявленииconst auto сх=х;спецификатором типа является const auto. А в объявленииconst auto& rx=х;спецификатором типа является const auto & . Для вывода типов для х, сх и rx в приведенных примерах компилятор действует так, как если бы для каждого объявленияимелся шаблон, а также вызов этого шаблона с соответствующим инициализирующимвыражением:template<typename Т>void func for х ( Т param) ;1 1 Концептуальный шаблон для11 вывода типа хfunc for_x ( 2 7 ) ;1 1 Концептуальный вызо в : выве1 1 денный тип param является11 типом хtemplate<typename Т>void func_for_cx ( const Т param) ;1 1 Концептуальный шаблон для11 вывода типа схfunc for_cx ( x ) ;11 Концептуальный вызо в : выве11 денный тип param является1 1 ТИПОМ СХ--11 Концептуальный шаблон дляtemplate<typename Т>11void func_for_rx ( const Т& param ) ;вывода типа rx11 Концептуальный вызов : выве11 денный тип param является11 типом rxfunc for_rx ( x ) ;-Как я уже говорил, вывод типов для auto представляет собой (с одним исключением,которое мы вскоре рассмотрим) то же самое, что и вывод типов для шаблонов.В разделе l .
l вывод типов шаблонов был разделен на три случая, основанных на характеристиках ParamType, спецификаторе типа param в обобщенном шаблоне функции.В объявлении переменной с использованием auto спецификатор типа занимает местоParamType, так что у нас опять имеются три случая.•Случай l . Спецификатор типа представляет собой ссылку или указатель, но неуниверсальную ссылку.•Случай 2. Спецификатор типа представляет собой универсальную ссылку.•Случай 3.
Спецификатор типа не является ни ссылкой, ни указателем.Мы уже встречались со случаями32Гnава 1 . В ывод типовlи 3:autoх = 2 7 ; / / Случай 3 ( х не указатель и не ссЬUiка )conзt auto схх; / / Случай 3 (сх не указатель и не ссыпка )conзt auto& rxх; / / Случай 1 ( rx - неуниверсальная ссылка )Случай 2 работает, как и ожидалось:auto&& ureflauto&& uref2auto&& ure f3х; 11 х - int и lva lue, так что тип urefl - int&сх ; 11 сх - const int и lvalue , так что тип1 1 ure f2 - const int &2 7 ; 1 1 2 7 - int и rvalue, так что тип1 1 uref 3 - int & &Раздел 1.1 завершился обсуждением того, как имена массивов и функций превращаются в указатели для спецификаторов типа, не являющихся ссылками. То же самое происходит и при выводе типа auto:const char name [ ]" R . N . Briggs " ;name ;auto arrlauto& arr2 = name ;=11 Тип name - const char [ 1 3 ]11 Тип arrl11 Тип arr2const char*const char ( &) [ 1 3 ]void someFunc ( int , douЬle ) ; 11 someFunc - функция, ее тип11 void ( int , douЬl e )11someFunc;Тип funcl - void ( * ) ( int, douЬle)auto funcl11 Тип func2 - void ( & ) ( in t , douЬle)someFunc;auto& func2Как можно видеть, вывод типа auto работает подобно выводу типа шаблона.
По сути этодве стороны одной медали.Они отличаются только в одном. Начнем с наблюдения, что если вы хотите объявить intс начальным значением 27, С++98 предоставляет вам две синтаксические возможности:int x l = 2 7 ;int х2 ( 2 7 ) ;С++ 1 1, поддерживая старые варианты инициализации, добавляет собственные:int х3{ 27 ) ;int х4 { 2 7 } ;=Таким образом, у нас есть четыре разных синтаксиса, но результат один: переменнаятипа int со значением 27.Но, как поясняется в разделе 2 .
1 , объявление переменных с использованием ключевого слова auto вместо фиксированных типов обладает определенными преимуществами,поэтому в приведенных выше объявлениях имеет смысл заменить int на auto. Простаязамена текста приводит к следующему коду:autoautoautoautoxl = 2 7 ;х2 ( 2 7 ) ;{ 27 } ;х3х4 { 2 7 } ;=1 .2. Вывод типа auto33Все эти объявления компилируются, но их смысл оказывается не тем же, что и у объявлений, которые они заменяют. Первые две инструкции в действительности объявляютпеременную типа int со значением 27. Вторые две, однако, определяют переменную типаs t d : : i n i t ia l i zer l i st <int>, содержащую единственный элемент со значением 27!_autoautoautoautoxl27;х2 ( 2 7 ) ;хЗ = { 2 7 } ;х4 { 2 7 } ;=11111111Тип int , значение27То же самоеstd : : initiali zer_list<int>, значение ( 2 7 }То же самое-Это объясняется специальным правилом вывода типа для auto.