Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - страница 17

стр.

. В частности, в распространенном случае, когда >c[i] возвращает >Т&, >authAndAccess также возвращает >Т&, и в том редком случае, когда >c[i] возвращает объект, >authAndAccess также возвращает объект.

Использование >decltype(auto) не ограничивается возвращаемыми типами функций. Это также может быть удобно для объявления переменных, когда вы хотите применять правила вывода типа >decltype к инициализирующему выражению:

>Widget w;

>const Widget& cw = w;


>auto myWidget1 = cw;           // Вывод типа auto:


>                               // тип myWidget1 - Widget

>decltype(auto) myWidget2 = cw; // Вывод типа decltype:

>                               // тип myWidget2 - const Widget&

Я знаю, что вас беспокоят два момента. Один из них — упомянутое выше, но пока не описанное уточнение >authAndAccess. Давайте, наконец-то, разберемся в этом вопросе. Еще раз посмотрим на версию >authAndAccess в С++14:

>template

>decltype(auto) authAndAccess(Container& с, Index i);

Контейнер передается как lvalue-ссылка на неконстантный объект, поскольку возвращаемая ссылка на элемент контейнера позволяет клиенту модифицировать этот контейнер. Но это означает, что этой функции невозможно передавать контейнеры, являющиеся rvalue. rvalue невозможно связать с lvalue-ссылками (если только они не являются lvalue-ссылками на константные объекты, что в данном случае очевидным образом не выполняется).

Надо сказать, что передача контейнера, являющегося rvalue, в >authAndAccess является крайним случаем. Такой rvalue-контейнер, будучи временным объектом, обычно уничтожается в конце инструкции, содержащей вызов >authAndAccess, а это означает, что ссылка на элемент в таком контейнере (то, что должна вернуть функция >authAndAccess) окажется “висячей” в конце создавшей ее инструкции. Тем не менее передача временного объекта функции >authAndAccess может иметь смысл. Например, клиент может просто хотеть сделать копию элемента во временном контейнере:

>std::deque makeStringDeque(); // Фабричная функция


>// Делаем копию пятого элемента deque, возвращаемого

>// функцией makeStringDeque

>auto s = authAndAccess(makeStringDeque(), 5);

Поддержка такого использования означает, что мы должны пересмотреть объявление функции >authAndAccess, которая должна принимать как lvalue, так и rvalue. Можно использовать перегрузку (одна функция объявлена с параметром, представляющим собой lvalue-ссылку, а вторая — с параметром, представляющим собой rvalue-ссылку), но тогда нам придется поддерживать две функции. Избежать этого можно, если у нас будет функция >authAndAccess, использующая ссылочный параметр, который может быть связан как с lvalue, так и с rvalue, и в разделе 5.2 поясняется, что это именно то, что делают универсальные ссылки. Таким образом, >authAndAccess может быть объявлена следующим образом:

>template // Теперь с -

>decltype(auto) authAndAccess(Container&& с,  // универсальная

>                             Index i);       // ссылка

В этом шаблоне мы не знаем, с каким типом контейнера работаем, и точно так же не знаем тип используемых им индексных объектов. Использование передачи по значению для объектов неизвестного типа обычно сопровождается риском снижения производительности из-за ненужного копирования, проблемами со срезкой объектов (см. раздел 8.1) и насмешками коллег. Но в случае индексов контейнеров, следуя примеру стандартной библиотеки для значений индексов (например, в >operator[] для >std::string, >std::vector и std::deque) это решение представляется разумным, так что мы будем придерживаться для них передачи по значению.

Однако нам нужно обновить реализацию шаблона для приведения его в соответствие с предостережениями из раздела 5.3 о применении >std::forward к универсальным ссылкам:

>template // Окончательная

>decltype(auto)                               // версия для

>authAndAccess(Container&& с, Index i)        // С++14

>{

> authenticateUser();

> return std::forward(c)[i];

>}

Этот код должен делать все, что мы хотели, но он требует компилятора С++14. Если у вас нет такового, вам следует использовать версию шаблона для С++11. Она такая же, как и ее аналог С++14, за исключением того, что вы должны самостоятельно указать возвращаемый тип: