and c++ c++11 overloading compile-time-constant constexpr

c++ - and - sobrecarga constexpr



constexpr in c++ (6)

Relacionado: la función que devuelve constexpr no compila

Siento que el uso de Constexpr es limitado en C ++ 11 debido a la incapacidad de definir dos funciones que de otro modo tendrían la misma firma, pero uno debe ser constestable y el otro no constestable. En otras palabras, sería muy útil si pudiera tener, por ejemplo, un constructor constexpr std :: string que toma solo argumentos constexpr, y un constructor no constexpr std :: string para argumentos no constexpr. Otro ejemplo sería una función teóricamente complicada que podría hacerse más eficiente mediante el uso de estado. No puede hacer eso fácilmente con una función constexpr, por lo que le quedan dos opciones: tener una función constexpr que sea muy lenta si pasa argumentos no constexpr, o renunciar por completo a constexpr (o escribir dos funciones separadas, pero puede que no sepa a qué versión llamar).

Mi pregunta, por lo tanto, es esta:

¿Es posible que una implementación de C ++ 11 compatible con estándares permita la sobrecarga de funciones en base a los argumentos que son constexpr, o esto requeriría actualizar el estándar? Si no está permitido, ¿fue intencionalmente prohibido?

@NicolBolas: Supongamos que tengo una función que mapea una enum a una std::string . La forma más directa de hacerlo, suponiendo que mi enum va de 0 a n - 1 , es crear una matriz de tamaño n llena con el resultado.

Podría crear un static constexpr char const * [] y construir un std::string en return (pagando el costo de crear un objeto std::string cada vez que llamo a la función), o puedo crear un static std::string const [] y devolver el valor que busco, pagando el costo de todos los constructores std::string la primera vez que llamo a la función. Parece que una mejor solución sería crear std::string en memoria en tiempo de compilación (similar a lo que se hace ahora con char const * ), pero la única forma de hacerlo sería alertar al constructor de que tiene constexpr argumentos.

Para un ejemplo que no sea un constructor std::string , creo que es bastante directo encontrar un ejemplo en el que, si pudiera ignorar los requisitos de constexpr (y así crear una función que no sea constexpr ), podría crear un función eficiente. Considere este hilo: pregunta constexpr, ¿por qué estos dos programas diferentes se ejecutan en una cantidad de tiempo tan diferente con g ++?

Si llamo fib con un argumento constexpr , no puedo constexpr mejor que el compilador optimizando la llamada a la función por completo. Pero si llamo a fib con un argumento no constexpr , podría querer que llame a mi propia versión que implemente cosas como la memoria (lo que requeriría estado) para que tenga tiempo de ejecución similar a lo que habría sido mi tiempo de compilación si hubiera pasado un argumento constexpr .


¿Es posible que una implementación de C ++ 11 compatible con estándares permita la sobrecarga de funciones en base a los argumentos que son constexpr, o esto requeriría actualizar el estándar? Si no está permitido, ¿fue intencionalmente prohibido?

Si el estándar no dice que puede hacer algo, entonces permitir que alguien lo haga sería un comportamiento no estándar. Y, por lo tanto, un compilador que lo permitió implementaría una extensión de idioma.

Eso no es necesariamente algo malo, después de todo. Pero no sería compatible C ++ 11.

Solo podemos adivinar las intenciones del comité de estándares. Es posible que no lo hayan permitido deliberadamente o que haya sido un descuido. El hecho es que el estándar no permite la sobrecarga, por lo tanto, no lo es.


El problema, como se dijo, se siente mal .

Una std::string , por construcción, posee la memoria. Si desea una referencia simple a un búfer existente, puede usar algo similar a llvm::StringRef :

class StringRef { public: constexpr StringRef(char const* d, size_t s): data(d), size(s) {} private: char const* data; size_t size; };

Por supuesto, está el rollo que strlen y todas las demás funciones de C no son constexpr . Esto se siente como un defecto del Estándar (piense en todas las funciones matemáticas ...).

En cuanto al estado, puede (un poco), siempre que sepa cómo almacenarlo. Recuerde que los bucles son equivalentes a las recursiones? Bueno, del mismo modo, puede "almacenar" el estado pasándolo como argumento a una función auxiliar.

// potentially unsafe (non-limited) constexpr int length(char const* c) { return *c == ''/0'' ? 0 : 1 + length(c+1); } // OR a safer version constexpr int length_helper(char const* c, unsigned limit) { return *c == ''/0'' or limit <= 0 ? 0 : 1 + length_helper(c+1, limit-1); } constexpr int length256(char const* c) { return length_helper(c, 256); }

Por supuesto, esta forma de este estado es algo limitada (no se pueden usar constructos complicados) y eso es una limitación de constexpr . Pero ya es un gran salto adelante. Yendo más lejos significaría profundizar en la pureza (lo cual es casi imposible en C ++).


Estoy de acuerdo en que esta característica falta, la necesito también. Ejemplo:

double pow(double x, int n) { // calculate x to the power of n return ... } static inline double pow (double x, constexpr int n) { // a faster implementation is possible when n is a compile time constant return ... } double myfunction (double a, int b) { double x, y; x = pow(a, b); // call version 1 unless b becomes a compile time constant by inlining y = pow(a, 5), // call version 2 return x + y; }

Ahora tengo que hacer esto con plantillas:

template <int n> static inline double pow (double x) { // fast implementation of x ^ n, with n a compile time constant return ... }

Esto está bien, pero extraño la oportunidad de sobrecarga. Si configuro una función de biblioteca para que otros la utilicen, entonces es inconveniente que el usuario tenga que usar diferentes llamadas de función dependiendo de si n es una constante de tiempo de compilación o no, y puede ser difícil predecir si el compilador ha reducido n a a. tiempo de compilación constante o no.


La detección de constexpr no se puede realizar mediante sobrecargas (como ya se ha respondido a otras), pero las sobrecargas son solo una forma de hacerlo.

El problema típico es que no podemos usar algo que pueda mejorar el rendimiento en tiempo de ejecución (por ejemplo, para llamar a funciones que no sean constexpr o para almacenar en caché los resultados) en la función constexpr . Entonces podemos terminar con dos algoritmos diferentes, uno menos eficiente pero escribible como constexpr , otro optimizado para correr rápido pero no constexpr . Entonces queremos que el compilador no elija el algoritmo constexpr para los valores de tiempo de ejecución y viceversa.

Esto se puede lograr detectando constexpr y seleccionando basado en él "manualmente" y luego acortando la interfaz con las macros del preprocesador.

Primero, tenemos dos funciones. En general, las funciones deberían alcanzar el mismo resultado con diferentes algoritmos. Elijo dos algoritmos que nunca dan las mismas respuestas aquí solo para probar e ilustrar la idea:

#include <iostream> // handy for test I/O #include <type_traits> // handy for dealing with types // run-time "foo" is always ultimate answer int foo_runtime(int) { return 42; } // compile-time "foo" is factorial constexpr int foo_compiletime(int num) { return num > 1 ? foo_compiletime(num - 1) * num : 1; }

Entonces necesitamos una forma de detectar ese argumento es la expresión constante de tiempo de compilación. Si no queremos usar formas específicas del compilador como __builtin_constant_p , también hay formas de detectarlo en C ++ estándar. Estoy bastante seguro de que el siguiente truco lo inventó Johannes Schaub, pero no puedo encontrar la cita. Muy buen truco y claro.

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

El operador noexcept debe trabajar en tiempo de compilación y, por lo tanto, la mayoría de los compiladores optimizarán la bifurcación. Entonces ahora podemos escribir una macro "foo" que selecciona el algoritmo basado en la consistencia del argumento y probarlo:

#define foo(X) (isprvalconstexpr(X)?foo_compiletime(X):foo_runtime(X)) int main(int argc, char *argv[]) { int a = 1; const int b = 2; constexpr int c = 3; const int d = argc; std::cout << foo(a) << std::endl; std::cout << foo(b) << std::endl; std::cout << foo(c) << std::endl; std::cout << foo(d) << std::endl; }

El resultado esperado es:

42 2 6 42

En los pocos compiladores que probé, funciona como se esperaba.


Si bien no existe la "sobrecarga de constexpr" en C ++ 11, aún puede usar GCC / Clang __builtin_constant_p intrinsic. Tenga en cuenta que esta optimización no es muy útil para double pow(double) , porque tanto GCC como Clang ya pueden optimizar pow para exponentes integrales constantes, pero si escribe una multiprecision o librería de vectores, entonces esta optimización debería funcionar.

Mira este ejemplo:

#define my_pow(a, b) (__builtin_constant_p(b) ? optimized_pow(a, b) : generic_pow(a, b)) double generic_pow(double a, double b); __attribute__((always_inline)) inline double optimized_pow(double a, double b) { if (b == 0.0) return 1.0; if (b == 1.0) return a; if (b == 2.0) return a * a; if (b == 3.0) return a * a * a; if (b == 4.0) return a * a * a * a; return generic_pow(a, b); } double test(double a, double b) { double x = 2.0 + 2.0; return my_pow(a, x) + my_pow(a, b); }

En este ejemplo my_pow(a, x) se expandirá a a*a*a*a (gracias a la eliminación del código muerto), y my_pow(a, b) se expandirá para dirigir la llamada generic_pow sin ninguna verificación preliminar.


Tendría que estar sobrecargado en función de que el resultado sea constexpr o no, en lugar de los argumentos.

Una const std::string podría almacenar un puntero al literal, sabiendo que nunca se escribiría (usar const_cast para eliminar const de std::string sería necesario, y eso ya es un comportamiento indefinido). Solo sería necesario almacenar una bandera booleana para inhibir la liberación del búfer durante la destrucción.

Pero una cadena no const , incluso si se inicializa desde argumentos constexpr , requiere una asignación dinámica, porque se requiere una copia escribible del argumento, y por lo tanto, no se debe usar un constructor constexpr hipotético.

Del estándar (sección 7.1.6.1 [dcl.type.cv] ), la modificación de cualquier objeto creado const es un comportamiento no definido:

Excepto que cualquier miembro de la clase declarado mutable (7.1.1) se puede modificar, cualquier intento de modificar un objeto const durante su ciclo de vida (3.8) da como resultado un comportamiento indefinido.