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

Скотт Мейерс

template<typename Container, typename Index> // С++14; работает,

decltype(auto)                               // но все еще

authAndAccess(Containers с, Index i)         // требует

{                                            // уточнения

 authenticateUser();

 return c[i];

}

Теперь authAndAccess действительно возвращает то же, что и c[i]. В частности, в распространенном случае, когда 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<typename Container, typename Index>

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

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

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

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

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

// функцией makeStringDeque

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

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