Как меняется тип возвращаемого значения шаблонной функции при использовании decltype(auto), если имя локальной переменной заменить выражением в скобках?
При decltype(auto) тип возвращаемого значения выводится по правилам decltype. Для неквалифицированного имени переменной decltype возвращает её объявленный тип, а для выражения в скобках применяет правила категорий значения: lvalue даёт ссылку, xvalue — ссылку на rvalue, prvalue — тип значения. Поэтому возврат локальной переменной без скобок может вернуть значение, а возврат той же переменной в скобках — ссылку на уже уничтоженный объект.
decltype появился в C++11 как средство получить тип выражения без выполнения этого выражения. В C++14 запись decltype(auto) позволила использовать эти правила непосредственно для вывода возвращаемого типа функции.
Подход решал проблему универсальных обёрток: им требовалось сохранить не только сам тип результата, но и ссылочность, const-квалификацию и категорию значения. Однако точное сохранение типа сделало синтаксические различия, включая скобки, семантически значимыми.
Рассмотрим шаблонную функцию, которая принимает значение, выполняет локальную обработку и возвращает его. Если вернуть локальную переменную как значение, вызывающий код получает независимый объект. Если написать выражение в скобках, decltype(auto) может вывести ссылочный тип, указывающий на локальную переменную.
После выхода из функции локальная переменная уничтожается. Использование возвращённой ссылки становится неопределённым поведением, поэтому ошибка может проявиться далеко от места её возникновения.
Для неквалифицированного имени, обозначающего объект, действует специальное правило: decltype(имя) равен объявленному типу объекта. Для параметра T value это T, поэтому return value; при decltype(auto) выводит T.
Но decltype((value)) рассматривает (value) как lvalue-выражение. В таком случае результатом становится T&. Именно поэтому добавление скобок способно изменить возвращаемый тип с значения на ссылку.
Минимальный пример:
В by_value возвращается копия или перемещённый результат. В dangling возвращается ссылка на параметр, существующий только до завершения функции; сам вызов не следует использовать.
Если возвращается не локальная переменная, а выражение объекта вызывающего кода, ссылочность может быть намеренной. Например, функция-доступор может вернуть decltype(auto), чтобы сохранить ссылку на поле. Но такой интерфейс требует контроля времени жизни объекта и осторожности с временными значениями.
В отличие от decltype(auto), обычный auto при выводе возвращаемого типа обычно отбрасывает ссылки и верхнеуровневые квалификаторы, то есть чаще возвращает значение. Это безопаснее для локальных переменных, но может нарушить семантику прокси-функции или аксессора.
Все операторы return функции с выведенным типом должны приводить к совместимому результату. Кроме того, тело функции должно быть доступно в месте, где компилятору требуется вывести возвращаемый тип.
В библиотечном коде создаётся шаблонная функция, нормализующая входное значение перед возвратом. Рассматривались три варианта: auto, decltype(auto) с возвратом имени локальной переменной и decltype(auto) со скобочным выражением.
auto надёжно возвращает значение, но не подходит, если функция должна быть универсальным доступором. Неквалифицированный возврат локального имени с decltype(auto) также возвращает значение и сохраняет эту безопасность. Скобочный вариант сохраняет lvalue-ссылку, но для локального объекта создаёт висячую ссылку.
Для функции-нормализатора выбирается auto или decltype(auto) с возвратом неквалифицированного имени — в зависимости от требуемого интерфейса. Скобки применяются только при осознанном возврате ссылки на объект, время жизни которого гарантированно превышает время использования результата.
Вопрос: Почему decltype(x) и decltype((x)) могут давать разные типы?
Ответ: Неквалифицированное имя x обрабатывается особым правилом и получает объявленный тип объекта. Скобки превращают его в обычное lvalue-выражение, для которого decltype добавляет &. Это отличие относится не только к возвращаемым типам, но и к любому применению decltype.
Вопрос: Что произойдёт, если шаблонная функция с decltype(auto) имеет два оператора return с разными ссылочными категориями?
Ответ: Вывод возвращаемого типа не сможет согласовать несовместимые результаты. Например, один путь может выводить T, а другой — T&; такая функция будет некорректна при инстанцировании. decltype(auto) не выбирает общий безопасный тип автоматически.
Вопрос: Почему decltype(auto) не гарантирует безопасное время жизни возвращаемого объекта?
Ответ: Он лишь точно выводит тип по правилам decltype и не управляет временем жизни. Если выведен ссылочный тип, безопасность зависит от объекта, на который ссылка указывает. Для локальных переменных ссылка после возврата недействительна, а для объекта вызывающего кода она может быть корректной при соблюдении условий времени жизни.