(Это продолжение "Есть ли реальные варианты использования переменных '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
не может быть частью сообщения журнала.