sobrecarga de constexpr

Relacionado: Função retornando constexpr não compila

Eu sinto como constexpr é limitado em utilidade em C + + 11 por causa da incapacidade de definir duas funções que teriam a mesma assinatura, mas tem um ser constexpr e o outro não constexpr. Em outras palavras, seria muito útil se eu pudesse ter, por exemplo, um construtor constexpr std :: string que toma apenas argumentos constexpr e um construtor não-constexpr std :: string para argumentos não-constexpr. Outro exemplo seria uma function teoricamente complicada que poderia ser mais eficiente usando o estado. Você não pode facilmente fazer isso com uma function constexpr, então você fica com duas opções: ter uma function constexpr que é muito lenta se você passar em argumentos não-constexpr, ou desistir de constexpr inteiramente (ou escrever duas funções separadas, mas você pode não saber qual versão chamar).

Minha pergunta, portanto, é esta:

É possível que uma implementação C ++ 11 compatível com o padrão permita a sobrecarga de function com base nos argumentos sendo constexpr ou isso exigiria a atualização do padrão? Se não for permitido, foi intencionalmente proibido?


@NicolBolas: Digamos que eu tenha uma function que mapeie um enum para std::string . A maneira mais direta de fazer isso, assumindo que meu enum vai de 0 a n - 1 , é criar uma matriz de tamanho n preenchida com o resultado.

Eu poderia criar um static constexpr char const * [] e construir um std::string em retorno (pagando o custo de criação de um object std::string toda vez que eu chamo a function), ou eu posso criar um static std::string const [] e retorna o valor que eu procuro, pagando o custo de todos os construtores std::string na primeira vez que eu chamo a function. Parece que uma solução melhor seria criar o std::string na memory em tempo de compilation (semelhante ao que é feito agora com char const * ), mas a única maneira de fazer isso seria alertar o construtor que ele tem constexpr argumentos.

Para um exemplo diferente de um construtor std::string , eu acho que é bem direto encontrar um exemplo onde, se você pudesse ignorar os requisitos de constexpr (e assim criar uma function não- constexpr ), você poderia criar um function eficiente. Considere este tópico: constexpr question, por que esses dois programas diferentes são executados em um período de tempo tão diferente com o g ++?

Se eu chamo fib com um argumento constexpr , não consigo me sair melhor do que o compilador otimizando a chamada de function completamente. Mas se eu chamar fib com um argumento non- constexpr , eu posso querer que ele chame minha própria versão que implementa coisas como memoization (o que exigiria estado) então eu tenho tempo de execução semelhante ao que teria sido meu tempo de compilation se eu tivesse passado um argumento constexpr .

Teria que ser sobrecarregado com base no resultado sendo constexpr ou não, em vez dos argumentos.

Um const std::string poderia armazenar um ponteiro para o literal, sabendo que ele nunca seria gravado (usando const_cast para remover const do std::string seria necessário, e isso já é um comportamento indefinido). Seria necessário apenas armazenar uma bandeira booleana para inibir a liberação do buffer durante a destruição.

Mas uma cadeia não- const , mesmo se inicializada a partir de argumentos constexpr , requer alocação dinâmica, porque uma cópia gravável do argumento é necessária e, portanto, um construtor constexpr hipotético não deve ser usado.


A partir do padrão (seção 7.1.6.1 [dcl.type.cv] ), modificar qualquer object que tenha sido criado const é um comportamento indefinido:

Exceto que qualquer membro da class declarado mutável (7.1.1) pode ser modificado, qualquer tentativa de modificar um object const durante sua vida útil (3.8) resulta em um comportamento indefinido.

Eu concordo que esse recurso está faltando – eu preciso disso também. Exemplo:

 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; } 

Agora eu tenho que fazer isso com templates:

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

Isso é bom, mas sinto falta da oportunidade de sobrecarga. Se eu fizer uma function de biblioteca para outros usarem, então é inconveniente que o usuário tenha que usar chamadas de function diferentes dependendo se n é uma constante de tempo de compilation ou não, e pode ser difícil prever se o compilador reduziu n para um compilar constante de tempo ou não.

A detecção de constexpr não pode ser feita usando sobrecargas (como outras já responderam), mas as sobrecargas são apenas uma maneira de fazê-lo.

O problema típico é que não podemos usar algo que possa melhorar o desempenho em tempo de execução (por exemplo, chamar funções não constexpr ou armazenar resultados em cache) na function constexpr . Assim, podemos acabar com dois algoritmos diferentes, um menos eficiente, mas gravável como constexpr , outro otimizado para rodar rápido, mas não constexpr . Então, queremos que o compilador não escolha o algoritmo constexpr para valores de tempo de execução e vice-versa.

Isso pode ser feito detectando constexpr e selecionando com base nele “manualmente” e encurtando a interface com macros de pré-processamento.

Primeiro, vamos ter duas funções. Em geral, as funções devem atingir o mesmo resultado com diferentes algoritmos. Eu escolho dois algoritmos que nunca dão as mesmas respostas aqui apenas para testar e ilustrar a ideia:

 #include  // handy for test I/O #include  // 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; } 

Então precisamos de uma maneira de detectar que o argumento é uma expressão constante no tempo de compilation. Se não quisermos usar maneiras específicas do compilador como __builtin_constant_p então também há maneiras de detectá-lo no C ++ padrão. Tenho certeza que o truque seguinte é inventado por Johannes Schaub, mas não consigo encontrar a citação. Truque muito bom e claro.

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

O operador noexcept é obrigado a trabalhar em tempo de compilation e, assim, a ramificação baseada nele será otimizada pela maioria dos compiladores. Então, agora podemos escrever uma macro “foo” que seleciona o algoritmo com base na constexprness do argumento e testá-lo:

 #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; } 

A saída esperada é:

 42 2 6 42 

Nos poucos compiladores que eu tentei funciona como esperado.

Embora não exista algo como “constexpr overloading” no C ++ 11, você ainda pode usar o GCC / Clang __builtin_constant_p intrinsic. Note que esta otimização não é muito útil para double pow(double) , porque tanto o GCC quanto o Clang já podem otimizar pow para expoentes integrais constantes, mas se você escrever uma multiprecisão ou biblioteca de vetores, esta otimização deve funcionar.

Veja este exemplo:

 #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); } 

Neste exemplo my_pow(a, x) será expandido para a*a*a*a (graças à eliminação do código morto), e my_pow(a, b) será expandido para direcionar a chamada generic_pow sem verificações preliminares.

O problema, como afirmado, parece errado .


Um std::string , por construção, possui a memory. Se você quiser uma simples referência a um buffer existente, você pode usar algo parecido com o llvm::StringRef :

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

Naturalmente, existe a chatice que strlen e todas as outras funções C não são constexpr . Isso parece um defeito do Standard (pense em todas as funções matemáticas …).


Quanto ao estado, você pode (um pouco), contanto que você entenda como armazená-lo. Lembre-se de que os loops são equivalentes a recursões? Bem, da mesma forma, você pode “armazenar” o estado passando-o como argumento para uma function 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); } 

É claro que essa forma desse estado é um tanto limitada (você não pode usar construções complicadas) e isso é uma limitação do constexpr . Mas já é um grande salto em frente. Indo mais longe significaria ir mais fundo na pureza (o que é dificilmente possível em C ++).

É possível que uma implementação C ++ 11 compatível com o padrão permita a sobrecarga de function com base nos argumentos sendo constexpr ou isso exigiria a atualização do padrão? Se não for permitido, foi intencionalmente proibido?

Se o padrão não disser que você pode fazer algo, permitir que alguém o faça seria um comportamento não padronizado. E, portanto, um compilador que permitisse implementar uma extensão de linguagem.

Isso não é necessariamente uma coisa ruim, afinal. Mas não seria compatível com o C ++ 11.

Só podemos adivinhar as intenções do comitê de padrões. Eles podem ter deliberadamente não permitido, ou pode ter sido uma espécie de supervisão. O fato é que o padrão não sobrecarrega é permitido, portanto não é.

Outra opção para detectar a compilation em tempo de compilation usando o SFINAE: http://coliru.stacked-crooked.com/a/f3a2c11bcccdb5bf

 template auto f(const T&) { return 1; } constexpr auto f(int) { return 2; } //////////////////////////////////////////////////////////////////////// template constexpr bool is_f_constexpr_for(int) {return true;} template constexpr bool is_f_constexpr_for(...) {return false;} template auto g(const T& t) { if constexpr (is_f_constexpr_for(0)) { } else { } }