【问题标题】:C++03. Test for rvalue-vs-lvalue at compile-time, not just at runtimeC++03.在编译时测试 rvalue-vs-lvalue,而不仅仅是在运行时
【发布时间】:2012-02-23 11:23:50
【问题描述】:

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

这是demo of this working at run-time

(这是我在考虑other recent question of mine 时提出的一个更基本的问题。对此的回答可能有助于我们回答其他问题。)

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

我希望能够在编译时进行此检查。在 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 Xoperator X&amp;这两个重载运算符中的哪一个?

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

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

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

如果 EXPRESSION 是左值,则选择 operator&amp;,我希望整个表达式将是 &amp; 类型。但这似乎不起作用。 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-versus-rvalue 在运行时。)

【问题讨论】:

  • @hvd,我已经相应地更新了问题的结尾。我应该说fooref,而不是foo。但无论如何,typeof 仍然认为true ?: fooref() : fooref() 不是 引用。
  • @hvd, ...我还检查了typeof(x)typeof(xr),它们给出的类型相同,这对我来说没有意义。 typeof 中似乎存在不一致之处。我使用typeof(int&amp;) 得到了正确的行为,但使用typeof(xr) 却没有做同样的事情。
  • xxr 的类型和左值是相同的,所以我假设这就是typeof 的结果相同的原因。 decltype 的行为略有不同,可能会对您有所帮助,但您的 g++ 版本不支持它。
  • 你试图发明标准的is_lvalue_reference/is_rvalue_reference?或者我错过了什么。
  • @Andrew 现在看到你的留言,我认为问题是“x 是左值”和“x 是参考”是两个不同的问题,在问题中很困惑。

标签: c++ language-lawyer rvalue c++03


【解决方案1】:

这需要一些努力,但这里有一个经过测试且可以正常工作的 is_lvalue 宏,它可以正确处理 const struct S 函数返回类型。它依赖于const struct S 右值不绑定到const volatile struct S&amp;,而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。

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

【讨论】:

  • 别在意我的最后一条评论,由于某种原因,Clang 会导致许多错误...... Clang 在您以前的版本上也有错误,完整的错误显示为here。不过,这可能是一个 Clang 错误。
  • 好的,所有的 comeau、gcc 和 clang 都接受 Xeo 版本的函数左值。那个clang和comeau拒绝代码与C ++ 11的澄清有关。他们澄清说,只有 const non-volatile 引用才能绑定到右值。 C++03 刚刚说过“将对非常量的引用绑定到右值”是不可能的。 C++11 添加了非易失性部分。他们在 C++03 中仍然拒绝它是因为引用绑定本身仍然是错误的,即使重载决议本身接受转换。
  • 为什么会这样?是不是因为右值不应该有任何其他对它们的引用,因此比左值更不“易变”?我以前从未真正关注过volatile - 感谢您激励我阅读它!
  • @JohannesSchaub-litb 这是澄清还是行为改变?如果这段代码在 C++03 模式下与 GCC 一起工作,因为它实现了 C++11 语义,那我不满意:)
  • @Johannes:可以并不意味着必须,所以我认为它的格式很好,除非其他段落说它不是。此外,关于“C++ 03 中的 cv ref”问题:为什么绑定仍然格式错误?
【解决方案2】:

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

(警告:未测试)

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

【讨论】:

  • 地址操作符不能与具有自己operator&amp;函数的类类型的右值一起使用吗?
  • 我喜欢这个。这还不是一个完整的解决方案,但它是正确的。我现在正在对此进行一些试验。
  • @hvd:很难说你想要什么样的结果让一个怪异到足以重载operator&amp; 的类。它可能试图透明地包装一个左值,在这种情况下,这个测试可能应该说它是一个左值。
  • 我想我设想有一个名为 STATIC_ASSERT_IS_LVALUE(x) 的宏,如果 x 不是左值(并且也将评估为 x),它将导致编译失败。接受&amp; 对我来说已经足够了。如果我们可以写出断言,那么我想我会接受这个。使用逗号运算符怎么样? #define STATIC_ASSERT_IS_LVALUE(x) ( (typeof(&amp;x) ) 0 , x)
  • ...最终,如果它做的不是强制编译错误,那就太好了。例如,有人可能想要STATIC_ASSERT_IS_RVALUE。但前者对我来说已经足够了。
猜你喜欢
  • 2022-11-28
  • 2021-09-11
  • 1970-01-01
  • 2016-06-23
  • 1970-01-01
  • 1970-01-01
  • 2020-10-19
  • 1970-01-01
  • 2022-01-25
相关资源
最近更新 更多