С++ 03. Тест для rvalue-vs-lvalue во время компиляции, а не только во время выполнения
В С++ 03, Boost Foreach, используя этот интересный метод, во время выполнения может обнаруживать, является ли выражение lvalue или rvalue. (Я нашел, что через этот вопрос StackOverflow: Rvalues в С++ 03)
Здесь демонстрация этой работы во время выполнения
(Это более простой вопрос, возникший в то время, когда я думал об этом другом недавнем вопросе моего вопроса. Ответ на этот вопрос может помочь нам ответить на этот другой вопрос. )
Теперь, когда я изложил вопрос, тестируя rvalue-ness в С++ 03 во время компиляции, я расскажу немного о том, что я пытался до сих пор.
Я хочу, чтобы сделать эту проверку во время компиляции. Это легко в С++ 11, но мне интересно, как С++ 03.
Я пытаюсь опираться на их идею, но также буду открыт для разных подходов. Основная идея их метода состоит в том, чтобы превратить этот код в макрос:
true ? rvalue_probe() : EXPRESSION;
Это "истина" слева от ?
, и поэтому мы можем быть уверены, что 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 значением r?
мы можем спросить:
Когда компилятор компилирует true? rvalue_probe(): EXPRESSION, какой из двух перегруженных операторов, operator X
или operator X&
, выбран?
(Обычно вы можете определить, какой метод был вызван, изменив типы возвращаемых данных и получив sizeof
его. Но мы не можем делать этого с этими операторами преобразования, особенно когда они зарыты внутри ?:
. )
Я думал, что смогу использовать что-то вроде
is_reference< typeof (true ? rvalue_probe() : EXPRESSION) > :: type
Если EXPRESSION является lvalue, тогда выбирается operator&
, и я надеялся, что все выражение будет тогда &
. Но, похоже, это не работает. типы ссылок и типы 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-versus-rvalue во время выполнения.)
Ответы
Ответ 1
Требуется некоторое усилие, но здесь протестированный и рабочий макрос is_lvalue
, который правильно обрабатывает типы возвращаемой функции const struct S
. Он полагается на const struct S
rvalues, не привязывающий к const volatile struct S&
, тогда как 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>();
}
Изменить: лишний дополнительный параметр удален, спасибо Xeo.
Изменить снова: согласно комментариям, это работает с GCC, но полагается на неуказанное поведение в С++ 03 (это действительно С++ 11) и не удается выполнить некоторые другие компиляторы. Дополнительный параметр восстановлен, что заставляет его работать в большем количестве случаев. const class rvalues дают жесткую ошибку для некоторых компиляторов и дают правильный результат (false) для других.
Ответ 2
Адрес-оператора (&
) может использоваться только с 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
(Предупреждение: не проверено)
Я не могу придумать никакого способа проверить произвольное выражение, действующее в контексте вызывающего, без нарушения компиляции при сбое.