С. Мейерс - Эффективный и современный C++ (1114942), страница 8
Текст из файла (страница 8)
То, что изначально можетпоказаться противоречием (decltype и auto?), в действительности имеет смысл: auto указывает, что тип должен быть выведен, а decltype говорит о том, что в процессе вывода следуетиспользовать правила decltype. Итак, можно записать authAndAccess следующим образом:template<typename Container, t ypename Index> / /////authAndAccess ( Contai ner& с, I ndex i )//authent icateUser ( ) ;return c [ i ] ;declt:ype (auto)С++ 1 4 ; работает,но все ещетребуетуточненияТеперь authAndAccess действительно возвращает то же, что и с [ i ] .
В частности, в распространенном случае, когда с [ i ] возвращает Т & , authAndAccess также возвращает Т&, и в томредком случае, когда с [ i ] возвращает объект, authAndAccess также возвращает объект.Использование decltype ( auto) не ограничивается возвращаемыми типами функций.Это также может быть удобно для объявления переменных, когда вы хотите применятьправила вывода типа decltype к инициализирующему выражению:38Гnава 1 . Вывод типовWidget w;const Widget& cw = w;auto myWidgetl = cw;1111declt:ype (auto) myWidget2Вывод типа auto :тип myWidgetl - Widgetcw; // Вывод типа decltype :1 1 тип myWidget2 - coпst Widget &Я знаю, что вас беспокоят два момента. Один из них - упомянутое выше, но пока неописанное уточнение authAndAccess.
Давайте, наконец-то, разберемся в этом вопросе.Еще раз посмотрим на версию aut hAndAcce s s в С++ 1 4:template<typename Container, typename Index>decltype ( auto) authAndAcces s ( Container& с, Index i ) ;Контейнер передается как lvalue-ccылкa на неконстантный объект, поскольку возвращаемая ссылка на элемент контейнера позволяет клиенту модифицировать этот контейнер. Ноэто означает, что этой функции невозможно передавать контейнеры, являющиеся rvalue.rvalue невозможно связать с lvаluе-ссылками (если только они не являются lvаluе-ссылкамина константные объекты, что в данном случае очевидным образом не выполняется).Надо сказать, что передача контейнера, являющегося rvalue, в aut hAndAcce s s является крайним случаем. Такой rvаluе-контейнер, будучи временным объектом, обычноуничтожается в конце инструкции, содержащей вызов authAndAcce ss, а это означает, чтоссылка на элемент в таком контейнере (то, что должна вернуть функция authAndAccess)окажется "висячей" в конце создавшей ее инструкции.
Тем не менее передача временногообъекта функции aut hAndAcces s может иметь смысл. Например, клиент может простохотеть сделать копию элемента во временном контейнере:std : : deque< std : : string> makeStringDeque ( ) ; 11 Фабричная функция/ / Делаем копию пятого элемента deque, возвращаемого11 функцией makeStringDequeauto s = authAndAcces s (makeStringDeque ( ) , 5 ) ;Поддержка такого использования означает, что мы должны пересмотреть объявлениефункции authAndAccess, которая должна принимать как lvalue, так и rvallle.
Можно использовать перегрузку (одна функция объявлена с параметром, представляющим собойlvalue-ccылкy, а вторая - с параметром, представляющим собой rvalue-ccылкy), но тогданам придется поддерживать две функции. Избежать этого можно, если у нас будет функция authAndAccess, использующая ссылочный параметр, который может быть связан какс lvalue, так и с rvalue, и в разделе 5.2 поясняется, что это именно то, что делают универсальные ссылки.
Таким образом, authAndAccess может быть объявлена следующим образом:template<typename Container, typename Index> / / Теперь с decltype ( auto) authAndAccess ( Container&& с, / / универсальнаяIndex i ) ;/ / ссылка1 .3. Знакомство с decltype39В этом шаблоне мы не знаем, с каким типом контейнера работаем, и точно так же незнаем тип используемых им индексных объектов. Использование передачи по значениюдля объектов неизвестного типа обычно сопровождается риском снижения производительности из-за ненужного копирования, проблемами со срезкой объектов (см. раздел 8.1) и насмешками коллег.
Но в случае индексов контейнеров, следуя примеру стандартной библиотеки для значений индексов (например, в operator [ ] для s t d : : st ring,std : : vector и s t d : : de que) это решение представляется разумным, так что мы будемпридерживаться для них передачи по значению.Однако нам нужно обновить реализацию шаблона для приведения его в соответствиес предостережениями из раздела 5.3 о применении std : : forward к универсальным ссылкам:template<typename Container, typename Index> / / Окончательнаяdecltype (auto)/ / версия для/ / С++14authAnd.Acces s ( Container&& с , Index i )authenticateUser ( ) ;return std: : forward<Container> (c) [ i ) ;Этот код должен делать все, что мы хотели, но он требует компилятора С++ 14.
Если у васнет такового, вам следует использовать версию шаблона для С++ 1 1 . Она такая же, каки ее аналог С++ 1 4, за исключением того, что вы должны самостоятельно указать возвращаемый тип:template<typename Container, typename Index> / / Окончательная/ / версия дляautoauthAnd.Acces s ( Container&& с, Index i )/ / C++ l l-> decl type ( std : : forward<Container> (с) [ i ] )(authenticateUser ( ) ;return std : : forward<Conta iner> ( с ) [ i ) ;Вторым беспокоящим моментом является мое замечание в начале этого раздела о том,что decl t уре почти всегда дает тип, который вы ожидаете, т.е. что он редко преподноситсюрпризы.
По правде говоря, вряд ли вы столкнетесь с этими исключениями из правила,если только вы не занимаетесь круглосуточно написанием библиотек.Чтобы полностью понимать поведение dec l t ype, вы должны познакомиться с некоторыми особыми случаями. Большинство из них слишком невразумительны, чтобы бытьразмещенными в этой книге, но один из них приводит к лучшему пониманию decl typeи его применения.Применение de c l t ype к имени дает объявленный тип для этого имени.
Имена представляют собой lvаluе-выражения, но это не влияет на поведение decl t ype. Однакодля lvаluе-выражений, более сложных, чем имена, decl t ype гарантирует, что возвращаемый тип всегда будет lvаluе-ссылкой. Иначе говоря, если lvаluе-выражение, отличноеот имени, имеет тип Т, то decl type сообщает об этом типе как об Т&. Это редко на что-то40Гл ава 1 . Вывод типоввлияет, поскольку тип большинства lvаluе-выражений в обязательном порядке включаетквалификатор lvаluе-ссылки. Например, функции, возвращающие lvalue, всегда возвращают lvаluе-ссылки.Однако у этого поведения есть следствия, о которых необходимо знать. В кодеint х=О;является именем переменной, так что dec l t ype ( х ) представляет собой i n t .
Однако"заворачивание" имени х в скобки - " ( х ) " - дает выражение, более сложное, чем имя.Будучи именем, х представляет собой lvalue, и С++ также определяет выражение ( х ) какlvalue. Следовательно, declt ype ( ( х ) ) представляет собой i n t & . Добавление скобок вокруг имени может изменить тип, возвращаемы й для него decl t ype !В C++ l l это просто любопытный факт, но в сочетании с поддержкой в С++ 14dec l t ype ( auto) это означает, что, казалось бы, тривиальные изменения в способе записи инструкции return могут повлиять на выводимый тип функции:хdecltype (auto)f1 ( ){int х=О;return х;decl type (auto)11 decltype ( x ) представляет собой int ,// так что fl возвращает intf2 ( ){int х=О;return (х);/ / decltype ( ( x) ) представляет собой int & ,/ / так что f2 возвращает int&Обратите внимание, что f2 не только имеет возвращаемый тип, отличный от fl, нои возвращает ссылку на локальную переменную! Этот код ведет вас к неопределенномуповедению, что вряд ли является вашей целью.Основной урок состоит в том, чтобы при использовании dec l t ype ( a u t o ) уделятьдеталям самое пристальное внимание.
Кажущиеся совершенно незначительными детали в выражении, для которого выводится тип, могут существенно повлиять на тип, возвращаемый dec l t ype ( auto ) . Чтобы гарантировать, что выводимый тип - именно тот,который вы ожидаете, используйте методы, описанные в разделе 1 .4.В то же время не забывайте и о более широкой перспективе. Конечно, declt ype (какавтономный, так и в сочетании с auto ) при выводе типов иногда может привести к сюрпризам, но это не нормальная ситуация.
Как правило, dec l t ype возвращает тот тип, который вы ожидаете. Это особенно верно, когда dec l t ype применяется к именам, потомучто в этом случае dec l t ype делает именно то, что скрывается в его названии: сообщаетобъявленный тип (declared type) имени.1 .3. Знакомство с decltype41Следует запомнить•dec l t ype почти всегда дает тип переменной или выражения без каких-либо изменений.•Для lvаluе-выражений типа т, отличных от имени, declt ype всегда дает тип Т &.•C++ l 4 поддерживает конструкцию decltype ( auto ) , которая, подобно auto, выводит тип из его инициализатора, но выполняет вывод типа с использованием правил dec l t ype.1 .4. Как просмотреть выведенные типыВыбор инструментов для просмотра результатов вывода типа зависит от фазы процесса разработки программного обеспечения, на которой вы хотите получить эту информацию.
Мы рассмотрим три возможности: получение информации о выводе типа приредактировании кода, во время компиляции и во время выполнения.Редакторы IDEРедакторы исходных текстов в IDE часто показывают типы программных сущностей(например, переменных, параметров, функций и т.п.), когда вы, например, помещаетеуказатель мыши над ними. Например, пусть у вас есть кодconst int theAnswer = 4 2 ;theAnswer;auto хauto у = &theAnswer;Редактор, скорее всего, покажет, что выведенный тип х представляет собой int, а выведенный тип у - const int * .Чтобы это сработало, ваш код должен быть в более-менее компилируемом состоянии,поскольку такого рода информация поставляется среде разработки компилятором С++(или как минимум его клиентской частью}, работающим в IDE.