C ++ 03. Тест для rvalue-vs-lvalue во время компиляции, а не только во время выполнения - PullRequest
22 голосов
/ 31 января 2012

В C ++ 03 Boost's Foreach, используя этот интересный метод , может обнаружить во время выполнения , является ли выражение lvalue или rvalue. (Я обнаружил, что с помощью этого вопроса StackOverflow: Rvalues ​​в C ++ 03 )

Вот демонстрация этой работы во время выполнения

(Это более простой вопрос, который возник, когда я размышлял над этим другим моим недавним вопросом . Ответ на этот вопрос может помочь нам ответить на этот другой вопрос.)

Теперь, когда я изложил вопрос, проверяя rvalue-ness в C ++ 03 во время компиляции, я немного расскажу о том, что я до сих пор пробовал.

Я хочу сделать эту проверку в время компиляции . Это легко в C ++ 11, но мне любопытно, что такое C ++ 03.

Я пытаюсь опираться на их идею, но буду открыт и для других подходов. Основная идея их техники - поместить этот код в макрос:

true ? rvalue_probe() : EXPRESSION;

Это 'true' слева от ?, и поэтому мы можем быть уверены, что EXPRESSION никогда не будет оцениваться. Но интересно то, что оператор ?: ведет себя по-разному, в зависимости от того, являются ли его параметры lvalues ​​или rvalues ​​(для получения подробной информации нажмите на ссылку выше). В частности, он преобразует наш rvalue_probe объект одним из двух способов, в зависимости от того, является ли EXPRESSION lvalue или нет:

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 lvalue или rvalue. Но я хочу каким-то образом определить во время компиляции, какое преобразование используется.

Теперь, это потенциально полезно, потому что это означает, что вместо того, чтобы спрашивать

EXPRESSION - это значение?

мы можем спросить:

Когда компилятор компилирует true? rvalue_probe (): EXPRESSION , какой из двух перегруженных операторов, operator X или operator X&, выбран?

(Обычно вы можете определить, какой метод был вызван, изменив возвращаемые типы и получив его sizeof. Но мы не можем сделать это с этими операторами преобразования, особенно когда они скрыты внутри ?:. )

Я думал, что смогу использовать что-то вроде

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

Если EXPRESSION является lvalue, тогда выбирается operator&, и я надеялся, что тогда все выражение будет иметь тип &. Но это не похоже на работу. Типы ref и non-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-vs.-rvalue во время выполнения .)

Ответы [ 2 ]

7 голосов
/ 01 февраля 2012

Это потребовало некоторых усилий, но вот протестированный и работающий макрос is_lvalue, который правильно обрабатывает const struct S типы возвращаемых функций.Он полагается на const struct S значения r, не привязанные к const volatile struct S&, в то время как const struct S значения l делают.

#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 класса r приводят к серьезным ошибкам в некоторых компиляторах и дают правильный результат (false) для других.

0 голосов
/ 31 января 2012

Адрес оператора (&) может использоваться только с lvalue.Поэтому, если вы использовали его в тесте 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

(Предупреждение: не проверено)

Я не могу придумать способ проверить произвольное выражение, допустимое в контексте вызывающего,без прерывания компиляции при неудаче.

...