C++ 03.在编译时测试rvalue-vs-lvalue,而不仅仅是在运行时

Aar*_*aid 22 c++ rvalue language-lawyer c++03

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

这是一个在运行时工作演示

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

现在我已经详细说明了这个问题,在编译时在C++ 03中测试rvalue-ness,我会谈谈到目前为止我一直在尝试的事情.

我希望能够在编译时进行此检查.在C++ 11中很容易,但我对C++ 03很好奇.

我正在努力建立他们的想法,但也会对不同的方法持开放态度.他们的技术的基本思想是将此代码放入一个宏:

true ? rvalue_probe() : EXPRESSION;
Run Code Online (Sandbox Code Playgroud)

它的左侧是'true' ?,因此我们可以确定永远不会评估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"; }
};
Run Code Online (Sandbox Code Playgroud)

这在运行时有效,因为可以捕获抛出的文本并用于分析EXPRESSION是左值还是右值.但我希望在编译时能够识别正在使用的转换.

现在,这可能是有用的,因为它意味着,而不是要求

EXPRESSION是一个右值?

我们可以问:

当编译器编译为true时?rvalue_probe():EXPRESSION,其两个重载运营商,operator X或者operator X&,选择?

(通常,您可以通过更改返回类型并获取sizeof它来检测调用哪个方法.但是我们不能对这些转换运算符执行此操作,尤其是当它们被隐藏在内部时?:.)

我以为我可以使用类似的东西

is_reference< typeof (true ? rvalue_probe() : EXPRESSION) > :: type
Run Code Online (Sandbox Code Playgroud)

如果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

}
Run Code Online (Sandbox Code Playgroud)

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

小智 7

它需要一些努力,但这是一个经过测试和工作的is_lvalue宏,可以正确处理const struct S函数返回类型.它依赖于const struct S不绑定的rvalues const volatile struct S&,而const struct Slvalues则依赖于rvalues .

#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>();
}
Run Code Online (Sandbox Code Playgroud)

编辑:删除了不必要的额外参数,谢谢Xeo.

再次编辑:根据评论,这适用于GCC但依赖于C++ 03中的未指定行为(它是有效的C++ 11)并且使其他编译器失败.额外参数已恢复,这使其在更多情况下可用.const类rvalues在某些编译器上给出了一个硬错误,并在其他编译器上给出了正确的结果(false).