decltype(auto)は、decltypeの型推論メカニズムとauto構文の便利さを組み合わせています。autoは、テンプレート引数の推論ルールを適用し、配列をポインタに変換し、最上位のcv修飾子や参照を削除しますが、decltype(auto)は初期化式の正確な型を保持します。具体的には、式が括弧なしの変数名であれば、decltypeは宣言された型を返し、括弧付きの左辺値式であれば、左辺値参照を返します。これにより、関数はdecltype式を明示的に指定することなく、戻り値を完璧に転送でき、参照のコラプシングの複雑さを心配する必要がありません。
私たちは、キャッシュされたレコードへの参照または新しく構築されたデフォルト値のいずれかを条件に返すデータベースアクセサの汎用ラッパーを実装する必要がありました。重要な要件は、正確な戻り値のセマンティクスを保持すること—参照は大きなオブジェクトのコピーを避けるために参照のままで、値は適宜移動またはコピーされる必要がありました。
候補の1つは、decltypeとstd::declvalを用いた明示的なトレーリング戻り値型を使用し、decltype(std::declval<Accessor>()(key))を指定しました。利点:型変換を明示的に文書化し、C++11で機能します。欠点:構文が冗長で、引数をstd::declvalに完璧に転送する必要があり、複数のオーバーロードや条件ロジックを扱うと保守が難しくなります。
別のアプローチでは、戻り値型として単純なautoを使用し、コンパイラが適切な型を推論すると仮定しました。利点:簡潔で読みやすいです。欠点:autoは劣化ルールを適用し、Record&をRecordに変換し、const修飾子を削除するため、不必要な深いコピーを引き起こし、呼び出し側が読み取り専用の参照を期待するならconst正確性を違反します。
私たちは、戻り値型としてdecltype(auto)を選択しました。これにより、戻される式に対してdecltypeの型保持ルールが適用されました。この選択により、ボイラープレートが排除され、左辺値参照、const修飾子、右辺値参照が呼び出し側に正しく伝搬します。その結果、コードの重複や暗黙的な変換なしに値と参照の戻り値を扱うゼロオーバーヘッドの汎用ファサードが実現され、高頻度キャッシュ検索のレイテンシが低下しました。
decltype((var))が左辺値参照型を返すのに対し、decltype(var)が宣言された型を返すのはなぜで、これがdecltype(auto)の戻り値文にどのように影響しますか?
decltypeは、2つの異なるルールに基づいて動作します:括弧のないid表現(例えばvar)の場合、そのエンティティのために宣言された型を生成し、括弧付きの表現(例えば(var))を含む他の任意の表現の場合、その表現の型を返します。この場合、式が左辺値である場合、左辺値参照型になります。**decltype(auto)**を使用する際に、(var)を返すと、ローカル変数への参照が作成され、関数終了時にダングリング参照が発生します。したがって、**decltype(auto)**を使用する際には戻り値文で不必要な括弧を避ける必要があります。追加の括弧がid表現から左辺値表現に表現のカテゴリを変更します。
decltype(auto)はx値(期限切れの値)とprvalueにどのように相互作用しますか?
**decltype(auto)**は、decltypeのセマンティクスに従って値のカテゴリを正確に保持します。関数がx値(例えば、std::move(obj))を返す場合、**decltype(auto)**は型を右辺値参照(T&&)として推論しますが、autoは型をTとして推論します。この区別は、戻される一時オブジェクトの移動セマンティクスを保持し、コピーを強制したり、呼び出し時に明示的なstd::moveの注釈を必要としない完璧に転送されたファクトリ関数を実装する際に重要です。
decltype(auto)がブレース初期化リストで使用されると何が起こり、これがauto推論とはどのように異なりますか?
{1, 2, 3}のようなブレース初期化リストで初期化された場合、autoは**std::initializer_list<int>**を推論しますが、decltype(auto)はブレース初期化リスト自体を型として推論しようとして、これはdecltypeの非推論コンテキストとなり、無効なコードになります。これにより、decltype(auto)はブレース初期化リストを直接返すことができなくなり、初期化リストの一時オブジェクトを推論できるautoとは異なります。この微妙な違いは、decltypeが表現型を正確に保持し、表現が変数や関数呼び出しでない非推論コンテキストを含むからです。