(これはからのフォローアップです) " ` decltype(auto) `変数の現実的な使用例はありますか? ")
次のシナリオを考えてみましょう-関数f
を別の関数_invoke_log_return
_に渡して、次のようにします。
f
;を呼び出す
stdoutに出力します
f
の結果を返し、不要なコピー/移動を回避し、コピーの省略を許可します。
f
がスローした場合、stdoutには何も出力されないことに注意してください。これは私がこれまでに持っているものです:
_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
がprvalueを返す場合:
result
はオブジェクトになります。
invoke_log_return(f)
はprvalueになります(コピー省略の対象)。
f
がlvalueまたはxvalueを返す場合:
result
は参照になります。
invoke_log_return(f)
はlvalueまたはxvalueになります。
テストアプリケーションは こちらのgodbolt.org で確認できます。ご覧のとおり、_g++
_はprvalueの場合にNRVOを実行しますが、_clang++
_は実行しません。
質問:
これは、関数からdecltype(auto)
変数を「完全に」返す最も短い方法ですか?達成する簡単な方法はありますか?私が欲しいものは?
_if constexpr { ... } else { ... }
_パターンを別の関数に抽出できますか?それを抽出する唯一の方法はマクロのようです。
上記のprvalueケースで_clang++
_がNRVOを実行しない理由はありますか?潜在的な機能強化として報告する必要がありますか、それとも_g++
_のNRVO最適化はここでは無効ですか?
_on_scope_success
_ヘルパーを使用する別の方法を次に示します(Barry Revzinが提案)。
_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 /コピー削除を実行します。
Ben Voigt で言及されているように、このアプローチの主な欠点の1つは、f
の戻り値をログメッセージの一部にすることができないことです。
_std::forward
_の修正バージョンを使用できます(ADLの問題を防ぐために、名前の転送は避けられます)。
_template <typename T>
T my_forward(std::remove_reference_t<T>& arg)
{
return std::forward<T>(arg);
}
_
この関数テンプレートは、decltype(auto)
変数を転送するために使用されます。次のように使用できます。
_template <typename F>
decltype(auto) invoke_log_return(F&& f)
{
decltype(auto) result{std::forward<F>(f)()};
std::printf(" ...logging here...\n");
return my_forward<decltype(result)>(result);
}
_
このように、std::forward<F>(f)()
が返す場合
prvalueの場合、result
は非参照であり、_invoke_log_return
_は非参照型を返します。
左辺値の場合、result
は左辺値参照であり、_invoke_log_return
_は左辺値参照型を返します。
xvalueの場合、result
は右辺値参照であり、_invoke_log_return
_は右辺値参照型を返します。
(基本的には https://stackoverflow.com/a/57440814 からコピーされます)
これが最も簡単で明確な方法です。
template <typename F>
auto invoke_log_return(F&& f)
{
auto result = f();
std::printf(" ...logging here... %s\n", result.foo());
return result;
}
GCCは正しい(不必要なコピーや移動はありません)期待される結果を取得します。
s()
in main
prvalue
s()
...logging here... Foo!
lvalue
s(const s&)
...logging here... Foo!
xvalue
s(s&&)
...logging here... Foo!
したがって、コードが明確な場合は、同じ機能を使用しますが、競合他社が実行するのと同じくらいに実行するように最適化されていません。これは、アプリケーション層の実装ではなく、ツールで解決する方が理にかなった種類の問題です。