23

在 C++03 中,Boost 的 Foreach 使用这种有趣的技术,可以在运行时检测表达式是左值还是右值。(我通过这个 StackOverflow 问题发现:Rvalues in C++03

这是在运行时工作的演示

(这是我在考虑我最近提出的另一个问题时出现的一个更基本的问题。对此的答案可能有助于我们回答另一个问题。)

现在我已经说明了问题,在编译时测试 C++03 中的右值性,我将谈谈我迄今为止一直在尝试的事情。

我希望能够在compile-time进行此检查。在 C++11 中这很容易,但我对 C++03 很好奇。

我正在尝试以他们的想法为基础,但也会对不同的方法持开放态度。他们技术的基本思想是将这段代码放入一个宏中:

true ? rvalue_probe() : EXPRESSION;

左边是“真” ?,因此我们可以确定 EXPRESSION 永远不会被计算。但有趣的是,?:运算符的行为取决于其参数是左值还是右值(单击上面的链接了解详细信息)。特别是,它将rvalue_probe以两种方式之一转换我们的对象,具体取决于 EXPRESSION 是否为左值:

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,选择两个重载运算符中的哪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

}

(最后我在这里有一些其他代码,但这只是令人困惑的事情。你真的不想看到我在答案中失败的尝试!上面的代码演示了它如何在运行时测试左值与右值。)

4

2 回答 2

8

这需要一些努力,但这是一个经过测试且可以正常工作的is_lvalue宏,可以正确处理const struct S函数返回类型。它依赖于const struct S不绑定到的右值const 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 类右值在某些编译器上给出了一个硬错误,而在其他编译器上给出了正确的结果(假)。

于 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 回答