decltype(auto)の用途は何ですか?


151

c ++ 14では、decltype(auto)イディオムが導入されています。

通常、その使用はauto宣言でdecltype指定された式のルールを使用できるようにすることです。

イディオムの "良い"使用法の例を検索すると、(Scott Meyersによる)次のようなもの、つまり関数の戻り値の型の推定についてしか考えることができません。

template<typename ContainerType, typename IndexType>                // C++14
decltype(auto) grab(ContainerType&& container, IndexType&& index)
{
  authenticateUser();
  return std::forward<ContainerType>(container)[std::forward<IndexType>(index)];
}

この新しい言語機能が役立つ他の例はありますか?


2
この記事は、基本的にそれを使用しているとき、あなたのコンパイラに最適化の少ない選択肢を与えているので、このイディオムを回避しようとすることをお勧めstackoverflow.com/a/20092875/2485710
user2485710

以前decltype(auto)はと似たものを使用していましたがtemplate<class U, V> decltype(auto) first(std::pair<U, V>& p) { return p.first; }、その後、return (p.first);意外に機能するものを使用する必要があることに気付きました(ただし、IIRCはこれも意図されています)。
dyp 2014年

@ user2485710は特に最適化に関するものかどうかわかりませんdecltype(auto)が、何かが宣言されたオブジェクトにコピー/移動される可能性があると、事故につながる可能性が高くなります。
underscore_d

回答:


170

一般的なコードでの戻り型の転送

非ジェネリックコードの場合、最初の例のように、参照を戻り値の型として取得するように手動で選択できます。

auto const& Example(int const& i) 
{ 
    return i; 
}

しかし、汎用コードでは、参照を処理するのか値を処理するのかを知らなくても、戻り値の型完全に転送できるようにしたいとします。decltype(auto)あなたにその能力を与えます:

template<class Fun, class... Args>
decltype(auto) Example(Fun fun, Args&&... args) 
{ 
    return fun(std::forward<Args>(args)...); 
}

再帰的なテンプレートでの戻り型の推定の遅延

ではこのQ&Aテンプレートの戻り値の型は次のように指定されたときに、数日前に、テンプレートのインスタンス中に無限再帰が発生しましたdecltype(iter(Int<i-1>{}))代わりにdecltype(auto)

template<int i> 
struct Int {};

constexpr auto iter(Int<0>) -> Int<0>;

template<int i>
constexpr auto iter(Int<i>) -> decltype(auto) 
{ return iter(Int<i-1>{}); }

int main() { decltype(iter(Int<10>{})) a; }

decltype(auto)ここでは、テンプレートのインスタンス化のほこりが落ち着いた後、戻り型の控除遅らせるために使用されます。

その他の用途

decltype(auto)他のコンテキストで使用することもできます。たとえば、標準N3936ドラフトも述べています。

7.1.6.4自動指定子[dcl.spec.auto]

1 autoand decltype(auto)型指定子は、初期化子からの推論によって、またはトレーリングリターン型を使用した明示的な指定によって、後で置き換えられるプレースホルダー型を指定します。auto型特異的ERは、ラムダは、一般的なラムダであることを意味するために使用されます。

2プレースホルダータイプ、decl-specifier-seq、type-specifier-seq、conversion-function-id、またはtrailing-return-typeの関数宣言子とともに、そのような宣言子が有効な任意のコンテキストで使用できます。関数宣言子にトレーリングリターンタイプ(8.3.5)が含まれている場合は、関数の宣言された戻りタイプを指定します。関数の宣言された戻り値の型にプレースホルダー型が含まれている場合、関数の戻り値の型は、関数の本体にあるreturnステートメント(ある場合)から推定されます。

ドラフトには、変数の初期化の次の例も含まれています。

int i;
int&& f();
auto x3a = i;                  // decltype(x3a) is int
decltype(auto) x3d = i;        // decltype(x3d) is int
auto x4a = (i);                // decltype(x4a) is int
decltype(auto) x4d = (i);      // decltype(x4d) is int&
auto x5a = f();                // decltype(x5a) is int
decltype(auto) x5d = f();      // decltype(x5d) is int&&
auto x6a = { 1, 2 };           // decltype(x6a) is std::initializer_list<int>
decltype(auto) x6d = { 1, 2 }; // error, { 1, 2 } is not an expression
auto *x7a = &i;                // decltype(x7a) is int*
decltype(auto)*x7d = &i;       // error, declared type is not plain decltype(auto)

17
differen動作です(i)VS iC ++ 14の新しい事は?
Danvil 2014年

14
@Danvil decltype(expr)decltype((expr))C ++ 11ではすでに異なり、これはその動作を一般化します。
TemplateRex 2014年

13
私はこれを学んだばかりで、ひどいデザイン決定のように感じます...括弧の構文の意味に時間厳守のニュアンスを追加します。
Kahler

この嫌悪感を常に促す例は、1行のファイルから文字列への構文です(このリンクでも言及されています)。それのすべての部分が逆のようです。あいまいさをまったく期待せず、余計な括弧を強制的にサンプルから削除するかもしれません。SFINAEによる削除のプロセスによってあいまいさが解決されることを期待しますが、宣言以外の候補者は事前に削除されます(SF AEです)。そして欲求不満では、任意の括弧が曖昧さを解決すると考えてコンパイルするとすぐに先に進むかもしれませんが、彼らそれを導入します。私が想像しているCS101教授にとって最も厄介なことです。
John P

@TemplateRex:参照されている質問での戻り値の型解決の遅延について:私が見る限り、特定のシナリオではauto、結果はいずれにしても値によって返されるため、単純に同様に機能します...または、私はミスしましたか何か?
アコンカグア

36

ここからの引用:

  • decltype(auto)これは主に、転送する関数や同様のラッパーの戻り値の型推測するのに役立ちます。この場合、呼び出している式を型に正確に「追跡」させます。

  • たとえば、次の関数があるとします。


   string  lookup1();
   string& lookup2();

  • C ++ 11では、次のラッパー関数を記述して、戻り値の型の参照性を維持することを覚えておくことができます。

   string  look_up_a_string_1() { return lookup1(); }
   string& look_up_a_string_2() { return lookup2(); }

  • C ++ 14では、それを自動化できます。

   decltype(auto) look_up_a_string_1() { return lookup1(); }
   decltype(auto) look_up_a_string_2() { return lookup2(); }

  • ただし、decltype(auto)それ以上に広く使用される機能を意図したものではありません。

  • 特に、これはローカル変数の宣言に使用できますが、ローカル変数の参照性は初期化式に依存してはならないため、これはおそらくアンチパターンにすぎません。

  • また、returnステートメントの記述方法も重要です。

  • たとえば、以下の2つの関数は戻り値の型が異なります。


   decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; }
   decltype(auto) look_up_a_string_2() { auto str = lookup2(); return(str); }

  • 最初は戻りstring、2番目はstring&ローカル変数への参照ですstr

提案からあなたはより多くの意図された用途を見ることができます。


3
単にauto返品に使用しないのはなぜですか?
BЈовић

@BЈовићは、一般化された戻り値の型の控除(つまり、auto戻り値)でも機能しますが、OPは特にの使用を求めましたdecltype(auto)
101010 2014

3
質問はまだ関連しています。戻り値の型はauto lookup_a_string() { ... } 何ですか?それは常に非参照型ですか?したがって、auto lookup_a_string() ->decltype(auto) { ... }(場合によっては)参照が返されるように強制する必要がありますか?
Aaron McDaid 2015

@AaronMcDaid Deductible autoは、値渡しテンプレートで定義されているため、参照にすることはできません。してくださいウェイトはautoもちろんの参照など、何もすることができます。
curiousguy

4
言及する価値のある追加の例は、の要素を返すことですstd::vector。あなたが持っていると言うtemplate<typename T> struct S { auto & operator[](std::size_t i) { return v[i]; } std::vector<T> v; }。次にS<bool>::operator[]ための専門のダングリング参照を返しますstd::vector<bool>。戻り値の型を変更してdecltype(auto)この問題を回避します。
Xoph
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.