(Это продолжение "Есть ли реальные варианты использования переменных 'decltype (auto)'?")
Рассмотрим следующий сценарий - я хочу передать функцию f другой функции invoke_log_return, которая будет:
Вызвать
f;Напечатайте что-нибудь на стандартный вывод;
Вернуть результат
f, избегая ненужных копий/перемещений и допуская копирование.
Обратите внимание, что если выбрасывает f, ничего не должно быть напечатано на стандартный вывод. Это то, что я до сих пор:
template <typename F>
decltype(auto) invoke_log_return(F&& f)
{
decltype(auto) result{std::forward<F>(f)()};
std::printf(" ...logging here...\n");
if constexpr(std::is_reference_v<decltype(result)>)
{
return decltype(result)(result);
}
else
{
return result;
}
}
Давайте рассмотрим различные возможности:
Когда
fвозвращает значение:resultбудет объектом;invoke_log_return(f)будет prvalue (право на получение копии).
Когда
fвозвращает lvalue или xvalue:resultбудет ссылкой;invoke_log_return(f)будет lvalue или xvalue.
Вы можете увидеть тестовое приложение здесь, на godbolt.org. Как видите, g++ выполняет NRVO для случая prvalue, а clang++ - нет.
Вопросы:
Является ли это кратчайшим способом "идеального" возврата переменной
decltype(auto)из функции? Есть ли более простой способ добиться того, чего я хочу?Можно ли извлечь шаблон
if constexpr { ... } else { ... }в отдельную функцию? Единственный способ извлечь его - это макрос.Есть ли веская причина, по которой
clang++не выполняет NRVO для описанного выше случая предварительной оценки? Следует ли сообщать о нем как о потенциальном улучшении, или оптимизацияg++NRVO здесь недопустима?
Вот альтернатива с использованием помощника on_scope_success (как предложил Барри Ревзин):
template <typename F>
struct on_scope_success : F
{
int _uncaught{std::uncaught_exceptions()};
on_scope_success(F&& f) : F{std::forward<F>(f)} { }
~on_scope_success()
{
if(_uncaught == std::uncaught_exceptions()) {
(*this)();
}
}
};
template <typename F>
decltype(auto) invoke_log_return_scope(F&& f)
{
on_scope_success _{[]{ std::printf(" ...logging here...\n"); }};
return std::forward<F>(f)();
}
Хотя invoke_log_return_scope намного короче, для этого требуется другая ментальная модель поведения функции и реализация новой абстракции. Удивительно, но и g++, и clang++ выполняют RVO/copy-elision с этим решением.
Как отметил Бен Фойгт, одним из основных недостатков этого подхода является то, что возвращаемое значение f не может быть частью сообщения журнала.