constexpr, static_assert, y en línea

Previamente pregunté sobre la sobrecarga de funciones en función de si los argumentos son constexpr . Estoy tratando de evitar la respuesta decepcionante a esa pregunta para hacer una función de afirmación más inteligente. Esto es más o menos lo que estoy tratando de hacer:

 inline void smart_assert (bool condition) { if (is_constexpr (condition)) static_assert (condition, "Error!!!"); else assert (condition); } 

Básicamente, la idea es que una verificación en tiempo de comstackción siempre es mejor que una verificación en tiempo de ejecución si es posible verificar en tiempo de comstackción. Sin embargo, debido a cosas como la alineación y el plegado constante, no siempre puedo saber si es posible realizar una verificación del tiempo de comstackción. Esto significa que puede haber casos en los que assert (condition) compile para assert(false) y el código solo está esperando que lo ejecute y ejecute esa ruta antes de que descubra que hay un error.

Por lo tanto, si hubiera alguna forma de verificar si la condición es una constexpr (debido a las optimizaciones en línea u otras), podría llamar a static_assert cuando sea posible, y recurrir a una static_assert de tiempo de ejecución de lo contrario. Afortunadamente, gcc tiene el intrínseco __builtin_constant_p (exp) , que devuelve true si exp es un constexpr. No sé si otros comstackdores tienen este intrínseco, pero esperaba que esto solucionara mi problema. Este es el código que se me ocurrió:

 #include  #undef IS_CONSTEXPR #if defined __GNUC__ #define IS_CONSTEXPR(exp) __builtin_constant_p (exp) #else #define IS_CONSTEXPR(exp) false #endif // TODO: Add other compilers inline void smart_assert (bool const condition) { static_assert (!IS_CONSTEXPR(condition) or condition, "Error!!!"); if (!IS_CONSTEXPR(condition)) assert (condition); } #undef IS_CONSTEXPR 

El static_assert basa en el comportamiento de cortocircuito de or . Si IS_CONSTEXPR es verdadero, entonces se puede usar static_assert , y la condición es !true or condition , que es lo mismo que condition justa. Si IS_CONSTEXPR es falso, entonces static_assert no se puede usar, y la condición es !false or condition , que es exactamente la misma que true y la static_assert se ignora. Si el static_assert no se puede verificar porque la condition no es una constexpr, entonces agrego una aserción de tiempo de ejecución a mi código como un esfuerzo de último momento. Sin embargo, esto no funciona, gracias a que no se pueden usar argumentos de función en static_assert , incluso si los argumentos son constexpr .

En particular, esto es lo que sucede si bash comstackr con gcc:

 // main.cpp int main () { smart_assert (false); return 0; } 

g++ main.cpp -std=c++0x -O0

Todo está bien, se comstack normalmente. No hay inline sin optimización, por lo que IS_CONSTEXPR es falso y static_assert se ignora, por lo que solo obtengo una statement de afirmación en tiempo de ejecución (que falla). Sin embargo,

 [david@david-desktop test]$ g++ main.cpp -std=c++0x -O1 In file included from main.cpp:1:0: smart_assert.hpp: In function 'void smart_assert(bool)': smart_assert.hpp:12:3: error: non-constant condition for static assertion smart_assert.hpp:12:3: error: 'condition' is not a constant expression 

Tan pronto como static_assert optimizaciones y, por lo tanto, potencialmente, permito que static_assert , falla porque no puedo usar los argumentos de la función en static_assert . ¿Hay alguna forma de static_assert esto (incluso si eso significa implementar mi propio static_assert )? Creo que, en teoría, mis proyectos de C ++ podrían beneficiarse bastante de una statement de afirmación más inteligente que detecte los errores lo antes posible.

No parece que hacer de smart_assert una macro similar a una función solucione el problema en el caso general. Obviamente, lo hará funcionar en este ejemplo simple, pero la condition puede provenir de una función de dos niveles en el gráfico de llamadas (pero el comstackdor todavía lo conoce como un constexpr debido a la inclinación), que se encuentra con el mismo problema de usar un Parámetro de la función en un static_assert .

Esto debería ayudarte a empezar

 template constexpr typename remove_reference::type makeprval(T && t) { return t; } #define isprvalconstexpr(e) noexcept(makeprval(e)) 

Lo explícito es bueno, lo implícito es malo, en general.

El progtwigdor siempre puede probar un static_assert .

Si la condición no se puede evaluar en el momento de la comstackción, entonces eso falla, y el progtwigdor necesita cambiar para assert .

Puede hacerlo más fácil al proporcionar una forma común para que el cambio se reduzca a, por ejemplo, STATIC_ASSERT( x+x == 4 )DYNAMIC_ASSERT( x+x == 4 ) , solo un cambio de nombre.

Dicho esto, ya que en su caso solo desea una optimización del tiempo del progtwigdor si esa optimización está disponible , es decir, ya que presumiblemente no le importa obtener los mismos resultados siempre con todos los comstackdores, siempre puede intentar algo como …

 #include  using namespace std; void foo( void const* ) { cout << "compile time constant" << endl; } void foo( ... ) { cout << "hm, run time,,," << endl; } #define CHECK( e ) cout << #e << " is "; foo( long((e)-(e)) ) int main() { int x = 2134; int const y = 2134; CHECK( x ); CHECK( y ); } 

Si lo hace, háganos saber cómo funcionó.

Nota: el código anterior produce resultados diferentes con MSVC 10.0 y g ++ 4.6.


Actualización : Me pregunté cómo el comentario sobre cómo funciona el código anterior tiene tantos votos positivos. Pensé que tal vez él está diciendo algo que simplemente no entiendo. Así que me puse a hacer el trabajo del OP, comprobando cómo le fue a la idea.

En este punto, creo que si se puede hacer que la función constexpr funcione con g ++, también es posible resolver el problema para g ++, de lo contrario, solo para otros comstackdores.

Lo anterior es todo lo que tengo con el soporte de g ++. Esto funciona bien (resuelve el problema del OP) para Visual C ++, usando la idea que presenté. Pero no con g ++:

 #include  #include  using namespace std; #ifdef __GNUC__ namespace detail { typedef double (&Yes)[1]; typedef double (&No)[2]; template< unsigned n > Yes foo( char const (&)[n] ); No foo( ... ); } // namespace detail #define CASSERT( e ) \ do { \ char a[1 + ((e)-(e))]; \ enum { isConstExpr = sizeof( detail::foo( a ) ) == sizeof( detail::Yes ) }; \ cout << "isConstExpr = " << boolalpha << !!isConstExpr << endl; \ (void)(isConstExpr? 1/!!(e) : (assert( e ), 0)); \ } while( false ) #else namespace detail { struct IsConstExpr { typedef double (&YesType)[1]; typedef double (&NoType)[2]; static YesType check( void const* ); static NoType check( ... ); }; } // namespace detail #define CASSERT( e ) \ do { \ enum { isConstExpr = \ (sizeof( detail::IsConstExpr::check( e - e ) ) == \ sizeof( detail::IsConstExpr::YesType )) }; \ (void)(isConstExpr? 1/!!(e) : (assert( e ), 0)); \ } while( false ) #endif int main() { #if defined( STATIC_TRUE ) enum { x = true }; CASSERT( x ); cout << "This should be displayed, OK." << endl; #elif defined( STATIC_FALSE ) enum { x = false }; CASSERT( x ); cerr << "!This should not even have compiled." << endl; #elif defined( DYNAMIC_TRUE ) bool x = true; CASSERT( x ); cout << "This should be displayed, OK." << endl; #elif defined( DYNAMIC_FALSE ) bool x = false; CASSERT( x ); cout << "!Should already have asserted." << endl; #else #error "Hey, u must define a test case symbol." #endif } 

Ejemplo del problema con g ++:

 [D: \ dev \ test]
 > g ++ foo.cpp -Werror = div-by-zero -D DYNAMIC_FALSE

 [D: \ dev \ test]
 > a
 isConstExpr = true
 ! Ya debería haber afirmado.

 [D: \ dev \ test]
 > _

Es decir, g ++ informa (incluso a través de su función intrínseca, e incluso si crea VLA o no) que una variable no constante de la que sabe el valor es constante, pero luego no aplica ese conocimiento para la división de enteros. para que luego no produzca advertencia.

Argh.


Actualización 2 : Bueno, yo soy tonto: por supuesto, la macro solo puede agregar una assert ordinaria para tenerla en cualquier caso. Dado que el OP solo está interesado en obtener la aserción estática cuando está disponible , lo que no es para g ++ en algunos casos de esquina. Problema resuelto, y fue resuelto originalmente.

¿Cómo es la respuesta a la otra pregunta decepcionante? Implementa casi exactamente lo que describe actualmente, excepto por la forma en que el comstackdor imprime el texto en el mensaje de diagnóstico.

La razón por la que se debe hacer con throw es que la evaluación en tiempo de constexpr de constexpr en un contexto que podría evaluarse en tiempo de ejecución es opcional. Por ejemplo, la implementación podría elegir permitirle recorrer el código constexpr en modo de depuración.

constexpr es un atributo débil de funciones (especificador de statement) que no puede cambiar el valor resultante de una expresión usando la función. Es una garantía de que el significado semántico en el tiempo de ejecución se fija en el tiempo de comstackción, pero no le permite especificar un acceso directo especial en tiempo de comstackción.

En cuanto a marcar condiciones no válidas, throw es una subexpresión que no es válida como una expresión constante, excepto cuando está oculta en el lado no evaluado de ?: , && , o || . El lenguaje garantiza que esto se marcará en el momento de la comstackción, incluso si el depurador le permite recorrerlo en el tiempo de ejecución, y solo si la bandera está realmente activada.

El lenguaje consigue las cosas aquí. Desafortunadamente, eso no se puede reconciliar con la función de mensaje de diagnóstico especial de static_assert o se ramifica en constexpr .