9

コード例を次に示します。

#include <iostream>

class Foo
{
public:
  explicit Foo(int x) : data(x) {};

  Foo& operator++()
  {
    data += 1;
    return *this;
  }

  void *get_addr()
  {
    return (void*)this;
  }

  friend Foo operator + (const Foo& lhs, const Foo& rhs);
  friend std::ostream& operator << (std::ostream& os, const Foo& f);

private:
  int data;
};

std::ostream& operator << (std::ostream& os, const Foo& f)
{
  return (os << f.data);
}

Foo operator + (const Foo& lhs, const Foo& rhs)
{
  return Foo(lhs.data + rhs.data);
}

void bar(Foo& f)
{
  std::cout << "bar(l-value ref)" << std::endl;
}

void bar(const Foo& f)
{
  std::cout << "bar(const l-value ref)" << std::endl;
}

void bar(Foo&& f)
{
  std::cout << "bar(r-value ref)" << std::endl;
}

int main()
{
  // getting the identity of the object
  std::cout << Foo(5).get_addr() << std::endl;  // Can write &Foo(5)
                                                // by overloading &
  // overload resolution
  bar(Foo(5));                                       // prints r-value ref

  // default copy assignment
  std::cout << (Foo(78) = Foo(86)) << std::endl;     // prints 86

  // mutating operations
  std::cout << (++Foo(5)) << std::endl;              // prints 6

  // more mutating operations
  std::cout << (++(Foo(78) + Foo(86))) << std::endl; // prints 165
  // overload resolution
  bar((Foo(78) + Foo(86)));                          // prints r-value ref
}

Foo(5) のような式は prvalue ですか、それとも一般的な rvalue ですか? これらの式で get_addr() を呼び出すことができるという事実は、それらが同一性を持っていることを意味しますか? または、デフォルトの &-operator (オーバーロードされていないことを意味します) を適用できないという事実は、それらが ID を持たず、したがって prvalues であることを意味しますか?

それを生成した式を介して生成された値の可変性は、この値分類に直交していると言っても過言ではありませんか?

4

2 に答える 2

17

すべての式は、次のうちの 1 つだけです。

  • 左辺値
  • x値
  • 価格

lvalues と xvalues である式の和集合は、まとめてglvaluesとして知られています。

xvalue と prvalue である式の結合は、まとめて rvalue と呼ばれます

したがって、xvalue 式は、glvalue と rvalue の両方として知られています。

Alfの回答にある便利な図は、上記の言葉で説明した関係を正しく示しており、C++ 標準のセクション 3.10、バージョン C++11 以降にもあります。

私が上で言ったことはすべて、この質問のタイトルの文言から、OPがすでに知っていたと思います。


トリビア:

Bjarne Stroustrup は、この式の分類を発明しました。そうすることで、おそらく右辺値参照の提案全体が Core Working Group で崩壊するのを防いだのでしょう。私は永遠に感謝します。


私が追加しているのは、任意の式が下位 3 つの分類カテゴリ (lvalue、xvalue、または prvalue) のどれに該当するかを自分で発見する方法です。

#include <type_traits>
#include <typeinfo>
#include <iostream>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <typename T>
std::string
expression_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                __cxxabiv1::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += "const ";
    if (std::is_volatile<TR>::value)
        r += "volatile ";
    if (std::is_lvalue_reference<T>::value)
        r = "lvalue expression of type " + r;
    else if (std::is_rvalue_reference<T>::value)
        r = "xvalue expression of type " + r;
    else
        r = "prvalue expression of type " + r;
    return r;
}

上記の関数は次のように使用できます。

std::cout << "some_expression is a " << expression_name<decltype(some_expression)>() << '\n';

そして、このOPの質問に答えます。例えば:

int main()
{
    std::cout << "Foo(5) is a " << expression_name<decltype(Foo(5))>() << '\n';
    std::cout << "Foo(5).get_addr() is a " << expression_name<decltype(Foo(5).get_addr())>() << '\n';
    std::cout << "Foo(78) = Foo(86) is a " << expression_name<decltype(Foo(78) = Foo(86))>() << '\n';
    std::cout << "++Foo(5) is a " << expression_name<decltype(++Foo(5))>() << '\n';
    std::cout << "++(Foo(78) + Foo(86)) is a " << expression_name<decltype(++(Foo(78) + Foo(86)))>() << '\n';
    std::cout << "Foo(78) + Foo(86) is a " << expression_name<decltype(Foo(78) + Foo(86))>() << '\n';
    std::cout << "std::move(Foo(5)) is a " << expression_name<decltype(std::move(Foo(5)))>() << '\n';
    std::cout << "std::move(++Foo(5)) is a " << expression_name<decltype(std::move(++Foo(5)))>() << '\n';
}

私にとっては、次のように出力されます。

Foo(5) is a prvalue expression of type Foo
Foo(5).get_addr() is a prvalue expression of type void*
Foo(78) = Foo(86) is a lvalue expression of type Foo
++Foo(5) is a lvalue expression of type Foo
++(Foo(78) + Foo(86)) is a lvalue expression of type Foo
Foo(78) + Foo(86) is a prvalue expression of type Foo
std::move(Foo(5)) is a xvalue expression of type Foo
std::move(++Foo(5)) is a xvalue expression of type Foo

この関数を使用する際に注意が必要な領域の 1 つ:

decltype(variable_name)変数名の宣言された型を与えます。が使用されたときに(宣言された型とは対照的に)の値カテゴリを見つけたい場合は、 で使用されたときvariable_nameに余分な括弧を追加する必要があります。あれは:(variable_name)decltype

decltype((variable_name))

は式 の型であり variable_nameの宣言された型ではありませんvariable_name

たとえば、次のようになります。

    Foo&& foo = Foo(5);
    std::cout << "foo is a " << expression_name<decltype(foo)>() << '\n';

これは誤って出力されます:

foo is a xvalue expression of type Foo

に余分な括弧を追加しますdecltype

    std::cout << "foo is a " << expression_name<decltype((foo))>() << '\n';

foo型名から式に変換します。出力は次のようになります。

foo is a lvalue expression of type Foo

正しい答えを得るために括弧を追加する必要があるかどうかわからない場合は、括弧を追加してください。それらを追加しても、正解が間違っていることはありません-式の型ではなく、宣言された変数の型を取得しようとしている場合を除きます。後者の場合、密接に関連する関数が必要です: type_name<T>().

于 2013-12-21T17:54:02.383 に答える
7

C++ の式は、左辺値または右辺値のいずれかです。したがって、右辺値である分類を求めています。そのためには、C++11 標準 §3.10/1 の分類ツリーを示す図を調べてください。

表現カテゴリの分類


詳細については (標準を掘り下げることなく)、What are rvalues, lvalues, ...を参照してください。


それにかんする

「Foo(5) のような式は rvalues か prvalue か」

左辺値にはなり得ないため、右辺値には prvalue が必要です。

prvalue "("pure" rvalue) は xvalue ではない rvalue であり、xvalue"rvalue 参照を含む特定の種類の式の結果" です コンストラクター呼び出しは rvalue 参照を生成しないため、xvalue ではありません. したがって、右辺値は純粋な右辺値である prvalue です。

于 2013-12-21T08:50:49.930 に答える