auto &&は何を教えてくれますか?


168

あなたがのようなコードを読んだ場合

auto&& var = foo();

ここで、fooタイプの値によって返す任意の関数ですT。次にvar、へのタイプの右辺値参照の左辺値ですT。しかし、これは何を意味するのvarでしょうか?それは私たちのリソースを盗むことを許可されているということですvarか?を使用auto&&してコードの読者に、unique_ptr<>あなたが独占的な所有権を持っていることを伝えるためにa を返すときのようなことを伝えるために使用すべき合理的な状況はありますか?そして、たとえばクラス型のT&&場合Tはどうでしょうか?

auto&&テンプレートプログラミング以外のユースケースがあるかどうかを理解したいだけです。この記事の例で説明されているものと同様に、スコット・マイヤーズのユニバーサルリファレンス


1
私も同じことを考えていました。型の推論がどのように機能するかは理解していますが、使用する私のコードは何と言っていますauto&&か?なぜ範囲ベースのforループauto&&が例として使用するために拡張されるのかを検討してきましたが、それについては説明していません。おそらく、だれでも答えれば説明できます。
ジョセフマンスフィールド

1
これは合法ですか?つまり、Tのインスタンスはfoo戻り直後に破棄され、右辺値の参照を格納すると、UBからNEのように聞こえます。

1
@alegunaこれは完全に合法です。ローカル変数への参照やポインタではなく、値を返したいのですが。関数fooは、たとえば次のようになりますint foo(){return 1;}
MWid

9
一時ファイルへの@aleguna参照は、C ++ 98と同様に、存続期間の延長を実行します。
ecatmur

5
@aleguna存続時間拡張は、ローカル一時でのみ機能し、参照を返す関数では機能しません。stackoverflow.com/a/2784304/567292を
ecatmur

回答:


232

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::forwardauto&&参照を使用して、それがもともと左辺値または右辺値のいずれかであったという事実を維持する場合、コードは次のように言います。私はそれを最も効率的に使用できるようにしました-これはそれを無効にするかもしれません。のように:

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&&のいずれかにコードでautoauto&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
}

2
T vec = get_vector<T>();関数fooの内部で単純に言うことはできませんか?それとも私はそれを不合理なレベルに単純化していますか:)
アスタリスク2014年

@Asteriskいいえbcoz T vecはstd :: vector <int&>の場合のみlvalueに割り当てることができ、Tがstd :: vector <int>の場合、非効率的な値による呼び出しを使用します
Kapil

1
auto&でも同じ結果が得られます。MSVC 2015を使用しています。GCCでエラーが発生します。
Sergey Podobry 2016

ここではMSVC 2015を使用しています。auto&はauto &&と同じ結果を示します。
Kehe CAI 2018

なぜint iですか。自動&& j = i; 許可されていますが、int i; int && j = i; ではありません ?
SeventhSon84

14

まず、普遍的な参照のテンプレート引数の控除がどのように機能するかについての段階的な説明の副読として、この私の回答を読むことをお勧めします。

それは私たちのリソースを盗むことを許可されているということですvarか?

必ずしも。どのような場合にはfoo()、突然のすべては、参照を返された、またはあなたが電話を変更したがの使用を更新するのを忘れvar?または、汎用コードを使用していて、戻り値の型がfoo()パラメーターに応じて変化する場合はどうなりますか?

考えるauto&&とまったく同じになるようにT&&template<class T> void f(T&& v);、それは(ほとんどだから、)まさにそれ。関数内のユニバーサル参照を渡したり、何らかの方法で使用したりする必要がある場合、どうすればよいですか?を使用std::forward<T>(v)して、元の値カテゴリを元に戻します。関数に渡される前に左辺値だった場合は、渡された後も左辺値のままstd::forwardです。それが右辺値だった場合、それは再び右辺値になります(名前付き右辺値参照は左辺値であることを忘れないでください)。

では、var一般的な方法でどのように正しく使用しますか?を使用しstd::forward<decltype(var)>(var)ます。これはstd::forward<T>(v)、上記の関数テンプレートのとまったく同じように機能します。がの場合varT&&右辺値が返され、それがのT&場合は左辺値が返されます。

だから、トピックに関するバック:何auto&& v = f();std::forward<decltype(v)>(v)コードベースでは教えて?彼らはそれvが最も効率的な方法で取得され、渡されることを教えてくれます。ただし、そのような変数を転送した後は、変数が移動された可能性があるため、リセットせずにそれをさらに使用するのは正しくないことに注意してください。

個人auto&&的には、変更可能な変数が必要なときに汎用コードで使用します。移動操作は根性を盗む可能性があるため、右辺値を完全転送することは修正されています。怠惰になりたい(つまり、タイプ名を知っていてもタイプスペルを入力しない)だけで、変更する必要がない場合(たとえば、範囲の要素を印刷する場合など)は、に固執しauto const&ます。


autoこれまでにその異なっているauto v = {1,2,3};でしょうしながら、控除の障害になります。vstd::initializer_listf({1,2,3})


あなたの答えの最初の部分:つまり、foo()がvalue-typeを返す場合Tvar(この式)は左辺値になり、(この式の)タイプはT(つまりT&&)への右辺値参照になります。
MWid

@MWid:わかりました、最初の部分を削除しました。
Xeo

3

Tmoveコンストラクターがある型を考えて、

T t( foo() );

その移動コンストラクタを使用します。

次に、中間参照を使用して、からの戻りをキャプチャしfooます。

auto const &ref = foo();

これは移動コンストラクタの使用を除外するため、戻り値は移動する代わりにコピーする必要があります(std::moveここで使用しても、実際にconst refを移動することはできません)。

T t(std::move(ref));   // invokes T::T(T const&)

ただし、

auto &&rvref = foo();
// ...
T t(std::move(rvref)); // invokes T::T(T &&)

移動コンストラクターは引き続き使用できます。


そして、あなたの他の質問に対処するには:

... auto &&を使用してコードの読者に何かを伝える必要がある状況はありますか...

Xeoが言うように、最初のことは、本質的に私は、Xがどんなタイプであっても、できるだけ効率的にXを渡すことです。したがって、auto&&内部で使用するコードを見ると、必要に応じて内部で移動セマンティクスを使用することが通知されます。

... unique_ptr <>を返し、独占的な所有権があることを伝えるときと同じように...

関数テンプレートがtypeの引数をとる場合、T&&それは渡したオブジェクトを移動する可能性があることを示しています。戻るunique_ptrと、呼び出し側に所有権が明示的に与えられます。受け入れると、呼び出し元から所有権T&&削除される場合があります(ムーブctorが存在する場合など)。


2
2番目の例が有効かどうかはわかりません。moveコンストラクターを呼び出すために、完全な転送を行う必要はありませんか?

3
これは間違っています。refrvrefはどちらも左辺値なので、どちらの場合も、コピーコンストラクターが呼び出されます。移動コンストラクタが必要な場合は、を記述する必要がありますT t(std::move(rvref))
MWid

最初の例でconst refを意味しましたauto const &か?
PiotrNycz

@aleguna-あなたとMWidは正しいです、ありがとう。答えを直しました。
役に立たない

1
@役に立たないあなたは正しいです。しかし、これは私の質問に答えません。いつ使用しauto&&、使用するコードの読者に何を伝えますauto&&か?
MWid

-3

auto &&構文はC ++ 11の2つの新機能を使用しています。

  1. このauto部分により、コンパイラーはコンテキスト(この場合は戻り値)に基づいて型を推定できます。これには、参照資格がありません(必要かどうかTT &またはT &&推定型に対して指定できますT)。

  2. これ&&は新しい移動セマンティクスです。移動セマンティクスをサポートするタイプT(T && other)は、新しいタイプのコンテンツを最適に移動するコンストラクターを実装します。これにより、オブジェクトはディープコピーを実行する代わりに内部表現を交換できます。

これにより、次のようなものが可能になります。

std::vector<std::string> foo();

そう:

auto var = foo();

返されたベクトルのコピーを実行します(高価)が、

auto &&var = foo();

ベクトルの内部表現(からのベクトルとからfooの空のベクトルvar)を交換するため、より高速になります。

これは、新しいforループ構文で使用されます。

for (auto &item : foo())
    std::cout << item << std::endl;

forループを保持している場合auto &&からの戻り値fooitemの各値への参照ですfoo


これは誤りです。auto&&何も移動せず、参照するだけです。それが左辺値か右辺値のどちらであるかは、それを初期化するために使用される式に依存します。
Joseph Mansfield

std::vectorstd::stringはmoveconstructibleなので、どちらの場合もmoveコンストラクターが呼び出されます。これはのタイプとは関係ありませんvar
MWid

1
@MWid:実際には、コピー/移動コンストラクターへの呼び出しもRVOで完全に省略できます。
Matthieu M.12年

@MatthieuM。あなたが正しいです。しかし、上の例では、すべてがmoveconstructibleであるため、copy cnstructorが呼び出されることはないと思います。
MWid

1
@MWid:私の要点は、移動コンストラクターさえも省略できるということでした。エリシオンの切り札が動きます(安いです)。
Matthieu M.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.