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

发布于 2025-01-01 07:09:42 字数 3942 浏览 3 评论 0原文

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

这是一个运行时工作的演示<​​/a>

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

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

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

我正在尝试以他们的想法为基础,但也愿意接受不同的方法。他们技术的基本思想是将此代码放入宏中:

true ? rvalue_probe() : EXPRESSION;

? 左侧的值为“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"; }
};

这在运行时有效,因为可以捕获抛出的文本并用于分析 EXPRESSION 是否为左值。是左值或右值。但我想要某种方法来在编译时识别正在使用哪种转换。

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

EXPRESSION 是右值吗?

我们可以问:

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

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

我想我也许可以使用类似的东西

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

If the EXPRESSION is an lvalue, then the operator& is selected 并且我希望整个表达式将是一个<代码>& 类型。但这似乎不起作用。引用类型和非引用类型很难(不可能?)区分,特别是现在我试图深入 ?: 表达式以查看选择了哪种转换。

这是粘贴在这里的演示代码:(

#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

}

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

In C++03, Boost's Foreach, using this interesting technique, can detect at run-time whether an expression is an lvalue or an rvalue. (I found that via this StackOverflow question: Rvalues in C++03 )

Here's a demo of this working at run-time

(This is a more basic question that arose while I was thinking about this other recent question of mine. An answer to this might help us answer that other question.)

Now that I've spelled out the question, testing rvalue-ness in C++03 at compile-time, I'll talk a little about the things I've been trying so far.

I want to be able to do this check at compile-time. It's easy in C++11, but I'm curious about C++03.

I'm trying to build upon their idea, but would be open to different approaches also. The basic idea of their technique is to put this code into a macro:

true ? rvalue_probe() : EXPRESSION;

It is 'true' on the left of the ?, and therefore we can be sure that EXPRESSION will never be evaluated. But the interesting thing is that the ?: operator behaves differently depending on whether its parameters are lvalues or rvalues (click that link above for details). In particular, it will convert our rvalue_probe object in one of two ways, depending on whether EXPRESSION is an lvalue or not:

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"; }
};

That works at runtime because the thrown text can be caught and used to analyze whether the EXPRESSION was an lvalue or an rvalue. But I want some way to identify, at compile-time, which conversion is being used.

Now, this is potentially useful because it means that, instead of asking

Is EXPRESSION an rvalue?

we can ask:

When the compiler is compiling true ? rvalue_probe() : EXPRESSION, which of the two overloaded operators, operator X or operator X&, is selected?

( Ordinarily, you could detect which method was called by changing the return types and getting the sizeof it. But we can't do that with these conversion operators, especially when they're buried inside the ?:. )

I thought I might be able to use something like

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

If the EXPRESSION is an lvalue, then the operator& is selected and I hoped that the whole expression would then be a & type. But it doesn't seem to work. ref types and non-ref types are pretty hard (impossible?) to distinguish, especially now that I'm trying to dig inside a ?: expression to see which conversion was selected.

Here's the demo code pasted here:

#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

}

(I had some other code here at the end, but it's just confusing things. You don't really want to see my failed attempts at an answer! The above code demonstrates how it can test lvalue-versus-rvalue at runtime.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

风情万种。 2025-01-08 07:09:42

这需要付出一些努力,但这里有一个经过测试且有效的 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 类右值在某些编译器上给出硬错误,而在其他编译器上给出正确的结果(假)。

It took some effort, but here's a tested and working is_lvalue macro that correctly handles const struct S function return types. It relies on const struct S rvalues not binding to const volatile struct S&, while const struct S lvalues do.

#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>();
}

Edit: unnecessary extra parameter removed, thanks Xeo.

Edit again: As per the comments, this works with GCC but relies on unspecified behaviour in C++03 (it's valid C++11) and fails some other compilers. Extra parameter restored, which makes it work in more cases. const class rvalues give a hard error on some compilers, and give the correct result (false) on others.

第几種人 2025-01-08 07:09:42

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

(警告:未测试)

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

The address-of operator (&) can only be used with an lvalue. So if you used it in an SFINAE test, you could distinguish at compile-time.

A static assertion could look like:

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

A trait version might be:

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 };
};

and could be used like

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

(Warning: not tested)

I can't think of any way to test an arbitrary expression valid in the caller's context, without breaking compilation on failure.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文