C++ 03. Prueba de rvalue-vs-lvalue en tiempo de compilación, no solo en tiempo de ejecución
language-lawyer c++03 (2)
La dirección de operador ( &
) solo se puede utilizar con un valor l. Entonces, si lo usó en una prueba SFINAE, podría distinguir en tiempo de compilación.
Una aserción estática podría verse como:
#define STATIC_ASSERT_IS_LVALUE(x) ( (sizeof &(x)), (x) )
Una versión de rasgo podría ser:
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 };
};
y podría ser utilizado como
has_lvalue_subscript< std::vector<int> >::value
(Advertencia: no probado)
No se me ocurre ninguna forma de probar una expresión arbitraria válida en el contexto de la persona que llama, sin romper la compilación en caso de fallo.
En C ++ 03, Foreach de Boost, utilizando esta técnica interesante , puede detectar en tiempo de ejecución si una expresión es un valor de l o un valor de r. (Encontré que a través de esta pregunta de StackOverflow: valores en C ++ 03 )
Aquí hay una demostración de esto trabajando en tiempo de ejecución
(Esta es una pregunta más básica que surgió cuando estaba pensando en esta otra pregunta reciente mía . Una respuesta a esto podría ayudarnos a responder esa otra pregunta).
Ahora que he formulado la pregunta, probando el valor en C ++ 03 en tiempo de compilación, hablaré un poco sobre las cosas que he estado tratando hasta ahora.
Quiero poder hacer esta comprobación en tiempo de compilación . Es fácil en C ++ 11, pero tengo curiosidad por C ++ 03.
Estoy tratando de aprovechar su idea, pero también estaría abierto a diferentes enfoques. La idea básica de su técnica es poner este código en una macro:
true ? rvalue_probe() : EXPRESSION;
Es ''verdadero'' a la izquierda de la ?
, y por lo tanto podemos estar seguros de que la EXPRESIÓN nunca será evaluada. Pero lo interesante es que el operador ?:
comporta de manera diferente dependiendo de si sus parámetros son lvalues o rvalues (haga clic en el enlace de arriba para obtener más información). En particular, convertirá nuestro objeto rvalue_probe
de una de las dos maneras, dependiendo de si EXPRESSION es un valor o no:
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"; }
};
Eso funciona en tiempo de ejecución porque el texto arrojado puede capturarse y usarse para analizar si la EXPRESIÓN era un valor o un valor. Pero quiero alguna forma de identificar, en tiempo de compilación, qué conversión se está utilizando.
Ahora, esto es potencialmente útil porque significa que, en lugar de preguntar
¿Es la expresión un valor?
podemos preguntar:
Cuando el compilador se está compilando verdad? rvalue_probe (): EXPRESIÓN , ¿cuál de los dos operadores sobrecargados, el
operator X
o eloperator X&
, se selecciona?
(Normalmente, se podría detectar a qué método se llamó cambiando los tipos de retorno y obteniendo el sizeof
éste. Pero no podemos hacer eso con estos operadores de conversión, especialmente cuando están enterrados dentro del ?:
.)
Pensé que podría ser capaz de usar algo como
is_reference< typeof (true ? rvalue_probe() : EXPRESSION) > :: type
Si la EXPRESIÓN es un lvalor, entonces se selecciona el operator&
y espero que toda la expresión sea un &
tipo. Pero no parece funcionar. Los tipos de referencia y los tipos que no son referenciados son bastante difíciles (¿imposibles?) de distinguir, especialmente ahora que estoy tratando de excavar dentro de una expresión ?:
para ver qué conversión se seleccionó.
Aquí está el código de demostración pegado aquí:
#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
}
(Tenía otro código aquí al final, pero son cosas confusas. ¡Realmente no quieres ver mis intentos fallidos de respuesta! El código anterior demuestra cómo puede probar el valor de lvalue contra rvalue en tiempo de ejecución ).
Tomó un poco de esfuerzo, pero aquí está una macro is_lvalue
probada y en funcionamiento que maneja correctamente los tipos de retorno de la función const struct S
Se basa en los const struct S
no se unen a la const volatile struct S&
, mientras que los valores const struct S
sí lo hacen.
#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>();
}
Edición : parámetro extra innecesario eliminado, gracias Xeo.
Vuelva a editar : según los comentarios, esto funciona con GCC pero se basa en un comportamiento no especificado en C ++ 03 (es válido en C ++ 11) y falla a otros compiladores. Parámetro extra restaurado, lo que hace que funcione en más casos. los valores de clase const dan un error difícil en algunos compiladores y dan el resultado correcto (falso) en otros.