Calcolo della lunghezza di una stringa C in fase di compilazione. È davvero un constexpr?


94

Sto cercando di calcolare la lunghezza di una stringa letterale in fase di compilazione. Per farlo sto usando il seguente codice:

#include <cstdio>

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

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

Tutto funziona come previsto, il programma stampa 4 e 8. Il codice assembly generato da clang mostra che i risultati vengono calcolati in fase di compilazione:

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

La mia domanda: è garantito dallo standard che la lengthfunzione verrà valutata in fase di compilazione?

Se questo è vero, la porta per i calcoli dei valori letterali delle stringhe in fase di compilazione si è appena aperta per me ... per esempio posso calcolare gli hash in fase di compilazione e molti altri ...


3
Finché il parametro è un'espressione costante, deve esserlo.
chris

1
@chris Esiste una garanzia che qualcosa che può essere un'espressione costante debba essere valutato in fase di compilazione se utilizzato in un contesto che non richiede un'espressione costante?
TC

12
A proposito, includere <cstdio>e poi chiamare ::printfnon è portatile. Lo standard richiede solo <cstdio>di fornire std::printf.
Ben Voigt

1
@BenVoigt Ok, grazie per averlo sottolineato :) Inizialmente ho usato std :: cout, ma il codice generato era abbastanza grande per trovare i valori effettivi :)
Mircea Ispas

3
@Felics Uso spesso godbolt quando rispondo a domande riguardanti l'ottimizzazione e l'utilizzo printfpuò portare a una quantità di codice notevolmente inferiore da gestire.
Shafik Yaghmour

Risposte:


76

Non è garantito che le espressioni costanti vengano valutate in fase di compilazione, abbiamo solo una citazione non normativa dalla bozza della sezione standard C ++ 5.19 Espressioni costanti che dice questo però:

[...]> [Nota: le espressioni costanti possono essere valutate durante la traduzione. - nota finale]

Puoi assegnare il risultato alla constexprvariabile per assicurarti che venga valutato in fase di compilazione, possiamo vederlo dal riferimento C ++ 11 di Bjarne Stroustrup che dice ( enfasi mia ):

Oltre ad essere in grado di valutare le espressioni in fase di compilazione, vogliamo essere in grado di richiedere che le espressioni vengano valutate in fase di compilazione; constexpr davanti a una definizione di variabile fa questo (e implica const):

Per esempio:

constexpr int len1 = length("abcd") ;

Bjarne Stroustrup fornisce un riepilogo di quando possiamo garantire la valutazione del tempo di compilazione in questo post del blog isocpp e dice:

[...] La risposta corretta - come affermato da Herb - è che secondo lo standard una funzione constexpr può essere valutata in fase di compilazione o in fase di esecuzione a meno che non sia usata come espressione costante, nel qual caso deve essere valutata in fase di compilazione -tempo. Per garantire la valutazione in fase di compilazione, dobbiamo usarlo dove è richiesta un'espressione costante (ad esempio, come un array associato o come un'etichetta case) o usarlo per inizializzare un constexpr. Mi auguro che nessun compilatore che si rispetti perda l'opportunità di ottimizzazione di fare quello che ho detto inizialmente: "Una funzione constexpr viene valutata in fase di compilazione se tutti i suoi argomenti sono espressioni costanti".

Quindi questo delinea due casi in cui dovrebbe essere valutato in fase di compilazione:

  1. Usalo dove è richiesta un'espressione costante, questo sembrerebbe essere ovunque nella bozza di standard in cui viene utilizzata la frase shall be ... converted constant expressiono shall be ... constant expression, ad esempio un limite di matrice.
  2. Usalo per inizializzare un constexprcome ho delineato sopra.

4
Detto questo, in linea di principio un compilatore ha il diritto di vedere un oggetto con collegamento interno o nessun collegamento con constexpr int x = 5;, osservare che non richiede il valore in fase di compilazione (supponendo che non sia usato come parametro di modello o altro) ed effettivamente emettere codice che calcola il valore iniziale in fase di esecuzione utilizzando 5 valori immediati di 1 e 4 operazioni di addizione. Un esempio più realistico: il compilatore potrebbe raggiungere un limite di ricorsione e rinviare il calcolo fino al runtime. A meno che non si faccia qualcosa che costringa il compilatore a utilizzare effettivamente il valore, "garantito per essere valutato in fase di compilazione" è un problema di QOI.
Steve Jessop

@SteveJessop Bjarne sembra utilizzare un concetto che non ha un analogo che posso trovare nella bozza di standard che viene utilizzato come mezzo di espressione costante valutato al momento della traduzione. Quindi sembrerebbe che lo standard non dichiari esplicitamente ciò che sta dicendo, quindi tenderei a essere d'accordo con te. Sebbene sia Bjarne che Herb sembrino essere d'accordo su questo, il che potrebbe indicare che è appena sotto specificato.
Shafik Yaghmour

2
Penso che entrambi stiano considerando solo "compilatori che si rispettano", in opposizione al compilatore conforme agli standard ma intenzionalmente ostruttivo che ipotizzo. È utile come mezzo per ragionare su ciò che lo standard garantisce effettivamente , e non molto altro ;-)
Steve Jessop

3
@SteveJessop Compilatori intenzionalmente ostruttivi, come il famigerato (e sfortunatamente inesistente) Hell ++. Una cosa del genere sarebbe davvero ottima per testare la conformità / portabilità.
Angew non è più orgoglioso di SO

Secondo la regola as-if, anche usare il valore come un'apparente costante di tempo di compilazione non è sufficiente: il compilatore è libero di spedire una copia del tuo sorgente e ricompilarlo in fase di runtime, o eseguire calcoli di ru ti e per determinare il tipo di a variabile, o semplicemente riesegui inutilmente il tuo constexprcalcolo per pura malvagità. È anche gratuito aspettare 1 secondo per personaggio in una data linea di origine, o prendere una data linea di fonte e usarla per seminare una posizione di scacchi, quindi giocare entrambe le parti per determinare chi ha vinto.
Yakk - Adam Nevraumont

27

È davvero facile scoprire se una chiamata a una constexprfunzione si traduce in un'espressione costante di base o viene semplicemente ottimizzata:

Usalo in un contesto in cui è richiesta un'espressione costante.

int main()
{
    constexpr int test_const = length("abcd");
    std::array<char,length("abcdefgh")> test_const2;
}

4
... e compila con -pedantic, se usi gcc. In caso contrario, non vengono visualizzati avvisi ed errori
BЈовић

@ BЈовић Oppure usalo in un contesto in cui GCC non ha estensioni potenzialmente d'intralcio, come un argomento modello.
Angew non è più orgoglioso di SO

Un enum hack non sarebbe più affidabile? Come enum { Whatever = length("str") }?
punta di diamante

18
Degno di menzione èstatic_assert(length("str") == 3, "");
chris

8
constexpr auto test = /*...*/;è probabilmente il più generale e diretto.
TC

19

Solo una nota, che i compilatori moderni (come gcc-4.x) fanno strlenper i valori letterali di stringa in fase di compilazione perché normalmente sono definiti come una funzione intrinseca . Senza ottimizzazioni abilitate. Sebbene il risultato non sia una costante del tempo di compilazione.

Per esempio:

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

Risultati in:

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

Nota, funziona perché strlenè una funzione incorporata, se la usiamo -fno-builtinstorna a chiamarla in fase di esecuzione,
guardala

strlenè constexprper me, anche con -fno-nonansi-builtins(sembra -fno-builtinsche non esista più in g ++). Dico "constexpr", perché posso fare questo template<int> void foo();e foo<strlen("hi")>(); g ++ - 4.8.4
Aaron McDaid

18

Vorrei proporre un'altra funzione che calcola la lunghezza di una stringa in fase di compilazione senza essere ricorsiva.

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

Dai un'occhiata a questo codice di esempio su ideone .


4
Può non essere uguale a strlen a causa di "\ 0" incorporato: strlen ("hi \ 0there")! = Length ("hi \ 0there")
unkulunkulu

Questo è il modo corretto, questo è un esempio in Effective Modern C ++ (se ricordo bene). Tuttavia, c'è una bella classe di stringhe che è interamente constexpr vedi questa risposta: str_const di Scott Schurr , forse questo sarà più utile (e meno in stile C).
QuantumKarl

@ MikeWeir Ops, è strano. Qui ci sono vari link: link alla domanda , link alla carta , link alla fonte su git
QuantumKarl

ora come fai: char temp[256]; sprintf(temp, "%u", 2); if(1 != length(temp)) printf("Your solution doesn't work"); ideone.com/IfKUHV
Pablo Ariel

7

Non vi è alcuna garanzia che una constexprfunzione venga valutata in fase di compilazione, sebbene qualsiasi compilatore ragionevole lo farà a livelli di ottimizzazione appropriati abilitati. D'altra parte, i parametri del modello devono essere valutati in fase di compilazione.

Ho usato il seguente trucco per forzare la valutazione in fase di compilazione. Purtroppo funziona solo con valori integrali (cioè non con valori in virgola mobile).

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

Ora, se scrivi

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

puoi essere certo che l' ifistruzione è una costante in fase di compilazione senza overhead di runtime.


8
o semplicemente usa std :: integral_constant <int, length (...)> :: value
Mircea Ispas

1
L'esempio è un po 'di un uso inutile poiché lenessendo constexprmezzo lengthdeve essere valutata al momento della compilazione comunque.
chris

@ Chris non sapevo che deve essere, anche se ho notato che è con il mio compilatore.
5gon12eder

Ok, secondo la maggior parte delle altre risposte deve, quindi ho modificato l'esempio in modo che sia meno inutile. In effetti, era una if-condizione (dove era essenziale che il compilatore eseguisse l'eliminazione del codice morto) per la quale originariamente usavo il trucco.
5gon12eder

1

Una breve spiegazione dalla voce di Wikipedia sulle espressioni costanti generalizzate :

L'uso di constexpr su una funzione impone alcune limitazioni su ciò che quella funzione può fare. Innanzitutto, la funzione deve avere un tipo di ritorno non void. In secondo luogo, il corpo della funzione non può dichiarare variabili o definire nuovi tipi. Terzo, il corpo può contenere solo dichiarazioni, istruzioni null e una singola istruzione return. Devono esistere valori di argomento tali che, dopo la sostituzione dell'argomento, l'espressione nell'istruzione return produca un'espressione costante.

La presenza della constexprparola chiave prima di una definizione di funzione indica al compilatore di verificare se queste limitazioni sono soddisfatte. Se sì, e la funzione viene chiamata con una costante, il valore restituito è garantito come costante e quindi può essere utilizzato ovunque sia richiesta un'espressione costante.


Queste condizioni non garantiscono che il valore restituito sia costante . Ad esempio, la funzione potrebbe essere chiamata con altri valori di argomento.
Ben Voigt

Esatto, @BenVoigt. L'ho modificato per essere chiamato con un'espressione costante.
kaedinger
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.