Comprimento de computação de uma string C em tempo de compilation. Isso é realmente um constexpr?

Eu estou tentando calcular o comprimento de uma string literal em tempo de compilation. Para fazer isso, estou usando o seguinte código:

#include  int constexpr length(const char* str) { return *str ? 1 + length(str + 1) : 0; } int main() { printf("%d %d", length("abcd"), length("abcdefgh")); } 

Tudo funciona como esperado, o programa imprime 4 e 8. O código de assembly gerado pelo clang mostra que os resultados são calculados em tempo de compilation:

 0x100000f5e: leaq 0x35(%rip), %rdi ; "%d %d" 0x100000f65: movl $0x4, %esi 0x100000f6a: movl $0x8, %edx 0x100000f6f: xorl %eax, %eax 0x100000f71: callq 0x100000f7a ; symbol stub for: printf 

Minha pergunta: é garantido pelo padrão que a function length será avaliada em tempo de compilation?

Se isso for verdade, a porta para cálculos de literais de string de tempo de compilation foram abertos para mim … por exemplo, posso calcular hashes em tempo de compilation e muito mais …

Não é garantido que as expressões constantes sejam avaliadas em tempo de compilation, só temos uma citação não-normativa do rascunho da seção padrão C ++ 5.19 Expressões constantes que dizem isso:

[…]> [Nota: expressões constantes podem ser avaliadas durante a tradução. – nota final]

Você pode atribuir o resultado à variável constexpr para ter certeza de que é avaliado em tempo de compilation, podemos ver isso na referência C ++ 11 da Bjarne Stroustrup, que diz ( ênfase minha ):

Além de poder avaliar expressões em tempo de compilation, queremos poder exigir que as expressões sejam avaliadas em tempo de compilation; constexpr na frente de uma definição de variável faz isso (e implica const):

Por exemplo:

 constexpr int len1 = length("abcd") ; 

Bjarne Stroustrup dá um resumo de quando podemos assegurar a avaliação do tempo de compilation nesta input de blog do isocpp e diz:

[…] A resposta correta – como declarado por Herb – é que, de acordo com o padrão, uma function constexpr pode ser avaliada no tempo do compilador ou em tempo de execução, a menos que seja usada como uma expressão constante, caso em que deve ser avaliada na compilation -Tempo. Para garantir a avaliação em tempo de compilation, devemos usá-la onde uma expressão constante é necessária (por exemplo, como um label de matriz ou como um label de caso) ou usá-la para inicializar um constexpr. Espero que nenhum compilador que se preze perderia a oportunidade de otimização para fazer o que eu disse originalmente: “Uma function constexpr é avaliada em tempo de compilation se todos os seus argumentos forem expressões constantes”.

Portanto, isso descreve dois casos em que ele deve ser avaliado em tempo de compilation:

  1. Usá-lo onde uma expressão constante é necessária, isso parece estar em qualquer lugar no padrão de rascunho onde a frase shall be ... converted constant expression ou shall be ... constant expression é usada, como um limite de matriz.
  2. Use-o para inicializar um constexpr como esquematizo acima.

É muito fácil descobrir se uma chamada para uma function constexpr resulta em uma expressão constante central ou está meramente sendo otimizada:

Use-o em um contexto em que uma expressão constante é necessária.

 int main() { constexpr int test_const = length("abcd"); std::array test_const2; } 

Apenas uma nota, que os compiladores modernos (como o gcc-4.x) fazem o strlen para literais de string em tempo de compilation, porque ele é normalmente definido como uma function intrínseca . Sem otimizações ativadas. Embora o resultado não seja uma constante de tempo de compilation.

Por exemplo:

 printf("%zu\n", strlen("abc")); 

Resulta em:

 movl $3, %esi # strlen("abc") movl $.LC0, %edi # "%zu\n" movl $0, %eax call printf 

Deixe-me propor outra function que calcula o comprimento de uma string em tempo de compilation sem ser recursiva.

 template< size_t N > constexpr size_t length( char const (&)[N] ) { return N-1; } 

Dê uma olhada neste exemplo de código no ideone .

Não há garantia de que uma function constexpr seja avaliada em tempo de compilation, embora qualquer compilador razoável faça isso nos níveis de otimização apropriados ativados. Por outro lado, os parâmetros do modelo devem ser avaliados em tempo de compilation.

Eu usei o seguinte truque para forçar a avaliação em tempo de compilation. Infelizmente, ele só funciona com valores integrais (ou seja, não com valores de ponto flutuante).

 template struct static_eval { static constexpr T value = V; }; 

Agora, se você escreve

 if (static_eval::value > 7) { ... } 

Você pode ter certeza de que a instrução if é uma constante de tempo de compilation sem sobrecarga de tempo de execução.

Uma breve explicação da input da Wikipedia sobre expressões constantes generalizadas :

O uso de constexpr em uma function impõe algumas limitações sobre o que essa function pode fazer. Primeiro, a function deve ter um tipo de retorno não-vazio. Em segundo lugar, o corpo da function não pode declarar variables ​​ou definir novos tipos. Terceiro, o corpo pode conter apenas declarações, instruções nulas e uma única declaração de retorno. Deve existir valores de argumento de forma que, após a substituição do argumento, a expressão na instrução de retorno produza uma expressão constante.

Ter a palavra-chave constexpr antes de uma definição de function instrui o compilador a verificar se essas limitações são atendidas. Se sim, e a function é chamada com uma constante, o valor retornado é garantido como constante e, portanto, pode ser usado em qualquer lugar em que uma expressão constante seja necessária.