回答:
転送の問題を理解する必要があります。問題全体を詳細に読むことができますが、要約します。
基本的に、式が与えられた場合E(a, b, ... , c)
、式はf(a, b, ... , c)
同等である必要があります。C ++ 03では、これは不可能です。多くの試みがありますが、それらはすべていくつかの点で失敗します。
最も簡単なのは、左辺値参照を使用することです。
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
E(a, b, c);
}
しかし、これは一時的な値の処理に失敗します。f(1, 2, 3);
これらは、左辺値参照にバインドできないためです。
次の試みは次のようになります。
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(a, b, c);
}
上記の問題は修正されますが、フロップはフリップします。現在E
は非const引数を持つことができません:
int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these
3回目の試みは、constの参照を受け入れますが、その後const_cast
「S const
離れて:
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つの解決策は、既存の型のテンプレート控除規則を変更しますが、これは潜在的に大量のコードを壊します。したがって、別の方法を見つける必要があります。
解決策は、代わりに新しく追加された右辺値参照を使用することです。右辺値参照型を推論するときに新しい規則を導入して、必要な結果を作成できます。結局のところ、今はおそらくコードを壊すことはできません。
参照への参照が与えられた場合(参照はT&
との両方を意味する包括的用語であることに注意T&&
)、次のルールを使用して、結果のタイプを把握します。
「[指定された]タイプTへの参照であるタイプTR。タイプ「lvalue参照to cv TR」を作成しようとすると、タイプ「lvalue参照to T」が作成され、タイプ「右辺値参照to cv TR”はタイプ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への左辺値参照を指定します。それ以外の場合は、通常どおりに推定します。これにより、いわゆる汎用参照が提供されます(現在、転送参照という用語が公式の参照です)。
なぜこれが便利なのですか?結合されているため、型の値カテゴリを追跡する機能が維持されます。それが左辺値の場合は左辺値参照パラメーターがあり、それ以外の場合は右辺値参照パラメーターがあります。
コードで:
template <typename T>
void deduce(T&& x);
int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)
最後に、変数の値カテゴリを「転送」します。関数内に入ると、パラメーターを左辺値として何にでも渡すことができます。
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
関数の内部にいて、左辺値が渡されたとしましょう。この手段がT
ありA&
、かつ静的なキャストのターゲット・タイプがあるのでA& &&
、あるいは単にA&
。x
はすでになのでA&
、何もせず、左辺値参照が残ります。
右辺値T
is が渡されるA
と、静的キャストのターゲットタイプはになりますA&&
。キャストの結果、右辺値の式が生成され、左辺値の参照に渡すことができなくなります。パラメータの値カテゴリを維持しました。
これらをまとめると、「完全な転送」が得られます。
template <typename A>
void f(A&& a)
{
E(static_cast<A&&>(a));
}
がf
左辺値を受け取ると、左辺値をE
取得します。がf
右辺値を受け取ると、右辺値をE
取得します。完璧です。
そしてもちろん、私たちは醜いものを取り除きたいです。static_cast<T&&>
不可解で覚えにくい。代わりにforward
、同じことを行うユーティリティ関数を作成してみましょう。
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
const int i
が受け入れられることに注意してください。A
はに推定されconst int
ます。失敗は右辺値リテラルに関するものです。また、への呼び出しではdeduced(1)
、xはint&&
であることに注意してくださいint
(完全転送では、x
値渡しパラメーターの場合のようにコピーが作成されることはありません)。ただT
ですint
。x
フォワーダーで左辺値に評価される理由は、名前付き右辺値参照が左辺値式になるためです。
forward
またはmove
ここでの使用に違いはありますか?それとも単なる意味の違いですか?
std::move
明示的なテンプレート引数なしで呼び出す必要があり、常に右辺値になりますstd::forward
が、どちらかになる場合があります。使用std::move
あなたはもはやあなたを知っていない値を必要とし、別の場所に移動したい、使用がstd::forward
行うことあなたの関数テンプレートに渡された値に応じました。
std :: forwardを実装する概念的なコードが議論に追加できると思います。これは、スコットマイヤーズの講演からのスライドです。効果的なC ++ 11/14サンプラー
move
コード内の関数はstd::move
です。その話の前半で、そのための(動作する)実装があります。libstdc ++のstd :: forwardの実際の実装をファイルmove.hで見つけましたが、まったく有益ではありません。
ユーザーの観点から見ると、その意味はstd::forward
、右辺値への条件付きキャストです。パラメータで左辺値または右辺値のいずれかを期待し、右辺値として渡された場合にのみ右辺値として別の関数に渡したい関数を書いている場合に役立ちます。パラメータをstd :: forwardでラップしなかった場合、常に通常の参照として渡されます。
#include <iostream>
#include <string>
#include <utility>
void overloaded_function(std::string& param) {
std::cout << "std::string& version" << std::endl;
}
void overloaded_function(std::string&& param) {
std::cout << "std::string&& version" << std::endl;
}
template<typename T>
void pass_through(T&& param) {
overloaded_function(std::forward<T>(param));
}
int main() {
std::string pes;
pass_through(pes);
pass_through(std::move(pes));
}
案の定、それは印刷します
std::string& version
std::string&& version
コードは、前述の講演の例に基づいています。スライド10、最初の15:00頃。
完全転送では、std :: forwardを使用して、名前付き右辺値参照t1およびt2を名前なし右辺値参照に変換します。それを行う目的は何ですか?t1とt2を左辺値のままにすると、呼び出された関数の内部にどのような影響がありますか?
template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) { inner(std::forward<T1>(t1), std::forward<T2>(t2)); }
式で名前付き右辺値参照を使用する場合、それは実際には左辺値です(名前でオブジェクトを参照するため)。次の例を考えてみます。
void inner(int &, int &); // #1
void inner(int &&, int &&); // #2
さて、outer
このように呼ぶと
outer(17,29);
17と29は整数リテラルであり、そのため右辺値なので、17と29が#2に転送されるようにします。しかし、以来、t1
及びt2
式では、inner(t1,t2);
あなたの代わりに#2の#1を呼び出すことだろう、左辺値です。そのため、を使用して、参照を名前のない参照に戻す必要がありstd::forward
ます。したがって、t1
in outer
は常に左辺値式ですが、forward<T1>(t1)
によっては右辺値式になる場合もありますT1
。後者がT1
左辺値参照の場合は、左辺値式のみです。そしてT1
、outerへの最初の引数が左辺値式である場合にのみ、左辺値参照であると推定されます。
t1とt2を左辺値のままにすると、呼び出された関数の内部にどのような影響がありますか?
インスタンス化後、T1
の型char
でありT2
、クラスの場合、t1
コピーt2
ごとおよびconst
参照ごとに渡したい場合。まあ、inner()
それは非const
参照ごとにそれらをとらない限り、つまり、その場合、あなたもそうしたいのです。
outer()
右辺値参照なしでこれを実装する関数のセットを作成して、inner()
の型から引数を渡す正しい方法を推測してください。私はあなたがそれらの2 ^ 2の何か、引数を推測するためのかなり多額のテンプレートメタのものが必要であり、すべての場合にこれを正しくするために多くの時間が必要だと思います。
そして、誰かがinner()
ポインタごとに引数をとるを伴います。今では3 ^ 2になると思います。(または4 ^ 2。地獄、const
ポインターが違いを生むかどうかを考えようとするのに悩むことはできません。)
そして、5つのパラメーターに対してこれを実行するとします。または7。
これで、いくつかの明るい心が「完全な転送」を思いついた理由がわかります。これにより、コンパイラーはすべてこれを行います。
明確にされていない点は、適切にstatic_cast<T&&>
処理const T&
することです。
プログラム:
#include <iostream>
using namespace std;
void g(const int&)
{
cout << "const int&\n";
}
void g(int&)
{
cout << "int&\n";
}
void g(int&&)
{
cout << "int&&\n";
}
template <typename T>
void f(T&& a)
{
g(static_cast<T&&>(a));
}
int main()
{
cout << "f(1)\n";
f(1);
int a = 2;
cout << "f(a)\n";
f(a);
const int b = 3;
cout << "f(const b)\n";
f(b);
cout << "f(a * b)\n";
f(a * b);
}
生成する:
f(1)
int&&
f(a)
int&
f(const b)
const int&
f(a * b)
int&&
'f'はテンプレート関数でなければならないことに注意してください。'void f(int && a)'として定義されているだけの場合、これは機能しません。
forwardは、forwarding / universal参照を使用する外部メソッドと組み合わせて使用する必要があることを強調する価値があるかもしれません。次のステートメントとしてforwardを単独で使用することは許可されていますが、混乱を招くこと以外は何の役にも立ちません。標準委員会はそのような柔軟性を無効にしたいと思うかもしれません。そうでなければ、なぜ代わりにstatic_castを使用しないのですか?
std::forward<int>(1);
std::forward<std::string>("Hello");
私の意見では、移動と前進は、r値参照タイプが導入された後の自然な結果である設計パターンです。不正な使用が禁止されていない限り、メソッドが正しく使用されていることを前提としてメソッドを指定しないでください。
f
機能、およびない表現になりますか?