23

C++03 では、Boost の Foreach は、こ​​の興味深い手法を使用して、式が左辺値か右辺値かを実行時に検出できます。(私はこの StackOverflow question: Rvalues in C++03を介してそれを見つけました)

これが実行時に動作するデモです

(これは、私の最近の別の質問について考えているときに発生した、より基本的な質問です。これに対する回答は、他の質問の回答に役立つ可能性があります。)

コンパイル時に C++03 で rvalue-ness をテストするという質問を詳しく説明したので、これまで試してきたことについて少しお話しします。

コンパイル時にこのチェックを実行できるようにしたい。C++11なら簡単だけど、C++03が気になる。

私は彼らのアイデアに基づいて構築しようとしていますが、さまざまなアプローチにもオープンです。彼らの手法の基本的な考え方は、このコードをマクロに入れることです。

true ? rvalue_probe() : EXPRESSION;

の左側が「true」である?ため、EXPRESSION が評価されることはないと確信できます。しかし興味深いのは?:、パラメーターが左辺値か右辺値かによって、演算子の動作が異なることです (詳細については、上記のリンクをクリックしてください)。rvalue_probe特に、 EXPRESSION が左辺値であるかどうかに応じて、次の 2 つの方法のいずれかでオブジェクトを変換します。

struct rvalue_probe
{
    template< class R > operator       R () { throw "rvalue"; }
    template< class L > operator       L & () const { throw "lvalue"; }
    template< class L > operator const L & () const { throw "const lvalue"; }
};

スローされたテキストをキャッチして、EXPRESSION が左辺値か右辺値かを分析するために使用できるため、これは実行時に機能します。しかし、コンパイル時にどの変換が使用されているかを特定する方法が必要です。

これは潜在的に有用です。

EXPRESSION は右辺値ですか?

私たちは尋ねることができます:

コンパイラがtrueをコンパイルしているとき rvalue_probe() : EXPRESSION、オーバーロードされた 2 つの演算子operator Xまたはoperator X&のどちらが選択されていますか?

(通常、戻り値の型を変更してそれを取得するsizeofことで、どのメソッドが呼び出されたかを検出できます。しかし、これらの変換演算子では、特にそれらが?:.

私は次のようなものを使用できるかもしれないと思った

is_reference< typeof (true ? rvalue_probe() : EXPRESSION) > :: type

EXPRESSION が左辺値の場合、operator&が選択され、式全体が型になることを期待しました&。しかし、うまくいかないようです。ref 型と非 ref 型を区別するのは非常に困難 (不可能?) です。特に、?:どの変換が選択されたかを確認するために式の内部を掘り下げようとしている今はそうです。

ここに貼り付けたデモコードは次のとおりです。

#include <iostream>
using namespace std;
struct X {
        X(){}
};

X x;
X & xr = x;
const X xc;

      X   foo()  { return x; }
const X   fooc() { return x; }
      X & foor()  { return x; }
const X & foorc() { return x; }

struct rvalue_probe
{
        template< class R > operator       R () { throw "rvalue"; }
        // template< class R > operator R const () { throw "const rvalue"; } // doesn't work, don't know why
        template< class L > operator       L & () const { throw "lvalue"; }
        template< class L > operator const L & () const { throw "const lvalue"; }
};

typedef int lvalue_flag[1];
typedef int rvalue_flag[2];
template <typename T> struct isref     { static const int value = 0; typedef lvalue_flag type; };
template <typename T> struct isref<T&> { static const int value = 1; typedef rvalue_flag type; };

int main() {
        try{ true ? rvalue_probe() : x;       } catch (const char * result) { cout << result << endl; } // Y lvalue
        try{ true ? rvalue_probe() : xc;      } catch (const char * result) { cout << result << endl; } // Y const lvalue
        try{ true ? rvalue_probe() : xr;      } catch (const char * result) { cout << result << endl; } // Y       lvalue
        try{ true ? rvalue_probe() : foo();   } catch (const char * result) { cout << result << endl; } // Y rvalue
        try{ true ? rvalue_probe() : fooc();  } catch (const char * result) { cout << result << endl; } // Y rvalue
        try{ true ? rvalue_probe() : foor();  } catch (const char * result) { cout << result << endl; } // Y lvalue
        try{ true ? rvalue_probe() : foorc(); } catch (const char * result) { cout << result << endl; } // Y const lvalue

}

(最後に別のコードがいくつかありましたが、それは単に混乱を招くだけです。失敗した回答の試行を実際に見たくはありません! 上記のコードは、実行時に lvalue-vsus-rvalue をテストする方法を示しています。)

4

2 に答える 2

8

少し手間がかかりましたが、関数の戻り型is_lvalueを正しく処理する、テスト済みで機能するマクロを次に示します。にバインドされていない右辺値const struct Sに依存しますが、左辺値はバインドされます。const struct Sconst volatile struct S&const struct S

#include <cassert>

template <typename T>
struct nondeducible
{
  typedef T type;
};

char (& is_lvalue_helper(...))[1];

template <typename T>
char (& is_lvalue_helper(T&, typename nondeducible<const volatile T&>::type))[2];

#define is_lvalue(x) (sizeof(is_lvalue_helper((x),(x))) == 2)

struct S
{
  int i;
};

template <typename T>
void test_()
{
  T a = {0};
  T& b = a;
  T (* c)() = 0;
  T& (* d)() = 0;
  assert (is_lvalue(a));
  assert (is_lvalue(b));
  assert (!is_lvalue(c()));
  assert (is_lvalue(d()));
}

template <typename T>
void test()
{
  test_<T>();
  test_<const T>();
  test_<volatile T>();
  test_<const volatile T>();
}

int main()
{
  test<int>();
  test<S>();
}

編集:Xeoに感謝し、不要な余分なパラメータを削除しました。

再度編集:コメントによると、これはGCCで機能しますが、C ++ 03(有効なC ++ 11)での不特定の動作に依存し、他のいくつかのコンパイラーに失敗します。余分なパラメータが復元され、より多くの場合に機能するようになります。constクラスの右辺値は、一部のコンパイラーではハードエラーを出し、他のコンパイラーでは正しい結果(false)を出します。

于 2012-01-31T23:27:58.043 に答える
1

アドレス演算子 ( &) は、左辺値でのみ使用できます。したがって、SFINAE テストで使用した場合は、コンパイル時に区別できます。

静的アサーションは次のようになります。

#define STATIC_ASSERT_IS_LVALUE(x) ( (sizeof &(x)), (x) )

特性バージョンは次のようになります。

template<typename T>
struct has_lvalue_subscript
{
    typedef char yes[1];
    typedef char no[2];

    yes fn( char (*)[sizeof (&(((T*)0)->operator[](0))] );
    no fn(...);
    enum { value = sizeof(fn(0)) == 1 };
};

のように使用できます

has_lvalue_subscript< std::vector<int> >::value

(警告: テストされていません)

失敗時にコンパイルを中断せずに、呼び出し元のコンテキストで有効な任意の式をテストする方法は考えられません。

于 2012-01-31T19:18:46.883 に答える