C++03.在编译时测试右值与左值,而不仅仅是在运行时

C++03. Test for rvalue-vs-lvalue at compile-time, not just at runtime

本文关键字:不仅仅是 运行时 测试 C++03 编译      更新时间:2023-10-16

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

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

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

既然我已经阐明了这个问题,在编译时测试C++03中的右值,我将谈谈到目前为止我一直在尝试的事情

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

我正试图建立在他们的想法之上,但也会对不同的方法持开放态度。他们技术的基本思想是将这些代码放入宏中:

true ? rvalue_probe() : EXPRESSION;

它在?的左边是"true",因此我们可以确信表达式永远不会被求值。但有趣的是,?:运算符的行为不同,这取决于它的参数是左值还是右值(点击上面的链接了解详细信息)。特别是,它将以两种方式之一转换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"; }
};

这在运行时有效,因为抛出的文本可以被捕获并用于分析表达式是左值还是右值。但我希望在编译时用某种方法来识别正在使用的转换。

现在,这可能是有用的,因为这意味着,而不是询问

表达式是右值吗?

我们可以问:

编译器何时编译true?rvalue_probe():表达式,选择了两个重载运算符operator Xoperator X&中的哪一个?

(通常,您可以通过更改返回类型并获取sizeof来检测调用了哪个方法。但我们无法使用这些转换运算符来检测,尤其是当它们隐藏在?:中时。)

我想我可以用之类的东西

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

如果表达式是左值,则选择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
}

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

这花了一些精力,但这里有一个经过测试且可以正常工作的is_lvalue宏,它可以正确处理const struct S函数返回类型。它依赖于不与const volatile struct S&绑定的const 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)。

运算符(&)的地址只能与左值一起使用。因此,如果您在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

(警告:未测试)

我想不出任何方法来测试在调用方上下文中有效的任意表达式,而不会在失败时中断编译。