2019年3月17日日曜日

4: 「ムーブセマンティクス」、「xvalue」、「prvalue」、「glvalue」?実体とかけはなれた名称

<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>

それらは全て実体とかけはなれた名称です(「lvalue」および「rvalue」と同様に)、そして、正しい理解は、それが実体とかけはなれた名称であると理解されてはじめて始まります。

話題


About: C++

この記事の目次


開始コンテキスト



ターゲットコンテキスト



  • 読者は、C++におけるいわゆる「ムーブセマンティクス」、「xvalue」、「prvalue」、「glvalue」が何であるかの1つのリーズナブルな説明を得る。

オリエンテーション


Hypothesizer 7
何?...いわゆる「lvalue」および「rvalue」とは何かをようやく理解したのに、「xvalue」、「prvalue」、「glvalue」もあるって?...そう、ある。

それらを'本体'で理解するが、ここでは、2つのことを言っておく。

第一に、それらの用語は実態とかけ離れた名前だ: 「xvalue」も「prvalue」も「glvalue」も、では全然なく、全て、表現だ(実際、それらを私は「xexpression」、「prexpression」、「glexpression」と呼ぶ)。

第二に、広く流布しているほとんどの説明は本質的に間違っており、その理由は、主に、それらはそうした実態とかけ離れた名前影響されており、表現のカテゴリーであるものを、のカテゴリーであるかのように説明しているから(他にも私が異議を持つ点があるが)。

'xexpression'、'prexpression'、'glexpression'を理解するには、まず、いわゆる「ムーブセマンティクス」を理解しなければならないので、そうしよう。

「ムーブセマンティクス」および表現の分類は、本当にいい考えなのだろうか?...実のところ私はそう思わない、あるセクションで論じるとおり。しかし、それらを私が好むか否かに関わらず、それらは存在し、それらを理解するしか私には選択肢がない。


本体


1: 「ムーブセマンティクス」とは何か?


Hypothesizer 7
まず注意すべきは、「ムーブセマンティクス」と呼ばれているが、何かのメモリまたはCPUレジスター内位置が変化するという意味において、本当は、何も動かない。確かに、あるオブジェクトのなんらかのメンバーデータのオーナーシップが別のオブジェクトへ移るということはある、一部のケースでは(全てのケースでではない)。

実のところ、任意の「ムーブ」アクションで起きることは、厳密にはただのシャローコピーであって、オリジナル側オブジェクトの再初期化を伴うこともある(必須ではない)。

1つの例を見てみよう。

@C++ ソースコード
#include <string>
			
			class Owned {
				public:
					::std::string i_name;
					Owned (::std::string a_name);
			};
			
			class Owner {
				public:
					::std::string i_name;
					Owned * i_ownedPointer;
					Owner (::std::string a_name, ::std::string a_ownedName);
					~Owner ();
			};

			Owned::Owned (::std::string a_name) : i_name (a_name) {
			}
			
			Owner::Owner (::std::string a_name, ::std::string a_ownedName) : i_name (a_name) {
				i_ownedPointer = new Owned (a_ownedName);
			}
			
			Owner::~Owner () {
				if (i_ownedPointer != nullptr) {
					delete i_ownedPointer;
					i_ownedPointer = nullptr;
				}
			}
			
			void moveWithoutMoveSemantics (Owner & a_destinationOwner, Owner & a_originalOwner) {
				a_destinationOwner.i_name = a_originalOwner.i_name;
				a_destinationOwner.i_ownedPointer = a_originalOwner.i_ownedPointer;
				// The re-initialization of 'a_originalOwner' Start
				a_originalOwner.i_name = string ("");
				a_originalOwner.i_ownedPointer = nullptr;
				// The re-initialization of 'a_originalOwner' End
			}
			
			int test1 () {
				Owner l_owner1 ("Owner1", "Owned1");
				Owner l_owner2 ("Owner2", "Owned2");
				moveWithoutMoveSemantics (l_owner1, l_owner2);
			}

実は、それは、「ムーブセマンティクス」の例ではなく、「ムーブセマンティクス」を使用しない「ムーブ」アクションの例だ。その例を示したのは、「ムーブセマンティクス」から離れて'「ムーブ」アクション'を実演するためだ。このように、「ムーブ」アクションを行なうために、「ムーブセマンティクス」は不可欠ではない。

以下は「ムーブセマンティクス」の例だ。

@C++ ソースコード
void copyOrMoveWithMoveSemantics (Owner & a_destinationOwner, Owner & a_originalOwner) {
				::std::cout << "The deep copy version is called." << ::std::endl << ::std::flush;
				a_destinationOwner.i_name = a_originalOwner.i_name;
				a_destinationOwner.i_ownedPointer = new Owned (a_originalOwner.i_ownedPointer->i_name);
			}
			
			void copyOrMoveWithMoveSemantics (Owner & a_destinationOwner, Owner && a_originalOwner) {
				::std::cout << "The shallow copy version is called." << ::std::endl << ::std::flush;
				a_destinationOwner.i_name = a_originalOwner.i_name;
				a_destinationOwner.i_ownedPointer = a_originalOwner.i_ownedPointer;
				// The re-initialization of 'a_originalOwner' Start
				a_originalOwner.i_name = string ("");
				a_originalOwner.i_ownedPointer = nullptr;
				// The re-initialization of 'a_originalOwner' End
			}
			
			int test2 () {
				Owner l_owner1 ("Owner1", "Owned1");
				Owner l_owner2 ("Owner2", "Owned2");
				copyOrMoveWithMoveSemantics (l_owner1, l_owner2);
				copyOrMoveWithMoveSemantics (l_owner1, Owner ("Owner3", "Owned3"));
			}

'copyOrMoveWithMoveSemantics'はオーバーロードされており、第1の'copyOrMoveWithMoveSemantics'はディープコピーを行ない、第2の'copyOrMoveWithMoveSemantics'はシャローコピーを行なってオリジナル側オブジェクトを再初期化する。それでは、'test2'内の'copyOrMoveWithMoveSemantics'コールの各々にどちらが呼ばれるのか?実は、第1のコールは第1のバージョンをコールし、第2のコールは第2のバージョンをコールする。

なぜか?...その理由は、rexpression(いわゆる"rvalue")は'&'引数より'&&'引数(それは、実のところ、rexpressionsのみを受け入れる)を優先し、lexpression(いわゆる"lvalue")は'&&'引数より'&'引数(それは、引数がコンスタントであれば、rexpressionsも受け入れる)を優先し、"l_owner2"はlexpressionであり、"Owner ("Owner3", "Owned3")"はrexpressionであることだ。

実は、「ムーブセマンティクス」とは、あるファンクションのオーバーロードがあった場合に、ファンクションに投入される各表現lexpressionであるかrexpressionであるかにしたがって、オーバーロードの内の1つが選択されるメカニズムのことだ。

繰り返すが、「ムーブセマンティクス」とは、'「ムーブ」アクションを行なう'ことではなく、'オーバーロードされたファンクションに投入された表現の表現カテゴリーにしたがって、「コピー」アクションではなく「ムーブ」アクションが選択されるメカニズム'ことだ。

他に注意すべきこととして、「ムーブセマンティクス」は「ムーブ」アクションを自動的に実装しはしない: プログラマーが「ムーブ」アクション(それは、実際には、本当にムーブを行なうものである必要はない、メカニズムの観点からすれば)を実装しなければならない。


2: 「ムーブセマンティクス」のよくあるユースケース


Hypothesizer 7
上の例では「ムーブセマンティクス」を独立(クラスメソッドでない)ファンクションに使用したが、「ムーブセマンティクス」の最もよくあるユースケースは、「ムーブ」コンストラクターおよび「ムーブ」代入オペレーターだ。

例を見てみよう。

@C++ ソースコード
#include <string>
#include <iostream>
			
			class Owned {
				public:
					::std::string i_name;
					Owned (::std::string a_name);
			};
			
			class Owner {
				public:
					::std::string i_name;
					Owned * i_ownedPointer;
					Owner (::std::string a_name, ::std::string a_ownedName);
					~Owner ();
					Owner (Owner const & a_originalOwner); // the copy consructor
					Owner (Owner && a_originalOwner); // the "move" constructor
					Owner & operator = (Owner const & a_originalOwner); // the copy assignment operator
					Owner & operator = (Owner && a_originalOwner); // the "move" assignment operator
			};
			
			Owned::Owned (::std::string a_name) : i_name (a_name) {
			}
			
			Owner::Owner (::std::string a_name, ::std::string a_ownedName) : i_name (a_name) {
				i_ownedPointer = new Owned (a_ownedName);
			}
			
			Owner::~Owner () {
				if (i_ownedPointer != nullptr) {
					delete i_ownedPointer;
					i_ownedPointer = nullptr;
				}
			}
			
			Owner::Owner (Owner const & a_originalOwner) {
				::std::cout << "The \"copy\" constructor is called." << ::std::endl << ::std::flush;
				i_name = a_originalOwner.i_name;
				i_ownedPointer = new Owned (a_originalOwner.i_ownedPointer->i_name);
			}
			
			Owner::Owner (Owner && a_originalOwner) {
				::std::cout << "The \"move\" constructor is called." << ::std::endl << ::std::flush;
				i_name = a_originalOwner.i_name;
				i_ownedPointer = a_originalOwner.i_ownedPointer;
				// The re-initialization of 'a_originalOwner' Start
				a_originalOwner.i_name = string ("");
				a_originalOwner.i_ownedPointer = nullptr;
				// The re-initialization of 'a_originalOwner' End
			}
			
			Owner & Owner::operator = (Owner const & a_originalOwner) {
				::std::cout << "The \"copy\" assignment operator is called." << ::std::endl << ::std::flush;
				i_name = a_originalOwner.i_name;
				if (i_ownedPointer != nullptr) {
					delete i_ownedPointer;
					i_ownedPointer = nullptr;
				}
				i_ownedPointer = new Owned (a_originalOwner.i_ownedPointer->i_name);
			}
			
			Owner & Owner::operator = (Owner && a_originalOwner) {
				::std::cout << "The \"move\" assignment operator is called." << ::std::endl << ::std::flush;
				i_name = a_originalOwner.i_name;
				if (i_ownedPointer != nullptr) {
					delete i_ownedPointer;
					i_ownedPointer = nullptr;
				}
				i_ownedPointer = a_originalOwner.i_ownedPointer;
				// The re-initialization of 'a_originalOwner' Start
				a_originalOwner.i_name = string ("");
				a_originalOwner.i_ownedPointer = nullptr;
				// The re-initialization of 'a_originalOwner' End
			}
			
			void test3 () {
				Owner l_owner1 ("Owner1", "Owned1");
				Owner l_owner2 (l_owner1); // calling the copy constructor
				Owner l_owner3 (Owner ("Owner3", "Owned3")); // not really calling the move constructor because of the optimization
				l_owner2 = l_owner3; // calling the copy assignment operator
				l_owner3 = Owner ("Owner4", "Owned4"); // calling the move assignment operator
			}

注意として、その「ムーブ」コンストラクターは私の環境では実際には呼ばれない、コンパイラーが最適化によってそれを除去するから。


3: 「ムーブセマンティクス」は良いアイデアなのか?


Hypothesizer 7
「ムーブセマンティクス」は本当に良いアイデアなのか?...ふーむ...

問題は、プログラマーが「ムーブ」をしたいか「コピー」をしたいかに、表現カテゴリーが本当にリンクするわけではないではことだ。なぜか?...「表現rexpressionlexpressionかが、その表現がテンポラリーオブジェクトであるか否かにリンクしているはずであり、そのデータがテンポラリーオブジェクトであるか否かが、プログラマーが「ムーブ」をしたいか「コピー」をしたいかにリンクしているはずだ」という前提に「ムーブセマンティクス」は基づいているようだが、その前提の前半部分も後半部分も正しくない。

前半部分についていうと、表現lexpressionであることは、必ずしも表現がテンポラリーオブジェクトでないことを意味しない、前記事で見たとおり。

後半部分についていうと、データがテンポラリーオブジェクトでないことは、必ずしもプログラマーが「コピー」をしたいことを意味しない。実際、「プログラマーは非テンポラリーデータから「ムーブ」をしたがるべきではない」などという断定は、余計なお世話だ。

確かに、lexpressionの値を「ムーブ」させる矯正策はある。明示的にlexpressionrexpressionに変換するという矯正策(その矯正策が次セクションのトピックだ)だ。しかし、lexpressionを「ムーブ」されないことに不正にリンクしておいて、lexpressionrexpressionに変換するよう要求するというのは、社会的不正を想起させる。なぜ、偏見そのものを是正しないのか?

それでは、どうあるべきだったと私は考えるのか?...私の意見では、「ムーブ」をしたいか「コピー」をしたいかをプログラマーに明示的に指定させればよかった(例えば、「ムーブ」代入を意味する記法(例えば、'<-')を導入すべきであった)。そのほうが、この表現lexpressionだろうかrexpressionだろうかとプログラマーの頭を悩ませる(その区別の正しく明確な説明が広まっているとは言えない)よりよいと思われる。


4: lexpressionをrexpressionに強制的に変換する方法


Hypothesizer 7
lexpressionを「ムーブ」されないことにリンクすることは不自然なので、自然なこととして、一部のlexpressionを「ムーブ」したいケースが出てくる。そうしたケースでは、'::std::move'ファンクションによって、任意のlexpressionrexpressionに変換できる。

上の'test3'ファンクションを変更して、'::std::move'ファンクションのユースケースをいくつか追加してみよう。

@C++ ソースコード
// Modified
			void test3 () {
				Owner l_owner1 ("Owner1", "Owned1");
				Owner l_owner2 (l_owner1); // calling the copy constructor
				Owner l_owner3 (Owner ("Owner3", "Owned3")); // not calling the move constructor because of the optimization
				// Added
				Owner l_owner4 (::std::move (l_owner1)); // calling the move constructor
				l_owner2 = l_owner3; // calling the copy assignment operator
				l_owner3 = Owner ("Owner4", "Owned4"); // calling the move assignment operator
				// Added
				l_owner2 = ::std::move (l_owner3); // calling the move assignment operator
			}


5: ファンクションのリターンに付ける'&&'


Hypothesizer 7
上で、ファンクション引数変数に付けられた'&&'を見たが、'&&'は、ファンクションのリターンにも使える。何の目的で?

'&&'は、そのファンクションの呼び出しをリファレンスかつrexpression(もっと詳しく言うと、xexpression)にするが、それは、そうしたファンクションコールが、別のファンクションに渡された歳に、'&'引数よりも'&&'引数を優先することを意味する。

'&&'ファンクションリターンタイプが、それだけで、lexpressionrexpressionに変換するわけではないことに注意しよう: ファンクションはrexpressionをリターンしなければならない。


6: 「xvalue」とは何か?


Hypothesizer 7
それで、"xvalue"とは何なのか?...実は、"xvalue"は実態とかけ離れた名前だ、"lvalue"および"rvalue"がそうであるように。実際には、いわゆる"xvalue"は表現であって、ではなく、私はそれを'xexpression'と呼ぶ。

'Xexpression'は、元の表現lexpressionであったかどうかにかかわらず、明示的にrexpressionに変換された表現だ。

「であったかどうかにかかわらず」?rexpressionrexpressionに変換できる?...えーと、どんなrexpressionも'move'ファンクションに渡すことができて、そのファンクションコールはxexpressionだ、そうしようとする目的は私は知らないが。

注意すべきは、「明示的にrexpressionに変換された」というのを私は広い意味で言っていることだ。

例えば、ある表現が明示的にrexpressionへ変換されたとき、その、メンバーアクセス表現も、「明示的にrexpressionへ変換された」とみなされる。

別の例としては、'&&'リターンファンクションコールも、「明示的にrexpressionへ変換された」とみなされる。

そうした例をコードで見てみよう。

@C++ ソースコード
Owner && getOwner (Owner & a_owner) {
				return ::std::move (a_owner);
			}
			
			void test4 () {
				Owner l_owner1 ("Owner1", "Owned1");
				::std::move (l_owner1).i_ownedPointer; // "::std::move (l_owner3).i_owenedPointer" is an xexpression.
				getOwner (l_owner1); // "getOwner (l_owner1)" is an xexpression.
				getOwner (l_owner1).i_ownedPointer; // "getOwner (l_owner1).i_owenedPointer" is an xexpression.
			}


7: 「prvalue」とは何か?


Hypothesizer 7
それでは、"prvalue"とは何か?...ここでもまた、"prvalue"は、実体とかけ離れた名前だ、"lvalue"および"rvalue"がそうであるように。実際、いわゆる「prvalue」は、表現であって、ではなく、それを私は'prexpression'と呼ぶ。

'prexpression'とは、明示的にrexpressionに変換したのではないrexpressionのことだ。

したがって、「ムーブセマンティクス」の導入前からrexpressionであったrexpressionはどれもprexpressionだ。


8: 表現はどれも、排他的に、lexpressionかxexpressionかprexpressionかだ


Hypothesizer 7
結局、表現はどれも、排他的に、lexpressionxexpressionprexpressionかだ。

もっと説明すると、明示的にrexpressionに変換されているのでない状態では、表現はどれも、排他的に、lexpressionprexpressionかだ。しかし、どのlexpressionprexpressionも、明示的にxexpressionへ変換できる。


9: 「glvalue」とは何か?


Hypothesizer 7
それでは、「glvalue」とは何なのか?...勿論、「glvalue」は、実体とかけ離れた名前だ、"lvalue"および"rvalue"がそうであるように。実際、いわゆる「glvalue」は、表現であって、ではなく、それを私は'glexpression'と呼ぶ。

'glexpression'とは、lexpressionまたはxexpressionのことだ。




10: 理解できない諸説明


Hypothesizer 7
「glvalueとは、アイデンティティを持っているものであり、rvalueとは、アイデンティティを持っていないものである」といった説明を見たが、その説明は私には理解できない。

xexpressionはどれも、glexpressionであり、「::std::move (Owner ("Owner1", "Owned1"))」は、xexpressionでしょう?それでは、xexpressionである「::std::move (Owner ("Owner1", "Owned1"))」はアイデンティティを持つが、prexpressionである「Owner ("Owner1", "Owned1")」はアイデンティティを持たないのか?...prexpressionは、'::std::move'ファンクションに通されると、突然、アイデンティティを得るのか?...そんな説明は道理を外れている、と私なら言う。

それに、全てのlexpressionsがアイデンティティを持っているとさえ言えるのか?...1つの例を見てみよう。

@C++ ソースコード
Owner const & getOwner () {
				return Owner ("Owner1", "Owned1");
			}
			
			void test5 () {
				::std::cout << "\getOwner ()\" is an lexpression whose value address is '" << &(getOwner ()) << "'" << ::std::endl << ::std::flush;
			}

「getOwner ()」はlexpressionである(そののアドレスを取得できるのが証拠だ)が、それがアイデンティティを持っているようには思われない、私には...(勿論、テンポラリーオブジェクトをリファレンスとしてリターンすることに問題があることを私は知っているが、それでも、それはlexpressionだ)。

表現のカテゴリーを、その表現がアイデンティティを持っているか否かで決定しようとする説明はどれも、本質的に破綻していると私は思う。

また、「rvalueとは、生存期間の終わりに近いものだ」といった説明も見たが、その説明も私には理解できない。

1つの例を見てみよう。

@C++ ソースコード
void test6 () {
				Owner l_owner1 ("Owner1", "Owned1");
				::std::move (l_owner1);
				// 'l_owner1' lives as long as this function call is not finished
				//
				//
				::std::cout << "'l_owner1' was not near the end of its lifetime, but is valid here: 'l_owner1.i_name is '" << l_owner1.i_name << "'." << ::std::endl << ::std::flush;
			}

'::std::move'ファンクションは、いかなるデータの生存期間も変えることはなく、「::std::move (l_owner1)」(正確に言うと、その)は、特に生存期間の終わりに近くない。...そもそも、「生存期間の終わりに近い」のような叙述はあいまいに過ぎるだろう!rexpressionであるために、一体どれだけ近くなければならないのか?

そのような説明をしたり、盲目的に受け入れたりするのはやめませんか?


11: いわゆる「lvalueリファレンス」および「rvalueリファレンス」とは何か?苦情付き


Hypothesizer 7
「lvalueリファレンス」は、'「lvalue」であるリファレンス'を意味せず(確かに、「lvalueリファレンス」変数はどれも(または「lvalueリファレンス」リターンのファンクションコールはどれも)、lexpressionであるが、それは、そのネーミングの理由ではない、なぜなら、「rvalueリファレンス」変数もどれも、lexpressionだから)、'「lvalue」を受け入れるリファレンス'を意味するという意図のようだが、その名前は不適切だ(間違っていないとしても): 確かに、「lvalueリファレンス」変数はどれも、どんなlexpressionも受け入れるが、コンスタントな「lvalueリファレンス」変数はどれも、どんなrexpressionをも受け入れるのであって、2つのカテゴリーの両方を受け入れるのに、その内の片方のみを掲げるそのような名前は適切には思われない。

「rvalueリファレンス」は、'「rvalue」であるリファレンス'を意味せず(「rvalueリファレンス」変数はどれもlexpressionである(「rvalueリファレンス」リターンのファンクションコールはどれもrexpressionであるが))、'「rvalue」を受け入れるリファレンス'を意味するという意図のようだが、その名前は不適切だ(間違っていないとしても): 一部の「lvalueリファレンス」変数rexpressionsを受け入れる中、その名前は、リファレンスのその2つのタイプを弁別していない。

...もっと適切な用語を採用する取り組みを始めませんか?...えーと、'lexpression親和性リファレンス'および'rexpression親和性リファレンス'はどうだろう?'親和性'を付け足すだけで、ずっとより適切になる: 第1に、「親和性」は、'lexpression'または'rexpression'が、そのリファレンスがそうであるかどうかについてではなく、そのリファレンスが何に対して親和性を持っているかについてであることを明確にするし、第2に、「親和性」は、そのリファレンスが、lexpressionsまたはrexpressionsを優先することを意味するのであって、他方を拒否することを意味しない(したがって、lexpression親和性リファレンスがrexpressionをも受け入れることは、名前を全然裏切っていない)。...もしくは、'&リファレンス'および'&&リファレンス'でもいいかもしれない: それらは、コンセプトを想起させる名前ではないが、少なくとも、実際に代表していないものを想起させて私を苛立たせはしない。

とにかく、私は、'lexpression親和性リファレンス'および'rexpression親和性リファレンス'という用語を使い、'lexpression親和性リファレンス'は、'&'で定義されたリファレンスであり、'rexpression親和性リファレンス'は、'&&'で定義されたリファレンスだ。


12: lexpression親和性リファレンスに対する理解できない制約と、部分的救済となる、rexpression親和性リファレンスの副作用


Hypothesizer 7
コンスタントでないlexpression親和性リファレンスrexpressionを受け入れないという制約があるが、その合理性の満足のいく説明に私は出会ったことがない: なぜ、rexpressionであることが、コンスタントであることを要求するのか?

その制約はテンポラリーオブジェクトを不変に保つことを意図していると私は想像するが、テンポラリーオブジェクトであることは、コンスタントであることと何の関係もないだろう?...「テンポラリーオブジェクトはどれも短時間で消滅するから、その消滅後に変更は見えないだろう」という人があるかもしれないが、私は返答するだろう、「それはそうです。だから何です?...テンポラリーオブジェクトはどれも一定時間生存するのであって(たとえ、それが短時間であったとしても)、その消滅までは、変更は、役に立ち得るし、見えもします!」。

1つの例を見てみよう。

@C++ ソースコード
#include <string>
#include <iostream>
#include <sstream>
	
			::std::string getCapitalizedString (::std::istringstream & a_stream) {
				char l_character = (char) -1;
				::std::string l_string;
				while (a_stream.get (l_character)) {
					l_string += toupper (l_character);
				}
				return l_string;
			}
			
			void test7 () {
				//::std::cout << "The capitalized string is '" << getCapitalizedString (::std::istringstream (::std::string ("abc"))) << "'." << ::std::endl << ::std::flush; // This is unreasonably rejected.
				::std::istringstream l_stream (::std::string ("abc"));
				::std::cout << "The capitalized string is '" << getCapitalizedString (l_stream) << "'." << ::std::endl << ::std::flush;
			}

そのストリームは、'getCapitalizedString'内で変化しなければならない、なぜなら、ストリームを読むことはストリーム内のカレント文字ポインターを進めることを意味するから。その変化は、有用であり必要だ、その変化が、ファンクションコール終了後に見えようが見えまいが。

テンポラリーオブジェクトを変化させることが不可能なのだろうか、テクニカルな理由のために?...そうは思わない: その証拠は、'rexpression親和性リファレンス'の導入だ('rexpression親和性リファレンス'がそれを実現している)。

今は、'rexpression親和性リファレンス'の導入によって、その不合理な制約から、私たちは開放された、部分的にだが。

なぜ部分的になのか?...なぜなら、rexpression親和性リファレンスはどれも、rexpressionsしか受け入れられないから。したがって、lexpressionrexpression親和性リファレンスに対して使うためには、そのlexpressionは、'::std::move'ファンクションに通すことによってrexpressionに変換しなければならない(特に有害ではないが、妙だ)だろう。


13: 結びとその先


Hypothesizer 7
これで、私は、「ムーブセマンティクス」、「xvalue」、「prvalue」、「glvalue」とは何かを理解したようだ。

そのようなセマンティクスおよび区分の導入は悪い判断だったのではないかと私は疑っているが、現実に向き合い、それらが何であるかを理解しなければならない。

C++について、他にも不満足な説明を見かけるので、もっとリーズナブルな説明をするよう試みよう、将来の記事にて。


参考資料


<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>