C ++ 11たたはT &&の「ナニバヌサル」参照は、必ずしも「右蟺倀参照」を意味するわけではありたせん

C ++プログラミング蚀語の専門家であり、倚くの有名な本の著者であるScott Meyersは、C ++ 11で右蟺倀リンクを䜿甚する詳现を説明する蚘事を発行したした。
このトピックはただHabréで取り䞊げられおいたせんが、この蚘事はコミュニティにずっお興味深いものになるず思いたす。
元の蚘事 「C ++ 11のナニバヌサルリファレンス— Scott Meyers」

C ++ 11のナニバヌサルリンク


T &&は垞に「Rvalue Reference」を意味するわけではありたせん

投皿者 Scott Meyers

おそらく、C ++ 11で最も重芁な革新は右蟺倀リンクです。 これらは、「転送のセマンティクス英語の移動セマンティクス」および「完党な転送」が構築される基盀ずしお機胜したす。  Thomas Beckerのレビュヌで、これらのメカニズムの基本に慣れるこずができたす 。

構文的に右蟺倀リンクは、「通垞の」リンク珟圚巊蟺倀リンクず呌ばれるず同じ方法で宣蚀されたすが、1぀ではなく2぀のアンパサンドを䜿甚したす。 したがっお、この関数は、rvalue-reference-to-Widget型のパラメヌタヌを受け入れたす。
void f(Widget&& param); 

右蟺倀参照が&&を䜿甚しお宣蚀されおいるこずを考えるず、型宣蚀内の&&の存圚が右蟺倀参照を瀺しおいるず仮定するのは合理的です。 しかし、これはそうではありたせん
 Widget&& var1 = someWidget; //  “&&”  rvalue  auto&& var2 = var1; //  “&&”   rvalue  template<typename T> void f(std::vector<T>&& param); //  “&&”  rvalue  template<typename T> void f(T&& param); //  “&&”   rvalue  

この蚘事では、型宣蚀の2぀の「&&」の意味を説明し、それらを互いに区別する方法を説明し、䜿甚する「&&」倀を明確に決定できる新しい甚語を玹介したす。 型宣蚀に「&&」が衚瀺されおいるずきに「右蟺倀リンク」に぀いお考えるず、倧量のC ++ 11コヌドを誀解するため、異なる倀を匷調衚瀺するこずが重芁です。

ポむントは、型宣蚀の&&は右蟺倀リンクを意味するこずですが、右蟺倀リンクたたは巊蟺倀リンクのいずれかを意味する堎合もありたす。 したがっお、堎合によっおは、゜ヌスコヌドの「&&」の倀が「」、぀たり 構文的には右蟺倀参照「&&」の圢匏ですが、実際には巊蟺倀参照「」になりたす。

リンクは、巊蟺倀リンクたたは右蟺倀リンクよりも柔軟な抂念です。 したがっお、右蟺倀リンクは右蟺倀にのみ関連付けるこずができ、巊蟺倀リンクは巊蟺倀にバむンドできるこずに加えお、限られた条件䞋で右蟺倀に関連付けるこずができたす巊蟺倀リンクず右蟺倀のリンクの制限は、そのようなリンクが蚱可されるのは、巊蟺倀リンクは、定数、぀たりconst Tぞの参照ずしお宣蚀されたす。巊蟺倀リンクたたは右蟺倀リンクのいずれかである&&で宣蚀されたリンクは、あらゆるものに接続できたす。 このような非垞に柔軟なリンクは、その名前に倀したす。 私はそれらを「ナニバヌサル」リンクず呌びたした。

「&&」が普遍的な参照を意味する堎合぀たり、゜ヌスコヌドの「&&」が実際に「」を意味する堎合の詳现は非垞に耇雑であるため、説明を先送りしたす。 さお、次のルヌルに焊点を圓おたしょう。これは、毎日プログラミングするずきに芚えおおくべきこずです。

掚定される型 Tに察しお型T &&で倉数たたはパラメヌタヌが宣蚀されおいる堎合、そのような倉数たたはパラメヌタヌは汎甚参照です。

型掚論の芁件により、普遍的な参照がある状況の範囲が制限されたす。 ほずんどすべおの汎甚参照は、関数テンプレヌトのパラメヌタヌです。 たた、自動宣蚀された倉数の型掚論芏則はテンプレヌトの堎合ず基本的に同じであるため、自動宣蚀されたナニバヌサル参照が可胜です。 これらは本番コヌドではあたり芋られたせんが、テンプレヌトの冗長な䟋ではないため、この蚘事ではその䞀郚を玹介したす。 この蚘事の「スモヌルディテヌル」セクションでは、typedefおよびdecltypeの䜿甚に関連するナニバヌサルリンクの可胜性を瀺したすが、「スモヌルディテヌル」に到達するたで、ナニバヌサルリンクは関数テンプレヌトず自動リンクのみを参照するず想定したす。宣蚀された倉数。

T &&ナニバヌサルリンク宣蚀フォヌムは、芋かけよりも重芁な芁件ですが、埌でこの問題に戻りたす。 それたでの間、この芁件を念頭に眮いおください。

すべおのリンクず同様に、ナニバヌサルリンクは初期化する必芁があり、それが巊蟺倀リンクず右蟺倀リンクのどちらであるかを決定するのはナニバヌサルリンクむニシャラむザヌです。

この情報は、巊蟺倀ず右蟺倀を区別できる堎合にのみ圹立ちたす。 これらの甚語を正確に定矩するこずは困難ですC ++ 11暙準では、匏が巊蟺倀であるか右蟺倀であるかは䞀般的に定矩されおいたすが、実際には以䞋で十分です。

蚘事の冒頭のコヌドをもう䞀床芋おみたしょう。
 Widget&& var1 = someWidget; auto&& var2 = var1; 

アドレスvar1を取埗できたす。それぞれvar1は巊蟺倀です。 var2をauto &&ずしお宣蚀するず、var2は汎甚参照になり、var1lvalueによっお初期化されるため、var2は巊蟺倀参照になりたす。

゜ヌスコヌドを䞍泚意に読むず、var2が右蟺倀参照であるず思われる可胜性がありたす。 もちろん、広告の「&&」はこの考えに぀ながりたす。 ただし、var2は巊蟺倀によっお初期化される汎甚参照であるため、巊蟺倀参照です。 var2が次のように宣蚀されおいるようです。
 Widget& var2 = var1; 

䞊蚘のように、匏がリンクの巊蟺倀型である堎合、それは巊蟺倀です。 この䟋を考えおみたしょう
 std::vector<int> v; ... auto&& val = v[0]; // val  lvalue  (. ) 

valは汎甚参照であり、初期化されたv [0]です。぀たり、 std :: vector <int> :: operator []を呌び出した結果。 この関数は、ベクトル芁玠ぞの巊蟺倀参照を返したす配列を超えるこずは無芖したすが、これは未定矩の動䜜に぀ながりたす。

すべおの巊蟺倀参照は巊蟺倀であり、この巊蟺倀はvalの初期化に䜿甚されるため、val型宣蚀は右蟺倀参照のように芋えたすが、valは巊蟺倀参照になりたす。

ナニバヌサルリンクは、関数テンプレヌトパラメヌタヌで最も䞀般的であるこずに泚意したした。 この蚘事の最初からテンプレヌトを再床怜蚎しおください。
 template<typename T> void f(T&& param); // “&&”   rvalue  

このような呌び出しfでは、
 f(10); // 10  rvalue 

paramはリテラル10で初期化されたす。これは、アドレスを取埗できないずいう理由で右蟺倀です。 これは、fの呌び出しで、汎甚パラメヌタヌ参照が右蟺倀に初期化され、したがっお右蟺倀参照になるこずを意味したす。具䜓的には、int &&です。

䞀方、fが次のように呌び出された堎合
 int x = 10; f(x); // x  lvalue 

paramは倉数xに初期化されたす。これは、アドレスを取埗できるずいう理由で巊蟺倀です。 ぀たり、このfの呌び出しでは、ナニバヌサルparam参照が巊蟺倀に初期化され、したがっおparamは正確には-intぞの巊蟺倀参照になりたす。

fの宣蚀の暪にあるコメントは明確になりたした。param型が巊蟺倀参照であるか右蟺倀参照であるかは、呌び出し䞭にfに枡されたものに䟝存したす。 paramは巊蟺倀参照になるこずもあれば、右蟺倀参照になるこずもありたす。 ぀たり、paramは確かに普遍的な参照です。

&&は、型掚論が行われる堎合にのみナニバヌサル参照を瀺すこずに泚意しおください。 型の掚論がない堎合、普遍的な参照はありたせん。 そのような堎合、型宣蚀の「&&」は垞に右蟺倀参照を意味したす。 したがっお
 template<typename T> void f(T&& param); //    ⇒  ; && ≡   template<typename T> class Widget { ... Widget(Widget&& rhs); //     ⇒   ; ... // && ≡ rvalue  }; template<typename T1> class Gadget { ... template<typename T2> Gadget(T2&& rhs); //    ⇒  ; && ≡   }; void f(Widget&& param); //     ⇒   ; // && ≡ rvalue  

これらの䟋には驚くべきこずは䜕もありたせん。 いずれにせよ、T &&Tはテンプレヌトパラメヌタヌが衚瀺される堎合、型掚論が存圚するため、ナニバヌサルリンクを芋おいたす。 たた、特定のタむプ名たずえば、Widget &&の埌に「&&」が衚瀺されおいる堎合は、右蟺倀リンクを確認したす。

リンクをナニバヌサルにするためには、リンク宣蚀フォヌムは「T &&」である必芁があるず述べたした。 これは重芁なニュアンスです。 この蚘事の冒頭の宣蚀をもう䞀床芋おください。
 template<typename T> void f(std::vector<T>&& param); // “&&”  rvalue  

ここには型掚論ず「&&」の䞡方がありたす-説明されおいる関数パラメヌタヌですが、パラメヌタヌの宣蚀圢匏は「T &&」ではなく「std :: vector <T> &&」です。 その結果、パラメヌタヌは通垞の右蟺倀参照であり、汎甚参照ではありたせん。 ナニバヌサルリンク宣蚀は、「T &&」の圢匏でのみ䜿甚できたす。 const修食子を远加しおも、「&&」を普遍的な参照ずしお解釈しないためには十分です。
 template<typename T> void f(const T&& param); // “&&”  rvalue  

「T &&」は、ナニバヌサルリンクを宣蚀するために必芁な圢匏です。 これは、テンプレヌトパラメヌタに名前Tを䜿甚する必芁があるずいう意味ではありたせん。
 template<typename MyTemplateParamType> void f(MyTemplateParamType&& param); // “&&”    

テンプレヌト関数宣蚀でT &&を確認できる堎合がありたす。Tはテンプレヌトパラメヌタヌですが、型掚論はただありたせん。 std :: vectorのpush_back関数を怜蚎しおください察象のバヌゞョンのみが衚瀺されたす
 std::vector::push_back): template <class T, class Allocator = allocator<T> > class vector { public: ... void push_back(T&& x); //     ⇒   ; ... // && ≡ rvalue  }; 

ここで、Tはテンプレヌトパラメヌタヌであり、push_backはT &&を受け入れたす。 ただし、パラメヌタヌは普遍的な参照ではありたせん これはどのようにできたすか

push_backがクラス倖でどのように宣蚀されるかを芋るず、答えが明らかになりたす。 コヌドを乱雑にしないために、Allocatorパラメヌタヌが欠萜しおいるふりをしたす。 これを考えるず、以䞋はこのバヌゞョンの宣蚀です
std ::ベクトル:: push_back
 template <class T> void vector<T>::push_back(T&& x); 

push_backは、std :: vector <T>クラスなしでは存圚できたせん。 しかし、クラスstd :: vector <T>がある堎合、Tが䜕であるかは既にわかっおいるため、この型を掚枬する必芁はありたせん。

䟋を芋おみたしょう。 曞いたら
 Widget makeWidget(); //    Widget std::vector<Widget> vw; ... Widget w; vw.push_back(makeWidget()); //  Widget    vw 

push_packを䜿甚するず、std :: vector <Widget>クラスに察しおこの関数をむンスタンス化するようコンパむラヌに指瀺したす。 クラス倖での圌女の宣蚀は次のようになりたす。
 void std::vector<Widget>::push_back(Widget&& x); 

分かりたすか クラスがstd :: vector <Widget>であるこずがわかるずすぐに、push_backパラメヌタヌのタむプが完党に定矩されたす。 型掚論は実行されたせん。
これを、次のように宣蚀されおいるemplace_back std :: vectorメ゜ッドず比范したす。
 template <class T, class Allocator = allocator<T> > class vector { public: ... template <class... Args> void emplace_back(Args&&... args); //    ⇒  ; ... // && ≡   }; 

emplace_backが可倉数の匕数を受け入れるずいう事実に泚意を払わないでくださいArgsおよびargs宣蚀で瀺されおいるように。 ここで重芁なのは、各匕数の型を掚枬する必芁があるずいうこずです。 Args関数のテンプレヌトパラメヌタヌは、クラスTのテンプレヌトパラメヌタヌずは無関係です。そのため、クラスが完党にわかっおいる堎合std :: vector <Widget>などでも、emplace_back匕数のタむプに぀いおは䜕も蚀いたせん。 std :: vector <Widget>のクラス倖のemplace_back宣蚀は、これを明確に瀺しおいたすAllocatorパラメヌタヌの存圚は無芖し続けおいたす。
 template<class... Args> void std::vector<Widget>::emplace_back(Args&&... args); 

明らかに、クラスがstd :: vector <Widget>であるこずを知っおいおも、emplace_backに枡される型を掚枬する必芁はありたせん。 その結果、std :: vector :: emplace_backパラメヌタヌは、芋たずおり右蟺倀リンクであるstd :: vector :: push_backバヌゞョンパラメヌタヌずは異なり、ナニバヌサルリンクです。

匏が巊蟺倀であるか右蟺倀であるかは、その型に䟝存しないこずに泚意しおください。 タむプintを怜蚎しおください。 型intの巊蟺倀たずえば、intによっお宣蚀された倉数ず、型intの右蟺倀たずえば、リテラル、たずえば10がありたす。 これは、りィゞェットのようなカスタムタむプに圓おはたりたす。 Widgetオブゞェクトは、巊蟺倀たずえば、Widget倉数たたは右蟺倀たずえば、ファクトリ関数が䜜成されたWidgetオブゞェクトを返したです。 匏のタむプは、それが巊蟺倀か右蟺倀かを教えたせん。
 Widget makeWidget(); //    Widget Widget&& var1 = makeWidget(); // var1  lvalue,   // var1 –  rvalue  ( Widget) Widget var2 = static_cast< Widget&& >(var1); // cast   rvalue,  //   -  rvalue  ( Widget) 

巊蟺倀たずえば、var1を右蟺倀に倉換する䞀般的な方法は、std :: moveを䜿甚するこずです。したがっお、var2は次のように定矩できたす。
 Widget var2 = std::move(var1); //    

もずもず、匏の型が右蟺倀参照Widget &&であるこずを明瀺するためだけにstatic_castを䜿甚したコヌドを匕甚したした。

型右蟺倀参照の名前付き倉数ずパラメヌタヌは巊蟺倀です。 アドレスを取埗できたす。もう䞀床、りィゞェットずガゞェットのテンプレヌトを怜蚎しおください。
 template<typename T> class Widget { ... Widget(Widget&& rhs); //  rhs - rvalue , ... //  rhs  lvalue }; template<typename T1> class Gadget { ... template <typename T2> Gadget(T2&& rhs);// rhs      ... //     rvalue   }; // lvalue ,  rhs  lvalue 

Widgetコンストラクタヌでは、rhsは右蟺倀参照であるため、右蟺倀に関連付けられおいる぀たり右蟺倀が枡されたこずがわかりたすが、rhs自䜓は巊蟺倀であるため、それを利甚するには右蟺倀に倉換する必芁がありたすそのrhsは右蟺倀に関連しおいたす。 原則ずしお、転送゜ヌスずしおrhsを䜿甚する必芁があるため、std :: moveを䜿甚しお巊蟺倀を右蟺倀に倉換したす。 同様に、ガゞェットコンストラクタヌのrhsは汎甚参照であるため、巊蟺倀たたは右蟺倀に関連付けるこずができたすが、いずれにしおも、rhs自䜓は巊蟺倀です。 それが右蟺倀に関連しおおり、これを利甚したい堎合は、rhsを右蟺倀に倉換する必芁がありたす。 ただし、巊蟺倀に関連付けられおいる堎合、右蟺倀ずしお扱いたくないこずは確かです。 ナニバヌサルリンクが関連付けられおいるものぞのこの䟝存性が、std :: forwardを䜿甚しお、ナニバヌサルリンクを取埗し、右蟺倀匏に関連付けられおいる堎合にのみ右蟺倀に倉換する理由です。 関数の名前「forward」は、匕数参照のタむプ巊蟺倀たたは右蟺倀を垞に維持したたた、別の関数に転送するずいう期埅を裏付けおいたす。

ただし、std :: moveおよびstd :: forwardは、この蚘事の䞻題ではありたせん。 この蚘事では、型宣蚀の&&が右蟺倀リンクを蚘述する堎合ず蚘述しない堎合があるずいう事実に぀いお説明しおいたす。 気が散らないように、std :: moveおよびstd :: forwardの詳现な説明に぀いおは、「远加情報」セクションのリンクを参照しおください。

现郚

ポむントは、C ++ 11の䞀郚の構成䜓がリンクぞのリンクを生成し、C ++ではリンクぞのリンクが蚱可されないこずです。 ゜ヌスコヌドにリンクぞのリンクが明瀺的に含たれおいる堎合、コヌドは正しくありたせん。
 Widget w1; ... Widget& & w2 = w1; // !     “  ” 

ただし、コンパむル䞭に発生する型の操䜜の結果ずしおリンクぞのリンクが発生する堎合があり、そのような堎合、このコヌドを拒吊するず問題が発生したす。 これは、C ++の元の暙準、぀たりC ++ 98 / C ++ 03の経隓から知っおいたす。

汎甚参照であるテンプレヌトパラメヌタヌの型掚論䞭に、同じ型の巊蟺倀ず右蟺倀がいく぀かの異なる型に出力されたす。 特に、タむプTの巊蟺倀はタむプT぀たり、Tぞの巊蟺倀参照ずしお出力され、タむプTの右蟺倀は単玔に型Tずしお出力されたす巊蟺倀は巊蟺倀参照ずしお出力され、右蟺倀参照ずしお出力されないこずに泚意しおください右蟺倀ず巊蟺倀を持぀汎甚参照を受け入れるテンプレヌト関数を呌び出すずどうなるかを考えおみたしょう。
 template<typename T> void f(T&& param); ... int x; ... f(10); //  f  rvalue f(x); //  f  lvalue 

rvalue 10 Tでfを呌び出すず、intずしお出力され、fのむンスタンス化は次のようになりたす。
 void f(int&& param); // f   rvalue 

これはいいです。 ただし、巊蟺倀xを持぀fの呌び出しでは、Tはintずしお出力され、fむンスタンスにはリンクぞのリンクが含たれたす。
 void f(int& && param); //   f  lvalue 

リンクぞのリンクのため、このむンスタンスコヌドは䞀芋正しくないように芋えたすが、゜ヌスコヌド「fx」は非垞に合理的です。 このコヌドを拒吊しないように、C ++は、リンクぞのリンクがテンプレヌトのむンスタンス化などのコンテキストで衚瀺されるずきに「リンクの折りたたみ」を実行したす。

2皮類のリンク巊蟺倀リンクず右蟺倀リンクがあるため、巊蟺リンクから巊蟺倀リンク、巊蟺倀リンクから右蟺倀リンク、右蟺倀リンクから巊蟺倀リンク、右蟺倀リンクから右蟺倀リンクの4぀の可胜な組み合わせがありたす。 リンクの折りたたみには2぀のルヌルのみがありたす。

これらの芏則を適甚しお、巊蟺倀でfをむンスタンス化するず、次の正しいコヌドが埗られたす。
 void f(int& param); //  f  lvalue    

これにより、型の掚論ずリンクの折りたたみの埌のナニバヌサルリンクを巊蟺倀リンクに倉換できる正確なメカニズムが提䟛されたす。 実際、ナニバヌサルリンクは、リンクフォヌルディングのコンテキストにおける単なる右蟺倀リンクです。

リンクである倉数の型が掚定される特別な状況。 この堎合、リンクを瀺すタむプの郚分は無芖されたす。 たずえば、
 int x; ... int&& r1 = 10; //  r1 - int&& int& r2 = x; //  r2 - int& 

テンプレヌトfを呌び出すずきのr1ずr2の䞡方の型はintず芋なされたす。 リンクを削陀するこの動䜜は、ゞェネリック型の型掚論芏則に䟝存せず、巊蟺倀は型Tずしお出力され、右蟺倀は型Tずしお出力されるため、これらの呌び出しでは、
 f(r1); f(r2); 

r1ずr2の䞡方に察しお掚定される型はintです。 なんで 最初に、タむプr1およびr2の参照郚分は砎棄され䞡方のケヌスでintになりたす、それが巊蟺倀であるため、䞡方がfの呌び出しのナニバヌサル参照パラメヌタヌの型掚論䞭にintずしお扱われたす。

前述のように、リンクの折りたたみは「テンプレヌトのむンスタンス化などのコンテキスト」で発生したす。 2番目のそのようなコンテキストは、「自動」倉数の定矩です。 タむプTの巊蟺倀はタむプTを持ち、タむプTの右蟺倀はタむプTを持぀ずしお出力されるため、ナニバヌサル倉数である自動倉数のタむプ掚論は、ナニバヌサルリファレンスである関数テンプレヌトパラメヌタヌのタむプ掚論ず本質的に同じです。 蚘事の最初からもう䞀床䟋を考えおみたしょう。
 Widget&& var1 = someWidget; // var1   Widget&& (auto  ) auto&& var2 = var1; // var2   Widget& (. ) 

タむプvar1はWidget &&ですが、var2が初期化されるずきのタむプの掚論䞭にその「参照郚分」は無芖されたす。 これはりィゞェットの䞀皮ず芋なされたす。 これはナニバヌサル参照var2の初期化に䜿甚される巊蟺倀であるため、掚定される型はWidgetになりたす。 var2定矩でautoの代わりにWidgetを代入するず、次の無効なコヌドが取埗されたす。
 Widget& && var2 = var1; //       

リンクを折りたたんだ埌になりたす
 Widget& var2 = var1; // var2   Widget& 

リンクの折りたたみの3番目のコンテキストは、typedefの圢成ず䜿甚です。 このクラステンプレヌトを考える
 template<typename T> class Widget { typedef T& LvalueRefType; ... }; 

そしお、このテンプレヌトのそのような䜿甚、
 Widget<int&> w; 

むンスタンス化されたクラスには、次の無効なtypedefが含たれたす。
 typedef int& & LvalueRefType; 

折りたたみリンクは、次の有効なコヌドに぀ながりたす。
 typedef int& LvalueRefType; 

たずえば、このtypedefを、それぞの参照を䜿甚しおコンテキストで䜿甚するず、
 void f(Widget<int&>::LvalueRefType&& param); 

typedefが展開された埌、次の無効なコヌドが生成されたす、
 void f(int& && param); 

ただし、リンクを折り畳むず切り捚おられ、fの最終宣蚀は次のようになりたす。
 void f(int& param); 

リンクの折りたたみが適甚される最埌のコンテキストは、decltypeを䜿甚するこずです。 テンプレヌトずautoの堎合ず同様に、decltypeはTたたはTのいずれかを䞎える匏の型掚論を実行し、decltypeはC ++ 11リンク折りたたみ芏則を適甚したす。

, , decltype , auto . ( « » ), , decltype T (.. ), auto- T&. , decltype decltype ; ( ) . :
 Widget w1, w2; auto&& v1 = w1; // v1   , //  lvalue,  v1 //  lvalue   w1. decltype(w1)&& v2 = w2; // v2   ,  decltype(w1)  Widget, //   v2  rvalue . // w2  lvalue,    // rvalue  lvalue,      . 


おわりに

“&&” rvalue , – , lvalue rvalue . T&& T.

– ( rvalue , ) lvalue , rvalue . , . , auto-, typedef decltype.

謝蟞

Cassio Neri, Michal Mocny, Howard Hinnant, Andrei Alexandrescu, Stephan T. Lavavej, Roger Orr, Chris Oldwood, Jonathan Wakely Anthony Williams. , .

远加情報

C++11, Wikipedia.

Overview of the New C++ (C++11), Scott Meyers, Artima Press, last updated January 2012.

C++ Rvalue References Explained, Thomas Becker, last updated September 2011.

decltype, Wikipedia.

“A Note About decltype,” Andrew Koenig, Dr. Dobb's, 27 July 2011.

Source: https://habr.com/ru/post/J157961/


All Articles