std::forward の主な目的は何ですか? また、どのような問題を解決しますか? 質問する

std::forward の主な目的は何ですか? また、どのような問題を解決しますか? 質問する

完全転送では、std::forward名前付き右辺値参照を名前t1なし右辺値参照に変換するために使用されます。これを行う目的は何ですか? & を左辺値のままにしておくと、t2呼び出された関数にどのような影響がありますか?innert1t2

template <typename T1, typename T2>
void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}

ベストアンサー1

転送の問題を理解する必要があります。問題全体を詳細に読むですが、要約します。

基本的に、式 が与えられた場合E(a, b, ... , c)、式 がf(a, b, ... , c)同等になることを望みます。C++03 では、これは不可能です。多くの試みがなされていますが、すべて何らかの点で失敗しています。


最も簡単なのは、lvalue-reference を使用することです。

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
    E(a, b, c);
}

しかし、一時的な値 (rvalues) はf(1, 2, 3);lvalue-reference にバインドできないため、これを処理することはできません。

次の試みは次のようになるかもしれません:

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(a, b, c);
}

これにより上記の問題が解決されます。const X&あらゆるものに結びつく" は左辺値と右辺値の両方を含みますが、これによって新たな問題が発生します。これでは、Econst引数が許可されなくなります。

int i = 1, j = 2, k = 3;
void E(int&, int&, int&); 
f(i, j, k); // oops! E cannot modify these

3 番目の試みは const 参照を受け入れますが、その後はconst_castconst失われます。

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}

これはすべての値を受け入れ、すべての値を渡すことができますが、未定義の動作につながる可能性があります。

const int i = 1, j = 2, k = 3;
E(int&, int&, int&); 
f(i, j, k); // ouch! E can modify a const object!

最終的な解決策は、すべてを正しく処理しますが、保守が不可能になるという代償があります。constと non-const のすべてfの組み合わせで、のオーバーロードを提供します。

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);

N 個の引数には 2 N 個の組み合わせが必要で、これは悪夢です。これを自動的に実行したいと考えています。

(これは、C++11 でコンパイラが実際に実行してくれる処理です。)


C++11 では、これを修正する機会が得られます。1 つの解決策は、既存の型のテンプレート推論ルールを変更することですが、これにより大量のコードが壊れる可能性があります。だから別の方法を見つけなければなりません。

解決策は、代わりに新しく追加されたrvalue-referencesを使用することです。rvalue-reference 型を推論するときに新しいルールを導入し、必要な結果を作成できます。結局のところ、今コードを壊すことはあり得ません。

参照への参照が与えられた場合 (参照はT&と の両方を意味する包括的な用語であることに注意してくださいT&&)、次の規則を使用して結果の型を判断します。

「[与えられた]型 T への参照である型 TR の場合、型「cv TR への lvalue 参照」を作成しようとすると型「T への lvalue 参照」が作成されますが、「cv TR への rvalue 参照」を作成しようとすると型 TR が作成されます。」

または表形式では:

TR   R

T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)

次に、テンプレート引数の推論です。引数が左辺値Aの場合、テンプレート引数にAへの左辺値参照を指定します。それ以外の場合は、通常の推論を行います。これにより、いわゆるユニバーサル参照(用語転送参照現在は公式のものとなっています。

なぜこれが便利なのでしょうか? 組み合わせることで、型の値カテゴリを追跡する機能が維持されるからです。つまり、lvalue の場合は lvalue-reference パラメーターがあり、そうでない場合は rvalue-reference パラメーターがあります。

コードでは:

template <typename T>
void deduce(T&& x); 

int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)

最後に、変数の値カテゴリを「転送」します。関数内に入ると、パラメータは lvalue として何にでも渡される可能性があることに注意してください。

void foo(int&);

template <typename T>
void deduce(T&& x)
{
    foo(x); // fine, foo can refer to x
}

deduce(1); // okay, foo operates on x which has a value of 1

それはダメです。E は私たちが得たのと同じ種類の値カテゴリを取得する必要があります。解決策は次のとおりです。

static_cast<T&&>(x);

これは何をするのでしょうか。関数の中にいてdeduce、lvalue が渡されたとします。これはTが であることを意味しますA&。したがって、静的キャストのターゲット型はA& &&、または単に です。はすでに であるA&ため、何もせず、lvalue 参照が残ります。xA&

右辺値が渡された場合、Tは なのでA、静的キャストのターゲット型は ですA&&。キャストの結果は右辺値式となり、左辺値参照に渡すことはできなくなります。パラメータの値カテゴリは維持されています。

これらを組み合わせると、「完全な転送」が実現します。

template <typename A>
void f(A&& a)
{
    E(static_cast<A&&>(a)); 
}

flvalue を受け取ると、 Elvalue を取得します。rvaluefを受け取ると、Ervalue を取得します。完璧です。


そしてもちろん、醜い部分を取り除きたいのです。は謎めいていて覚えるのが奇妙です。代わりに、同じことを行うstatic_cast<T&&>というユーティリティ関数を作成しましょう。forward

std::forward<A>(a);
// is the same as
static_cast<A&&>(a);

おすすめ記事