and - constexpr in c++
¿Es posible is_constexpr en C++ 11? (5)
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 compiladores (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.
¿Es posible producir un valor booleano en tiempo de compilació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.
Hagamos un juego ingenuo con el modismo SFINAE :
template <typename C> struct IsConstExpr
{
typedef char yes;
typedef char no[2];
template <typename T> static constexpr yes& swallow(T) { int x[T()]; return 0; };
template <typename T> static no& swallow(...);
static const int value = sizeof(swallow<C>(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<decltype(&f)>::value << std::endl;
}
El compilador dice:
In instantiation of ''static constexpr IsConstExpr<C>::yes& IsConstExpr<C>::swallow(T) [with T = int (*)(); C = int (*)(); IsConstExpr<C>:
: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 .
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 <type_traits>
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 <class T> constexpr int make_int(T &&) { return 0; }
// Helper to turn some function type (e.g. int(float)) into a function
// pointer type (e.g. int (*)(float)).
template <class T> struct signature_from;
template <class R, class... Args> struct signature_from<R(Args...)> {
using type = R(*)(Args...);
};
// See std::void_t for the idea. This does it for ints instead of types.
template <int...> using void_from_int = void;
// The fallback case: F is not a function pointer to a constexpr function
template <class T, typename signature_from<T>::type F, class = void_from_int<>>
struct is_constexpr {
static constexpr bool value = false;
};
// If void_from_int<make_int(F(Args()...))> 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 <class R, class... Args, typename signature_from<R(Args...)>::type F>
struct is_constexpr<R(Args...), F, void_from_int<make_int(F(Args()...))>>
{
static constexpr bool value = true;
};
// proof that it works:
static_assert(!is_constexpr<A(A), f1>::value, "");
static_assert( is_constexpr<A(A), f2>::value, "");
#if __cplusplus >= 201703
// with C++17 the type of the function can be deduced:
template<auto F> struct is_constexpr2 : is_constexpr<std::remove_pointer_t<decltype(F)>, F> {};
static_assert(!is_constexpr2<f1>::value, "");
static_assert( is_constexpr2<f2>::value, "");
#endif
Véalo en acción en https://godbolt.org/g/rdeQme .
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<int (*p)()> std::true_type is_constexpr_impl(decltype(int{(p(), 0U)}));
template<int (*p)()> std::false_type is_constexpr_impl(...);
template<int (*p)()> using is_constexpr = decltype(is_constexpr_impl<p>(0));
constexpr int f() { return 0; }
int g() { return 0; }
static_assert(is_constexpr<f>());
static_assert(!is_constexpr<g>());
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
.
Una vez lo escribí (EDITAR: ver a continuación las limitaciones y explicaciones). Desde https://.com/a/10287598/34509 :
template<typename T>
constexpr typename remove_reference<T>::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
otypeid
.
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.