auto&& var = <initializer>
あなたが言っていることを使用して:私はそれが左辺値または右辺値式であるかどうかに関係なく任意の初期化子を受け入れ、その定数を保持します。これは通常、転送に使用されます(通常はでT&&
)。これが機能する理由は、「ユニバーサル参照」auto&&
またはT&&
が何にでもバインドするためです。
あなたは、なぜだけ使用しないだけでなく、言うかもしれないconst auto&
ことをしますので、また何に特異的に結合しますか?const
リファレンスの使用に関する問題は、それがconst
!後でそれを非const参照にバインドしたり、マークされていないメンバー関数を呼び出したりすることはできませんconst
。
例として、を取得しstd::vector
、イテレータを最初の要素に移動し、そのイテレータが指す値を何らかの方法で変更するとします。
auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;
このコードは、初期化子の式に関係なく正常にコンパイルされます。auto&&
次の方法で失敗する代替案:
auto => will copy the vector, but we wanted a reference
auto& => will only bind to modifiable lvalues
const auto& => will bind to anything but make it const, giving us const_iterator
const auto&& => will bind only to rvalues
したがって、これはauto&&
完全に機能します!このauto&&
ような使用例は、範囲ベースのfor
ループです。詳細については、他の質問を参照してください。
次にstd::forward
、auto&&
参照を使用して、それがもともと左辺値または右辺値のいずれかであったという事実を維持する場合、コードは次のように言います。私はそれを最も効率的に使用できるようにしました-これはそれを無効にするかもしれません。のように:
auto&& var = some_expression_that_may_be_rvalue_or_lvalue;
// var was initialized with either an lvalue or rvalue, but var itself
// is an lvalue because named rvalues are lvalues
use_it_elsewhere(std::forward<decltype(var)>(var));
これによりuse_it_elsewhere
、元の初期化子が変更可能な右辺値であった場合に、パフォーマンスのために(コピーを回避して)根性を取り除くことができます。
これは、リソースを盗むことができるかどうか、いつリソースを盗むことができるかについてはどういう意味var
ですか?まあ、それはauto&&
何かにバインドするので、var
私たちは自分の内臓を自分で引き抜こうとすることはできません-それはまさしく左辺値であるか、あるいはconstでさえあるかもしれません。しかしstd::forward
、その内部を完全に破壊する可能性のある他の関数にそれをすることができます。これを実行したらすぐvar
に、無効な状態であると見なす必要があります。
さてauto&& var = foo();
、質問にあるように、これをfoo の場合に適用してみましょうT
。この場合、のタイプがvar
として推定されることが確実にわかりT&&
ます。右辺値であることは確かなので、std::forward
そのリソースを盗むのにの許可は必要ありません。この特定のケースでは、値によって返されることを知っているfoo
ので、読者はそれを次のように読み取る必要があります。foo
補足として、some_expression_that_may_be_rvalue_or_lvalue
「コードが変わるかもしれない」状況以外に、のような式が現れる可能性がある場合について言及する価値があると思います。だからここに人為的な例があります:
std::vector<int> global_vec{1, 2, 3, 4};
template <typename T>
T get_vector()
{
return global_vec;
}
template <typename T>
void foo()
{
auto&& vec = get_vector<T>();
auto i = std::begin(vec);
(*i)++;
std::cout << vec[0] << std::endl;
}
ここでget_vector<T>()
は、ジェネリック型に応じて左辺値または右辺値のいずれかになる可能性がある素敵な式ですT
。基本的にはget_vector
、のテンプレートパラメータを通じての戻り値の型を変更しますfoo
。
を呼び出すとfoo<std::vector<int>>
、値でget_vector
戻りglobal_vec
、右辺値式が返されます。または、を呼び出すとfoo<std::vector<int>&>
、参照によってget_vector
返さglobal_vec
れ、左辺値の式になります。
私たちが行う場合:
foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;
予想どおり、次の出力が得られます。
2
1
2
2
あなたが変更した場合auto&&
のいずれかにコードでauto
、auto&
、const auto&
、またはconst auto&&
その後、私たちは私たちが望む結果を得ることはありません。
auto&&
参照が左辺値または右辺値のどちらの式で初期化されているかに基づいてプログラムロジックを変更する別の方法は、型特性を使用することです。
if (std::is_lvalue_reference<decltype(var)>::value) {
// var was initialised with an lvalue expression
} else if (std::is_rvalue_reference<decltype(var)>::value) {
// var was initialised with an rvalue expression
}
auto&&
か?なぜ範囲ベースのforループauto&&
が例として使用するために拡張されるのかを検討してきましたが、それについては説明していません。おそらく、だれでも答えれば説明できます。