c++ - and - Error constexpr en tiempo de compilación, pero sin sobrecarga en tiempo de ejecución
constexpr in c++ (5)
Sin embargo, si el argumento de f no es
constexpr
, arrojará una excepción en el tiempo de ejecución six == 0
, lo que puede no ser siempre deseable por razones de rendimiento.
Un argumento de función nunca se considera una expresión constante. La distinción requeriría que los objetos de tiempo de compilación y tiempo de ejecución tengan diferentes tipos.
A pesar de que el compilador está utilizando semántica puramente funcional cuando evalúa la función en el momento de la compilación, sigue siendo la misma función con el mismo significado. Si desea otra función de significado similar pero diferente, tendrá que definir otra función completa o crear una plantilla.
Podrías usar una firma como esta:
template< typename int_type >
constexpr int f(int_type x);
con llamadas como esta:
f( std::integral_constant< int, 0 >() ) // Error.
f( std::integral_constant< int, 3 >() ) // OK.
f( 0 ) // Not checked.
La metaprogramación puede indicar que integral_constant
significa un valor de tiempo de compilación. Pero no creo que sea realmente apropiado. Si un sentido de la función funciona con cero y el otro no, entonces tiene dos funciones diferentes.
Un lenguaje de envoltura podría evitar la duplicación entre las diferentes funciones:
constexpr int f_impl(int x) { // Actual guts of the function.
return x;
}
int f(int x) { // Non-constexpr wrapper prevents accidental compile-time use.
assert ( x != 0 && "Zero not allowed!" );
return f_impl( x );
}
template< int x > // This overload handles explicitly compile-time values.
constexpr int f( std::integral_constant< int, x > ) {
static_assert ( x != 0, "Zero not allowed!" );
return f_impl( x );
}
Hay un truco bien conocido para causar un error en tiempo de compilación en la evaluación de una función constexpr
al hacer algo como esto:
constexpr int f(int x) {
return (x != 0) ? x : throw std::logic_error("Oh no!");
}
Y si la función se utiliza en un contexto constexpr
, obtendrá un error en tiempo de compilación si x == 0
. Sin embargo, si el argumento de f
no es constexpr
, arrojará una excepción en el tiempo de ejecución si x == 0
, lo que puede no ser siempre deseable por razones de rendimiento.
Similar a la teoría de assert
está protegido por NDEBUG
, ¿hay una manera de causar un error en tiempo de compilación con una función constexpr
, pero no hacer nada en tiempo de ejecución?
Finalmente, ¿las reglas constexpr
relajadas en C ++ 1y (C ++ 14) cambian algo?
En lugar de usar una función constexpr
, debe usar static_assert
. Esto le permite ejecutar una aserción en tiempo de compilación que tiene un costo de tiempo de ejecución cero.
Esto parece hacer el truco. No es muy bonito, pero la idea es distinguir el valor que está disponible en tiempo de compilación de uno que no usa SFINAE en constexprs. Puedo compilar esto con el Clang 3.3 y hacer que falle la compilación cuando intento usar f (0) en un contexto constexpr, pero no lanzar cuando lo uso en tiempo de ejecución. Probablemente puede crear una sobrecarga de un parámetro que puede hacer que el truco preferido no sea interno a su implementación.
struct not_preferred {};
struct preferred { operator not_preferred() { return not_preferred(); } };
template< typename T, T X >
T compiletime() { return X; }
template< typename T >
constexpr auto maybethrow( preferred, T x ) -> decltype( compiletime< T, x >() )
{
return 0 ? x : throw 1;
}
template< typename T >
constexpr auto maybethrow( not_preferred, T x ) -> T
{
return x;
}
constexpr int f(int x)
{
return x ? x + 1 : maybethrow( preferred(), x + 1 );
}
Esto debería funcionar:
#ifdef NDEBUG
// Suppresses unused variable warnings in release builds.
#define ASSERT(X) (void(sizeof (X)))
#else
#define ASSERT(X) ((X) ? void() : std::abort())
#endif
constexpr int f(int const x)
{
return ASSERT(x != 0), x;
}
Puedes ver la salida here . Si agrega constexpr
al inicio de la línea 17, obtendrá un error en tiempo de compilación.
¿Hay una manera de causar un error en tiempo de compilación con una función constexpr
, pero no hacer nada en tiempo de ejecución?
Puede usar el mismo truco exacto, pero en lugar de usar una expresión de lanzamiento , use una expresión que no sea una expresión constante pero que haga lo que quiere en tiempo de ejecución. Por ejemplo:
int runtime_fallback(int x) { return x; } // note, not constexpr
constexpr int f(int x) {
return (x != 0) ? x : runtime_fallback(0);
}
constexpr int k1 = f(1); // ok
constexpr int k2 = f(0); // error, can''t call ''runtime_fallback'' in constant expression
int k3 = f(0); // ok
¿ constexpr
reglas constexpr
relajadas en C ++ 1y (C ++ 14) cambian algo?
No en esta área, no. Hay algunas formas de expresión que son válidas en expresiones constantes en C ++ 14 pero no en C ++ 11, pero no hay expresiones de lanzamiento ni llamadas a funciones que no sean constexpr
en esa lista.