Compilar o comprimento de uma string C em tempo de compilação. Este é realmente um constexpr?

94

Estou tentando calcular o comprimento de uma string literal em tempo de compilação. Para fazer isso, estou usando o seguinte código:

#include <cstdio>

int constexpr length(const char* str)
{
    return *str ? 1 + length(str + 1) : 0;
}

int main()
{
    printf("%d %d", length("abcd"), length("abcdefgh"));
}

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

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 lengthfunção será avaliada em tempo de compilação?

Se isso for verdade, a porta para cálculos de literais de string em tempo de compilação acabou de se abrir para mim ... por exemplo, posso calcular hashes em tempo de compilação e muito mais ...

Mircea Ispas
fonte
3
Desde que o parâmetro seja uma expressão constante, deve ser.
chris de
1
@chris Existe uma garantia de que algo que pode ser uma expressão constante deve ser avaliado em tempo de compilação quando usado em um contexto que não requer uma expressão constante?
TC de
12
BTW, incluindo <cstdio>e, em seguida, a chamada ::printfnão é portátil. O padrão exige apenas <cstdio>fornecer std::printf.
Ben Voigt de
1
@BenVoigt Ok, obrigado por apontar isso :) Inicialmente eu usei std :: cout, mas o código gerado era muito grande para encontrar os valores reais :)
Mircea Ispas
3
@Felics Eu costumo usar godbolt ao responder perguntas relacionadas com otimização e usar printfpode levar a muito menos código para lidar.
Shafik Yaghmour

Respostas:

76

Expressões constantes não são garantidas para serem avaliadas em tempo de compilação, nós temos apenas uma citação não normativa do rascunho da seção padrão C ++ 5.19 Expressões constantes que diz o seguinte:

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

Você pode atribuir o resultado à constexprvariável para ter certeza de que ele é avaliado em tempo de compilação, podemos ver isso na referência C ++ 11 de Bjarne Stroustrup que diz ( grifo meu ):

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

Por exemplo:

constexpr int len1 = length("abcd") ;

Bjarne Stroustrup fornece um resumo de quando podemos garantir a avaliação do tempo de compilação nesta entrada do blog isocpp e diz:

[...] A resposta correta - como afirma Herb - é que de acordo com o padrão uma função constexpr pode ser avaliada em tempo de compilação ou tempo de execução a menos que seja usada como uma expressão constante, caso em que deve ser avaliada em tempo de compilação -Tempo. Para garantir a avaliação em tempo de compilação, devemos usá-lo onde uma expressão constante é necessária (por exemplo, como um limite de array ou como um rótulo de caso) ou usá-lo para inicializar um constexpr. Espero que nenhum compilador que se preze perca a oportunidade de otimização de fazer o que eu disse originalmente: "Uma função constexpr é avaliada em tempo de compilação se todos os seus argumentos forem expressões constantes."

Portanto, isso descreve dois casos em que deve ser avaliado em tempo de compilação:

  1. Use-o onde uma expressão constante é necessária, parece estar em qualquer lugar no padrão de rascunho onde a frase shall be ... converted constant expressionou shall be ... constant expressioné usada, como um limite de matriz.
  2. Use-o para inicializar um constexprconforme delineado acima.
Shafik Yaghmour
fonte
4
Dito isso, em princípio, um compilador tem o direito de ver um objeto com interno ou sem ligação com constexpr int x = 5;, observe que ele não requer o valor em tempo de compilação (assumindo que não seja usado como um parâmetro de modelo ou outros enfeites) e realmente emitir código que calcula o valor inicial em tempo de execução usando 5 valores imediatos de 1 e 4 ops de adição. Um exemplo mais realista: o compilador pode atingir um limite de recursão e adiar a computação até o tempo de execução. A menos que você faça algo que force o compilador a realmente usar o valor, "garantia de avaliação em tempo de compilação" é um problema de QOI.
Steve Jessop
@SteveJessop Bjarne parece estar usando um conceito que não tem um análogo que posso encontrar no projeto de norma que é usado como uma expressão constante de meios avaliados na tradução. Portanto, parece que o padrão não declara explicitamente o que ele está dizendo, então eu tenderia a concordar com você. Embora tanto Bjarne quanto Herb pareçam concordar com isso, o que pode indicar que está apenas subespecificado.
Shafik Yaghmour
2
Acho que os dois estão considerando apenas "compiladores que se respeitam", ao contrário do compilador que segue os padrões, mas propositalmente obstrutivo, que eu suponho. É útil como um meio de raciocinar sobre o que o padrão realmente garante , e não muito mais ;-)
Steve Jessop
3
@SteveJessop Compiladores intencionalmente obstrutivos, como o infame (e infelizmente inexistente) Hell ++. Isso seria ótimo para testar a conformidade / portabilidade.
Angew não está mais orgulhoso de SO
Sob a regra de como se, mesmo usando o valor como uma aparente constante de tempo de compilação não é suficiente: o compilador está livre para enviar uma cópia de sua fonte e recompilá-la em tempo de execução, ou fazer cálculos ru ti e para determinar o tipo de um variável, ou simplesmente refaça inutilmente seu constexprcálculo do mal absoluto. É até mesmo gratuito esperar 1 segundo por personagem em uma determinada linha de origem, ou pegar uma determinada linha de origem e usá-la para semear uma posição de xadrez e, em seguida, jogar os dois lados para determinar quem ganhou.
Yakk - Adam Nevraumont
27

É realmente fácil descobrir se uma chamada para uma constexprfunção resulta em uma expressão constante central ou se está apenas sendo otimizada:

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

int main()
{
    constexpr int test_const = length("abcd");
    std::array<char,length("abcdefgh")> test_const2;
}
Ben Voigt
fonte
4
... e compilar com -pedantic, se você usar gcc. Caso contrário, você não receberá avisos e erros
B 17овић
@ BЈовић Ou use-o em um contexto em que o GCC não tenha extensões que possam atrapalhar, como um argumento de modelo.
Angew não está mais orgulhoso de SO
Não seria um hack enum mais confiável? Como enum { Whatever = length("str") }?
sharptooth
18
Digno de menção éstatic_assert(length("str") == 3, "");
chris
8
constexpr auto test = /*...*/;é provavelmente o mais geral e direto.
TC
19

Apenas uma observação, que compiladores modernos (como gcc-4.x) fazem strlenpara literais de string em tempo de compilação porque normalmente é definido como uma função intrínseca . Sem otimizações habilitadas. Embora o resultado não seja uma constante de tempo de compilação.

Por exemplo:

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

Resulta em:

movl    $3, %esi    # strlen("abc")
movl    $.LC0, %edi # "%zu\n"
movl    $0, %eax
call    printf
Maxim Egorushkin
fonte
Observe, isso funciona porque strlené uma função incorporada, se a usarmos -fno-builtinsvolta a chamá-la em tempo de execução, veja-a ao vivo
Shafik Yaghmour
strlené constexprpara mim, mesmo com -fno-nonansi-builtins(parece -fno-builtinsque não existe mais no g ++). Eu digo "constexpr", porque posso fazer isso template<int> void foo();e foo<strlen("hi")>(); g ++ - 4.8.4
Aaron McDaid
18

Deixe-me propor outra função que calcula o comprimento de uma string em tempo de compilação sem ser recursiva.

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

Dê uma olhada neste código de amostra em ideone .

user2436830
fonte
4
Não pode ser igual a strlen devido a incorporado '\ 0': strlen ("hi \ 0there")! = Length ("hi \ 0there")
unkulunkulu
Esta é a maneira correta, este é um exemplo em Effective Modern C ++ (se bem me lembro). No entanto, existe uma classe de string agradável que é inteiramente constexpr, veja esta resposta: str_const de Scott Schurr , talvez seja mais útil (e menos no estilo C).
QuantumKarl
@MikeWeir Ops, isso é estranho. Aqui estão vários links: link para a pergunta , link para o papel , link para a fonte no git
QuantumKarl
agora sim: char temp[256]; sprintf(temp, "%u", 2); if(1 != length(temp)) printf("Your solution doesn't work"); ideone.com/IfKUHV
Pablo Ariel
7

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

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

template<typename T, T V>
struct static_eval
{
  static constexpr T value = V;
};

Agora, se você escrever

if (static_eval<int, length("hello, world")>::value > 7) { ... }

você pode ter certeza de que a ifinstrução é uma constante de tempo de compilação sem sobrecarga de tempo de execução.

5gon12eder
fonte
8
ou apenas use std :: integral_constant <int, length (...)> :: value
Mircea Ispas
1
O exemplo é um pouco inútil, já que lenser constexprmeio lengthdeve ser avaliado em tempo de compilação de qualquer maneira.
Chris
@chris Eu não sabia que deveria ser, embora tenha observado que é com meu compilador.
5gon12eder
Ok, de acordo com a maioria das outras respostas, ele tem que ser, então eu modifiquei o exemplo para ficar menos inútil. Na verdade, era uma ifcondição (em que era essencial que o compilador eliminasse o código morto) para a qual eu originalmente usei o truque.
5gon12eder
1

Uma breve explicação da entrada da Wikipedia sobre Expressões constantes generalizadas :

O uso de constexpr em uma função impõe algumas limitações sobre o que essa função pode fazer. Primeiro, a função deve ter um tipo de retorno não nulo. Em segundo lugar, o corpo da função não pode declarar variáveis ​​ou definir novos tipos. Terceiro, o corpo pode conter apenas declarações, instruções nulas e uma única instrução de retorno. Deve haver 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 constexprpalavra - chave antes de uma definição de função instrui o compilador a verificar se essas limitações são atendidas. Se sim, e a função é 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.

Kaedinger
fonte
Essas condições não garantem que o valor retornado seja constante . Por exemplo, a função pode ser chamada com outros valores de argumento.
Ben Voigt de
Certo, @BenVoigt. Eu editei para ser chamado com uma expressão constante.
Kaedinger