C++ 03では、式は rvalue または lvalue のいずれかです。
C++ 11では、式は次のようになります。
2つのカテゴリーが5つのカテゴリーになりました。
私はこの文書がそれほど短くない紹介として役立つかもしれないと思います: n3055
全体の虐殺は移動の意味論から始まった。移動することができてコピーできない表現ができたら、規則をつかむのが突然容易になるため、移動できる表現とその方向を区別する必要がありました。
私がドラフトに基づいて推測していることから、r/l値の区別は変わらず、物事を動かすという意味では乱雑になります。
彼らは必要ですか?おそらく私たちが新機能を失いたいのであれば、そうではないでしょう。しかし、より良い最適化を可能にするためには、おそらくそれらを受け入れるべきです。
引用符付け n3055 :
E
がポインター型の式である場合、*E
はE
が指すオブジェクトまたは関数を指す左辺値式です。別の例として、戻り値の型が左辺値参照である関数の呼び出し結果は左辺値。]それが新しい命名法の導入の結果として起こった規格の正確な変化を示すので、問題の文書はこの質問のための素晴らしい参考文献です。
これらの新しいカテゴリの表現は何ですか?
FCD(n3092) には、優れた説明があります。
- 左辺値(代数式の左辺に左辺値が表示される可能性があるため、歴史的に呼ばれます)は関数またはオブジェクトを指定します。 [例:Eがポインタ型の式の場合、* EはEが指すオブジェクトまたは関数を指す左辺値式です。別の例として、戻り値の型が左辺値参照である関数を呼び出した結果は左辺値です。 - 終了例]
- xvalue( "eXpiring"値)も、通常は存続期間の終わり近く(たとえば、リソースが移動される可能性があるように)にあるオブジェクトを指します。 xvalueは右辺値参照を含むある種の式の結果です(8.3.2)。 [例:戻り値の型が右辺値参照である関数を呼び出した結果はxvalueです。 - 終了例]
- glvalue(「一般化」左辺値)は、左辺値またはx値です。
- 右辺値(代数式の右辺に右辺値が現れる可能性があるため、いわゆる歴史的に)は、xvalue、一時オブジェクト(12.2)、またはそれらのサブオブジェクト、あるいはオブジェクトに関連付けられていない値です。
- prvalue(“純粋な”右辺値)はx値ではない右辺値です。 [例:戻り型が参照ではない関数を呼び出した結果はprvalueです。 12、7.3e5、trueなどのリテラルの値も重要です。 - 終了例]
すべての表現は、この分類法の基本的な分類の1つ、lvalue、xvalue、またはprvalueに正確に属します。式のこのプロパティは、その値カテゴリと呼ばれます。 [注:5項の各組み込み演算子の説明は、それがもたらす値のカテゴリーと、それが期待するオペランドの値カテゴリーを示しています。たとえば、組み込み代入演算子は、左のオペランドが左辺値であり、右のオペランドが右辺値であると想定し、結果として左辺値を生成します。ユーザー定義演算子は関数であり、それらが期待し、生み出す値のカテゴリはそれらのパラメータと戻り値の型によって決定されます。 - エンドノート
3.10左辺値と右辺値 を読んでください。
これらの新しいカテゴリは、既存の右辺値および左辺値カテゴリとどのように関連していますか?
再び:
C++ 0xの右辺値と左辺値のカテゴリは、C++ 03のものと同じですか?
右辺値のセマンティクスは、移動セマンティクスの導入によって特に進化しました。
なぜこれらの新しいカテゴリが必要なのでしょうか。
そのため、移動の構築/割り当てを定義し、サポートすることができます。
最後の質問から始めましょう。
なぜこれらの新しいカテゴリが必要なのでしょうか。
C++標準には、式の値のカテゴリを扱う多くの規則が含まれています。左辺値と右辺値を区別する規則がいくつかあります。例えば、それが過負荷解決になると。他の規則はglvalueとprvalueを区別します。たとえば、不完全型または抽象型のglvalueを持つことはできますが、不完全型または抽象型のprvalueはありません。この用語が使われる前は、glvalue/prvalueを実際に区別する必要があるルールはlvalue/rvalueを参照していました。右辺値参照... "ですから、glvaluesとprvaluesの概念にそれら自身の名前を単に与えることは良い考えのように思えます。
これらの新しいカテゴリの表現は何ですか?これらの新しいカテゴリは、既存の右辺値および左辺値カテゴリとどのように関連していますか?
C++ 98と互換性のある、左辺値と右辺値という用語がまだあります。右辺値を2つのサブグループ、xvaluesとprvaluesに分割したところで、lvaluesとxvaluesをglvaluesと呼びます。 X値は、名前のない右辺値参照のための新しい種類の値カテゴリです。すべての式は、次の3つのうちのいずれかです。l値、x値、pr値。ベン図は次のようになります。
______ ______
/ X \
/ / \ \
| l | x | pr |
\ \ / /
\______X______/
gl r
関数を使った例
int prvalue();
int& lvalue();
int&& xvalue();
しかし、名前付き右辺値参照が左辺値であることも忘れないでください。
void foo(int&& t) {
// t is initialized with an rvalue expression
// but is actually an lvalue expression itself
}
なぜこれらの新しいカテゴリが必要なのでしょうか。 WG21の神々は私たちを単なる人間に混乱させようとしているだけなのでしょうか。
私は他の答え(それらの多くはそうですが良い)が本当にこの特定の質問に対する答えをとらえているとは思わない。はい、これらのカテゴリなどは移動セマンティクスを許可するために存在しますが、複雑さは1つの理由で存在します。これはC++ 11でものを動かすことの1つの不可抗力な規則です:
間違いなく安全な場合にのみ移動します。
それがこれらのカテゴリーが存在する理由です:それらから移動するのが安全であるところで値について話すことができる、そしてそうでないところで値について話すことができるようにするため。
初期のバージョンのr値参照では、動きは簡単に起こりました。 Too簡単。ユーザーが本当に意図していなかったときに暗黙のうちに物事を動かす可能性がたくさんあったことは簡単に十分です。
これは、何かを動かしても安全な状況です。
あなたがこれをするならば:
SomeType &&Func() { ... }
SomeType &&val = Func();
SomeType otherVal{val};
これは何をしますか?仕様の古いバージョンでは、5つの値が現れる前に、これは動きを引き起こします。もちろんそうです。右辺値参照をコンストラクタに渡したため、右辺値参照を受け取るコンストラクタにバインドされます。それは明らかです。
これにはただ一つ問題があります。あなたはそれを動かすためにaskをしませんでした。おお、あなたは&&
が手がかりであるべきだったと言うかもしれません、しかしそれはそれが規則を破ったという事実を変えません。一時変数には名前がないため、val
は一時的なものではありません。あなたはテンポラリーの寿命を延ばしたかもしれませんが、それはテンポラリーではないことを意味します。他のスタック変数とまったく同じです。
それが一時的なものではなく、あなたがそれを動かすことを求めなかったならば、動くことは間違っています
明らかな解決策はval
を左辺値にすることです。これはあなたがそこから動くことができないことを意味します。いいよ;それは名前が付けられているので、それは左辺値です。
一度それをしたら、SomeType&&
がどこでも同じことを意味すると言うことはもはやできません。名前付き右辺値参照と名前なし右辺値参照を区別しました。名前付き右辺値参照は左辺値です。それが上記の解決策でした。それでは、無名の右辺値参照(上記のFunc
からの戻り値)を何と呼びますか?
左辺値から移動することはできませんので、それは左辺値ではありません。 &&
を返すことで移動できるようにするためにneed;何か他の人が何かを明示的に言うことができますか?結局それはstd::move
が返すものです。これは右辺値(昔ながらのスタイル)ではありません、なぜならそれは方程式の左側にあるからです(実際にはもう少し複雑です。 this question と以下のコメントを参照してください)。左辺値でも右辺値でもありません。それは新しい種類のものです。
私たちが持っているものは左辺値として扱うことができる値であり、exceptは暗黙のうちに移動可能です。これをxvalueと呼びます。
Xvaluesは他の2つの値のカテゴリーを得るためのものです。
Prvalueは、実際には前のタイプの右辺値の新しい名前です。つまり、それらはare not xvaluesという右辺値です。
Glvaluesはxvalueとlvalueを1つのグループに結合したものです。なぜなら、それらは共通の多くの特性を共有しているからです。
それで、本当に、それはすべてx値と動きを正確にそして特定の場所だけに制限する必要性に帰着します。それらの場所は右辺値カテゴリによって定義されます。 prvaluesは暗黙的な移動で、xvaluesは明示的な移動です(std::move
はxvalueを返します)。
私見、その意味についての最良の説明は私たちに与えた Stroustrup + DánielSándor と Mohan の例を考慮に入れる:
Stroustrup:
今、私は真剣に心配していました。明らかに我々は行き詰まりまたは混乱、あるいはその両方に向かっていた。私は昼食時間を分析して、どの値のプロパティが独立しているかを調べました。 2つの独立したプロパティのみがありました。
has identity
–つまり、アドレス、ポインタ、ユーザーは2つのコピーが同一であるかどうかを判断できます。can be moved from
–つまり、不確定だが有効な状態で「コピー」のソースに残すことが許可されていますこれにより、正確に3種類の値があるという結論に至りました(ネガティブを示すために大文字を使用する正規表現のトリックを使用して、急いでいた)。
iM
:IDがあり、移動できませんim
:IDがあり、移動可能です(たとえば、左辺値を右辺値参照にキャストした結果)
Im
:IDがないため、移動できます。4番目の可能性
IM
(IDがなく、移動できない)は、他の言語のC++
(または、と思います)では役に立ちません。これらの3つの基本的な値の分類に加えて、2つの独立したプロパティに対応する2つの明白な一般化があります。
i
:IDを持っていますm
:から移動可能ネーミング
名前を付ける自由が限られていることに気付きました:左の2つのポイント(
iM
とi
のラベル)は、多少形式的な人がlvalues
と呼んでいるもので、右側(ラベルm
およびIm
)は、多少形式的な人々がrvalues
と呼んでいるものです。これは命名に反映する必要があります。つまり、W
の左側の「脚」にはlvalue
に関連する名前があり、W
の右側の「脚」にはrvalue.
に関連する名前が必要です。この全体の議論/問題は、右辺値参照の導入とセマンティクスの移動から生じること。これらの概念は、単にrvalues
とlvalues
だけで構成されるStracheyの世界には存在しません。誰かがその考えを観察した
- すべての
value
は、lvalue
またはrvalue
のいずれかです。lvalue
はrvalue
ではなく、rvalue
はlvalue
ではありませんは私たちの意識に深く埋め込まれており、非常に有用な特性であり、この二分法の痕跡はドラフト標準全体に見られます。私たちは皆、それらのプロパティを保存する(そして正確にする)ことに同意しました。これにより、命名の選択肢がさらに制限されました。標準ライブラリの文言は
rvalue
を使用してm
(一般化)を意味するため、標準ライブラリの期待とテキストを保持するためにW
の右下の点を観察しました。rvalue.
という名前にする必要がありますこれにより、ネーミングの議論が集中しました。まず、
lvalue.
を決定する必要がありましたlvalue
はiM
を意味するか、一般化i
を意味しますか?ダグ・グレゴール率いる、コア言語の言葉遣いで、単語lvalue
がどちらかを意味するように修飾された場所をリストしました。リストが作成され、ほとんどの場合、最も扱いにくい/壊れやすいテキストでは、lvalue
は現在iM
を意味します。 「昔は」何も動かされなかったため、これは左辺値の古典的な意味です。move
はC++0x
の斬新な概念です。また、W
lvalue
の左上のポイントに名前を付けると、すべての値がlvalue
またはrvalue
であるが、両方ではないというプロパティが得られます。それで、
W
の左上の点はlvalue
であり、右下の点はrvalue.
です。それは左下と右上の点を何にするのでしょうか。左下の点は、古典的な左辺値の一般化であり、移動が可能です。generalized lvalue.
という名前です。glvalue.
という名前を付けました。略語については口論することはできますが、論理的にはそうではありません。深刻な使用では、generalized lvalue
はとにかく省略されると想定したので、すぐに実行する(または混乱を招く)方がよいと考えました。 Wの右上のポイントは、右下よりも一般的ではありません(これまでどおり、rvalue
と呼ばれます)。その点は、(デストラクタによる場合を除いて)再び参照することができないため、移動できるオブジェクトの元の純粋な概念を表します。specialized rvalue
とは対照的にgeneralized lvalue
というフレーズが好きでしたが、pure rvalue
と省略されたprvalue
が勝ちました(そしておそらくそうでしょう)。したがって、Wの左脚はlvalue
およびglvalue
であり、右脚はprvalue
およびrvalue.
です。ちなみに、すべての値はglvalueまたはprvalueですが、両方。これにより、
W
の上部中央が残ります:im
;つまり、IDがあり、移動できる値。私たちはこれらの難解な獣の良い名前に私たちを導くものを本当に持っていません。それらは(ドラフト)標準テキストで作業する人々にとって重要ですが、一般的な名前にはなりそうにありません。ネーミングに実際の制約はありませんでした。そのため、センター、不明、奇妙、xpertのみ、またはx-ratedに「x」を選択しました。
ISOC++ 11(正式にはISO/IEC 14882:2011)は、C++プログラミング言語の標準の最新バージョンです。たとえば、次のようないくつかの新機能と概念が含まれています。
新しい式の値カテゴリの概念を理解したい場合は、右辺値と左辺値の参照があることに注意する必要があります。右辺値を非定数右辺値参照に渡すことができることを知っておくとよいでしょう。
int& r_i=7; // compile error
int&& rr_i=7; // OK
作業ドラフトN3337(公開されたISOC++ 11標準に最も類似したドラフト)から左辺値と右辺値というタイトルのサブセクションを引用すると、値カテゴリの概念のいくつかの直観を得ることができます。
3.10左辺値と右辺値[basic.lval]
1式は、図1の分類法に従って分類されます。
- 左辺値(歴史的に、左辺値は代入式の左側に表示される可能性があるため)は、関数またはオブジェクトを指定します。 [例:Eがポインター型の式である場合、* EはEが指すオブジェクトまたは関数を参照する左辺値式です。別の例として、戻り値の型が左辺値参照である関数を呼び出した結果は左辺値です。 —例の終了]
- Xvalue(「eXpiring」値)は、通常はその寿命の終わり近くにあるオブジェクトも参照します(たとえば、リソースを移動できるようにするため)。 xvalueは、右辺値参照を含む特定の種類の式の結果です(8.3.2)。 [例:戻り値の型が右辺値参照である関数を呼び出した結果はxvalueです。 —例の終了]
- Glvalue(「一般化された」左辺値)は左辺値またはx値です。
- 右辺値(歴史的に、右辺値は代入式の右側に表示される可能性があるため、いわゆる)はxvalueです。
一時オブジェクト(12.2)またはそのサブオブジェクト、またはそうでない値
オブジェクトに関連付けられています。- Prvalue(「純粋な」右辺値)は、xvalueではない右辺値です。 [例:戻り値の型がaではない関数を呼び出した結果
referenceはprvalueです。 12、7.3e5などのリテラルの値、または
trueは値でもあります。 —例の終了]すべての式は、この分類法の基本的な分類(lvalue、xvalue、またはprvalue)のいずれかに属します。式のこのプロパティは、値カテゴリと呼ばれます。
しかし、このサブセクションが概念を明確に理解するのに十分かどうかはよくわかりません。「通常」はあまり一般的ではなく、「その寿命の終わり近く」は実際には具体的ではなく、「右辺値参照を含む」は本当に明確ではないため「例:戻り値の型が右辺値参照である関数を呼び出した結果はxvalueです。」ヘビが尾を噛んでいるようです。
すべての式は、厳密に1つのプライマリ値カテゴリに属します。これらの値カテゴリは、左辺値、x値、およびpr値のカテゴリです。
式Eは、ALREADYがEの外部からアクセスできるようにするID(アドレス、名前、またはエイリアス)を持っているエンティティを参照する場合にのみ、左辺値カテゴリに属します。
#include <iostream>
int i=7;
const int& f(){
return i;
}
int main()
{
std::cout<<&"www"<<std::endl; // The expression "www" in this row is an lvalue expression, because string literals are arrays and every array has an address.
i; // The expression i in this row is an lvalue expression, because it refers to the same entity ...
i; // ... as the entity the expression i in this row refers to.
int* p_i=new int(7);
*p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ...
*p_i; // ... as the entity the expression *p_i in this row refers to.
const int& r_I=7;
r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ...
r_I; // ... as the entity the expression r_I in this row refers to.
f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ...
i; // ... as the entity the expression f() in this row refers to.
return 0;
}
式Eは、次の場合にのみxvalueカテゴリに属します
—暗黙的または明示的に、関数を呼び出した結果、その戻り値の型は、返されるオブジェクトの型への右辺値参照、または
int&& f(){
return 3;
}
int main()
{
f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type.
return 0;
}
—オブジェクト型への右辺値参照へのキャスト、または
int main()
{
static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type.
std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7).
return 0;
}
—オブジェクト式がxvalueである非参照型の非静的データメンバーを指定するクラスメンバーアクセス式、または
struct As
{
int i;
};
As&& f(){
return As();
}
int main()
{
f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category.
return 0;
}
—第1オペランドがxvalueで、第2オペランドがデータメンバーへのポインターであるメンバーへのポインター式。
上記のルールの効果は、オブジェクトへの名前付き右辺値参照が左辺値として扱われ、オブジェクトへの名前なし右辺値参照がxvaluesとして扱われることに注意してください。関数への右辺値参照は、名前の有無にかかわらず左辺値として扱われます。
#include <functional>
struct As
{
int i;
};
As&& f(){
return As();
}
int main()
{
f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object.
As&& rr_a=As();
rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object.
std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function.
return 0;
}
式Eは、Eが左辺値にもxvalueカテゴリにも属さない場合にのみprvalueカテゴリに属します。
struct As
{
void f(){
this; // The expression this is a prvalue expression. Note, that the expression this is not a variable.
}
};
As f(){
return As();
}
int main()
{
f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category.
return 0;
}
さらに2つの重要な混合値カテゴリがあります。これらの値カテゴリは、右辺値カテゴリと左辺値カテゴリです。
式Eは、Eがxvalueカテゴリまたはprvalueカテゴリに属する場合にのみ、右辺値カテゴリに属します。
この定義は式Eが右辺カテゴリに属することを意味することに注意してください。ただし、EがE YETの外部からアクセスできるIDを持たないエンティティを参照する場合のみです。
式Eは、Eが左辺値カテゴリまたはxvalueカテゴリに属する場合にのみglvalueカテゴリに属します。
Scott Meyerには、右辺値と左辺値を区別する非常に便利な経験則 published があります。
- 式のアドレスを取得できる場合、式は左辺値です。
- 式の型が左辺値参照(例:T&またはconst T&など)である場合、その式は左辺値です。
- それ以外の場合、式は右辺値です。概念的に(そして実際にも)、右辺値は、関数から返されたオブジェクトや暗黙的な型変換によって作成されたオブジェクトなどの一時オブジェクトに対応します。ほとんどのリテラル値(10や5.3など)も右辺値です。
C++ 03のカテゴリはあまりにも制限されているため、右辺値参照を式属性に正しく取り込むことができません。
それらの導入により、名前のない右辺値参照は右辺値に評価されるので、オーバーロード解決は右辺値参照バインディングを優先し、コピーコンストラクタよりも移動コンストラクタを選択するようになります。しかし、これは、たとえば 動的型 や修飾子など、あらゆる問題を引き起こすことがわかりました。
これを示すために、考えてみましょう
int const&& f();
int main() {
int &&i = f(); // disgusting!
}
Xvalue以前のドラフトでは、これが許可されていました。C++ 03では、クラス以外の型の右辺値がcv修飾されていないためです。しかし、ここではdoがオブジェクト(= memory!)を参照しているため、const
が右辺値参照の場合に適用されることを意図しています。周りのオブジェクト。
動的型の問題も同様です。 C++ 03では、クラス型の右辺値は既知の動的型を持ちます - それはその式の静的型です。それを別の方法で持つためには、左辺値に評価される参照または間接参照が必要です。名前のない右辺値参照ではそうではありませんが、多相的な振る舞いを示すことがあります。それを解決するために、
無名の右辺値参照は xvalues になります。それらは修飾することができ、潜在的にそれらの動的タイプを異ならせることができます。意図したように、それらはオーバーロード時に右辺値参照を優先し、非定数左辺値参照にバインドしません。
以前は右辺値(リテラル、非参照型へのキャストによって作成されたオブジェクト)は、 prvalue になりました。それらはオーバーロード中のxvaluesと同じ好みを持ちます。
以前左辺値だったものは左辺値のままです。
そして、2つのグループ化が行われ、修飾可能で異なる動的タイプを持つことができるもの( glvalues )と、オーバーロードが右辺値参照バインディングを好むもの( rvalues )を取ります。
値のカテゴリ についてのcppreference.comの説明に出会うまで、私は長い間これに苦労してきました。
それは実際にはかなり簡単です、しかし私はそれが暗記するのが難しい方法で説明されることが多いことがわかります。ここでそれは非常に概略的に説明されています。ページの一部を引用します。
主なカテゴリー
主な値のカテゴリは、式の2つのプロパティに対応しています。
同一性を持つ :式が他の式と同じ実体を参照しているかどうかを判断することができます。
は から移動できます。移動コンストラクタ、移動代入演算子、または移動セマンティクスを実装する別の関数オーバーロードを式にバインドできます。
式は、
- 左辺値式 ;と同一性があり、移動できません。
- xvalue式 ;と同一性があり、そこから移動することができます。
- prvalue式 ;というアイデンティティを持たず、移動することができます。
- アイデンティティを持たず、そこから移動できないものは使用されません。
左辺値
左辺値( "left value")式は、 が同一性 を持ち、 を から移動できないという式です。
rvalue(C++ 11まで)、prvalue(C++ 11以降)
Prvalue( "pure rvalue")式は、 が同一性 を持たず、 を から移動できる式です。
x値
Xvalue( "expiring value")式は、 が同一性 を持ち、 を から移動できる式です。
大値
Glvalue( "一般化左辺値")式は、左辺値またはx値のいずれかである表現です。それは アイデンティティ を持ちます。移動してもしなくてもかまいません。
右辺値(C++ 11以降)
右辺値( "正しい値")式は、prvalueまたはxvalueのいずれかである式です。 は から移動できます。それはアイデンティティを持っていてもいなくてもよいです。
これらの新しいカテゴリは、既存の右辺値および左辺値カテゴリとどのように関連していますか?
C++ 03の左辺値はまだC++ 11の左辺値ですが、C++ 11ではC++ 03の右辺値はprvalueと呼ばれます。
Stroustrupを読んで右辺値と左辺値の違いを理解した後でさえ私を混乱させたという点で、上記の優れた答えへの1つの補足見ると
int&& a = 3
、
int&&
を型として読み、a
は右辺値であると結論付けるのは非常に魅力的です。そうではありません:
int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles
a
は名前を持ち、ipso factoは左辺値です。 &&
をa
の型の一部と考えないでください。それはa
がどんなものに束縛されることが許されるかをあなたに言っているだけのものです。
これは特にコンストラクタ内のT&&
型引数にとって重要です。書くなら
Foo::Foo(T&& _t) : t{_t} {}
_t
をt
にコピーします。あなたが必要
移動したい場合はFoo::Foo(T&& _t) : t{std::move(_t)} {}
。 move
を省略したときに、私のコンパイラが私に警告してくれたでしょうか。
これまでの回答で値カテゴリーの背後にある理論が徹底的に網羅されていたので、追加したいことがもう1つあります。実際にそれを試してテストすることができます。
値のカテゴリを実際に試してみるためには、 decltype指定子 を利用することができます。その振る舞いは、3つの基本値カテゴリー(xvalue、lvalue、およびprvalue)を明確に区別します。
プリプロセッサを使用すると、タイピングの手間が省けます。
主なカテゴリー:
#define IS_XVALUE(X) std::is_rvalue_reference<decltype((X))>::value
#define IS_LVALUE(X) std::is_lvalue_reference<decltype((X))>::value
#define IS_PRVALUE(X) !std::is_reference<decltype((X))>::value
混合カテゴリー:
#define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)
#define IS_RVALUE(X) IS_PRVALUE(X) || IS_XVALUE(X)
これで、(ほぼ)すべての例を 値カテゴリのcppreference から再現できます。
C++ 17(簡潔なstatic_assert用)のいくつかの例を示します。
void doesNothing(){}
struct S
{
int x{0};
};
int x = 1;
int y = 2;
S s;
static_assert(IS_LVALUE(x));
static_assert(IS_LVALUE(x+=y));
static_assert(IS_LVALUE("Hello world!"));
static_assert(IS_LVALUE(++x));
static_assert(IS_PRVALUE(1));
static_assert(IS_PRVALUE(x++));
static_assert(IS_PRVALUE(static_cast<double>(x)));
static_assert(IS_PRVALUE(std::string{}));
static_assert(IS_PRVALUE(throw std::exception()));
static_assert(IS_PRVALUE(doesNothing()));
static_assert(IS_XVALUE(std::move(s)));
// The next one doesn't work in gcc 8.2 but in gcc(trunk). Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x));
あなたが一次カテゴリーを考え出したら、混合カテゴリーは一種の退屈です。
いくつかの例(および実験)については、次の コンパイラエクスプローラのリンク を参照してください。しかし、議会を読むことを邪魔しないでください。私はそれがすべての一般的なコンパイラに渡って確実に機能するように、たくさんのコンパイラを追加しました。