¿Es posible is_constexpr en C ++ 11?

¿Es posible producir un valor booleano en tiempo de comstackción en función de si una expresión C ++ 11 es una expresión constante (es decir, constexpr ) en C ++ 11? Algunas preguntas sobre SO se relacionan con esto, pero no veo una respuesta directa en ninguna parte.

A partir de 2017, is_constexpr no es posible en C ++ 11. Eso suena raro, así que déjame explicarte un poco de la historia.

Primero, agregamos esta característica para resolver un defecto: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1129

Johannes Schaub – litb publicó una macro de detección de constexpr que se basó en la disposición de que las expresiones constantes son implícitamente noexcept. Esto funcionó en C ++ 11, pero nunca fue implementado por al menos algunos comstackdores (por ejemplo, clang). Luego, como parte de C ++ 17, evaluamos la eliminación de las especificaciones de excepciones obsoletas de C ++ 17 . Como efecto secundario de esa redacción, eliminamos accidentalmente esa disposición. Cuando el Grupo de trabajo central discutió la posibilidad de volver a incluir la disposición, se dieron cuenta de que había algunos problemas serios al hacerlo. Puede ver todos los detalles en el informe de errores LLVM . Entonces, en lugar de volver a agregarlo, decidimos considerarlo como un defecto contra todas las versiones de estándares y eliminarlo retroactivamente .

El efecto de esto es que, a mi conocimiento, no hay manera de detectar si una expresión se puede usar como una expresión constante.

Una vez lo escribí (EDITAR: ver a continuación las limitaciones y explicaciones). Desde https://stackoverflow.com/a/10287598/34509 :

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

Sin embargo, hay muchos tipos de expresiones constantes. La respuesta anterior detecta expresiones de constante prvalue.


Explicación

La noexcept(e) da false iff e contiene

  • una llamada potencialmente evaluada a una función que no tiene una especificación de excepción sin lanzamiento a menos que la llamada sea una expresión constante,
  • una expresión de throw potencialmente evaluada,
  • una forma arrojadiza potencialmente evaluada de dynamic_cast o typeid .

Tenga en cuenta que la plantilla de función makeprval no está declarada como noexcept , por lo que la llamada debe ser una expresión constante para que la primera viñeta no se aplique, y esto es lo que abusamos. Necesitamos que las otras balas no se apliquen también, pero, gracias a Dios, tanto un throw como un throw dynamic_cast o un dynamic_cast arrojadizos typeid están permitidos en expresiones constantes, así que está bien.

Limitaciones

Lamentablemente, existe una limitación suble, que puede o no ser importante para usted. La noción de “potencialmente evaluado” es mucho más conservadora que los límites de las expresiones constantes que se aplican. Entonces el anterior noexcept puede dar falsos negativos. Informará que algunas expresiones no son expresiones constantes prvalue, aunque sí lo son. Ejemplo:

 constexpr int a = (0 ? throw "fooled!" : 42); constexpr bool atest = isprvalconstexpr((0 ? throw "fooled!" : 42)); 

En el atest anterior, es falso, aunque la inicialización de a tuvo éxito. Esto se debe a que, por ser una expresión constante, basta con que las subexpresiones no constantes “malvadas” “nunca se evalúen”, aunque esas sub-expresiones malvadas sean potencialmente evaluadas, formalmente.

Sí, esto es posible Una forma de hacerlo (que es válida incluso con los cambios noexcept recientes) es aprovechar las reglas de conversión de angostamiento de C ++ 11:

Una conversión de estrechamiento es una conversión […] implícita de un tipo entero o no enumerado a un tipo entero que no puede representar todos los valores del tipo original, excepto cuando el origen es una expresión constante cuyo valor después de las promociones integrales cabrá en el tipo de destino.

(énfasis mío). La inicialización de listas generalmente no permite el estrechamiento de conversiones, y cuando se combina con SFINAE, podemos crear gadgets para detectar si una expresión arbitraria es una expresión constante:

 // p() here could be anything template std::true_type is_constexpr_impl(decltype(int{(p(), 0U)})); template std::false_type is_constexpr_impl(...); template using is_constexpr = decltype(is_constexpr_impl

(0)); constexpr int f() { return 0; } int g() { return 0; } static_assert(is_constexpr()); static_assert(!is_constexpr());

Demostración en vivo

La clave aquí es que int{(expr, 0U)} contiene una conversión de estrechamiento desde unsigned int a int (y por lo tanto está mal formado), a menos que expr sea ​​una expresión constante, en cuyo caso toda la expresión (expr, 0U) es una expresión constante cuyo valor evaluado se ajusta al tipo int .

La siguiente es una implementación de is_constexpr para funciones , no para expresiones arbitrarias, para C ++ 11 y C ++ 17. Sin embargo, requiere que los argumentos de la función que desea probar sean constructibles por defecto.

 #include  struct A {}; // don't make it too easy, use a UDT A f1(A a) { return a; } // is_constexpr -> false constexpr A f2(A a) { return a; } // is_constexpr -> true // The following turns anything (in our case a value of A) into an int. // This is necessary because non-type template arguments must be integral // (likely to change with C++20). template  constexpr int make_int(T &&) { return 0; } // Helper to turn some function type (eg int(float)) into a function // pointer type (eg int (*)(float)). template  struct signature_from; template  struct signature_from { using type = R(*)(Args...); }; // See std::void_t for the idea. This does it for ints instead of types. template  using void_from_int = void; // The fallback case: F is not a function pointer to a constexpr function template ::type F, class = void_from_int<>> struct is_constexpr { static constexpr bool value = false; }; // If void_from_int doesn't lead to a substitution // failure, then this is the preferred specialization. In that case F must // be a function pointer to a constexpr function. If it is not, it could // not be used in a template argument. template ::type F> struct is_constexpr> { static constexpr bool value = true; }; // proof that it works: static_assert(!is_constexpr::value, ""); static_assert( is_constexpr::value, ""); #if __cplusplus >= 201703 // with C++17 the type of the function can be deduced: template struct is_constexpr2 : is_constexpr, F> {}; static_assert(!is_constexpr2::value, ""); static_assert( is_constexpr2::value, ""); #endif 

Véalo en acción en https://godbolt.org/g/rdeQme .

Hagamos un juego ingenuo con el modismo SFINAE :

 template  struct IsConstExpr { typedef char yes; typedef char no[2]; template  static constexpr yes& swallow(T) { int x[T()]; return 0; }; template  static no& swallow(...); static const int value = sizeof(swallow(0)) == sizeof(yes); }; 

El código anterior es sintácticamente incorrecto, pero nos dará una idea. Tratemos de usarlo:

 constexpr int f() { return 32167; } int g() { return 32167; } int main() { std::cout << IsConstExpr::value << std::endl; } 

El comstackdor dice:

 In instantiation of 'static constexpr IsConstExpr::yes& IsConstExpr::swallow(T) [with T = int (*)(); C = int (*)(); IsConstExpr: :yes = char]': 

Ahora el problema es obvio: el parámetro de la plantilla es T = int (*)();

Significa que constexpr no es parte del tipo y no podemos detectarlo .