sovraccarico di constexpr

Correlato: la funzione restituire constexpr non viene compilata

Mi sembra che constexpr sia limitato nell’utilità in C ++ 11 a causa dell’incapacità di definire due funzioni che altrimenti avrebbero la stessa firma, ma una deve essere constexpr e l’altra no constexpr. In altre parole, sarebbe molto utile se potessi avere, ad esempio, un costruttore di constexpr std :: string che accetta solo gli argomenti di constexpr e un costruttore non constexpr std :: string per gli argomenti non-constexpr. Un altro esempio potrebbe essere una funzione teoricamente complicata che potrebbe essere resa più efficiente utilizzando lo stato. Non puoi farlo facilmente con una funzione constexpr, quindi ti restano due scelte: avere una funzione constexpr che è molto lenta se passi argomenti non-constexpr, oppure rinunciare interamente a constexpr (o scrivere due funzioni separate, ma potresti non sapere quale versione chiamare).

La mia domanda, quindi, è questa:

È ansible implementare C ++ 11 conforms allo standard per consentire il sovraccarico della funzione in base agli argomenti di constexpr o richiedere l’aggiornamento dello standard? Se non è permesso, è stato intenzionalmente vietato?


@NicolBolas: Dire che ho una funzione che mappa un enum su una std::string . Il modo più diretto per farlo, supponendo che il mio enum passi da 0 a n - 1 , è quello di creare una matrice di dimensione n piena del risultato.

    Potrei creare un static constexpr char const * [] e build una std::string sul ritorno (pagando il costo della creazione di un object std::string ogni volta che chiamo la funzione), oppure posso creare una static std::string const [] e restituisce il valore che cerco, pagando il costo di tutti i costruttori std::string la prima volta che chiamo la funzione. Sembra che una soluzione migliore sarebbe quella di creare la std::string in memoria in fase di compilazione (simile a quanto fatto ora con char const * ), ma l’unico modo per farlo sarebbe avvertire il costruttore che ha constexpr argomenti.

    Per un esempio diverso da un costruttore std::string , penso che sia piuttosto semplice trovare un esempio in cui, se si potesse ignorare i requisiti di constexpr (e quindi creare una funzione non- constexpr ), si potrebbe creare un altro funzione efficiente. Considera questo thread: domanda di constexpr, perché questi due programmi diversi vengono eseguiti in un intervallo di tempo così diverso con g ++?

    Se chiamo fib con un argomento constexpr , non posso battere fare meglio del compilatore che ottimizza completamente la chiamata di funzione. Ma se chiamo fib con un argomento non constexpr , potrei volere che si chiami la mia versione che implementa cose come la memoizzazione (che richiederebbe lo stato) così ottengo un tempo di esecuzione simile a quello che sarebbe stato il mio tempo di compilazione se avessi passato un argomento di constexpr .

    constexpr essere sovraccaricato in base al risultato essere constexpr o no, piuttosto che gli argomenti.

    Un const std::string potrebbe memorizzare un puntatore al letterale, sapendo che non verrebbe mai scritto (usando const_cast per rimuovere const dalla std::string sarebbe necessario, e questo è già un comportamento non definito). Sarebbe solo necessario memorizzare un flag booleano per inibire la liberazione del buffer durante la distruzione.

    Ma una stringa non const , anche se inizializzata dagli argomenti di constexpr , richiede l’allocazione dynamic, poiché è richiesta una copia scrivibile dell’argomento e quindi non dovrebbe essere usato un ipotetico costruttore di constexpr .


    Dallo standard (sezione 7.1.6.1 [dcl.type.cv] ), la modifica di qualsiasi object che è stato creato const è un comportamento non definito:

    Tranne che qualsiasi membro della class dichiarato mutabile (7.1.1) può essere modificato, qualsiasi tentativo di modificare un object const durante la sua vita (3.8) si traduce in un comportamento indefinito.

    Sono d’accordo che questa funzionalità è mancante – ne ho bisogno anche io. Esempio:

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

    Ora devo farlo con i modelli:

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

    Questo va bene, ma mi manca l’opportunità di sovraccarico. Se creo una funzione di libreria che altri possano utilizzare, è scomodo che l’utente debba utilizzare chiamate di funzioni diverse a seconda che n sia una costante di tempo di compilazione o meno, e potrebbe essere difficile prevedere se il compilatore ha ridotto n a un compilare costante di tempo o no.

    Rilevare constexpr non può essere fatto usando i sovraccarichi (come altri già hanno risposto) ma i sovraccarichi sono solo un modo per farlo.

    Il problema tipico è che non è ansible utilizzare qualcosa che possa migliorare le prestazioni di runtime (ad esempio per chiamare le funzioni di non- constexpr o per memorizzare i risultati nella cache) nella funzione constexpr . Quindi potremmo ritrovarci con due algoritmi diversi, uno meno efficiente ma scrivibile come constexpr , l’altro ottimizzato per funzionare velocemente ma non con constexpr . Quindi vogliamo che il compilatore non scelga l’algoritmo di constexpr per i valori di runtime e viceversa.

    Ciò può essere ottenuto rilevando constexpr e selezionando in base ad esso “manualmente” e accorciando quindi l’interfaccia con macro preprocessore.

    Per prima cosa abbiamo due funzioni. In generale le funzioni dovrebbero raggiungere lo stesso risultato con diversi algoritmi. Scelgo due algoritmi che non danno mai le stesse risposte qui solo per testare e per illustrare l’idea:

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

    Quindi abbiamo bisogno di un modo per rilevare che l’argomento è espressione costante di tempo di compilazione. Se non vogliamo usare metodi specifici del compilatore come __builtin_constant_p allora ci sono modi per rilevarlo anche in C ++ standard. Sono sicuro che Johannes Schaub ha inventato il seguente trucco, ma non riesco a trovare la citazione. Trucco molto bello e chiaro.

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

    L’operatore noexcept è tenuto a lavorare in fase di compilazione e quindi la ramificazione basata su di essa verrà ottimizzata dalla maggior parte dei compilatori. Così ora possiamo scrivere una macro “foo” che seleziona l’algoritmo basato sulla constexprness dell’argomento e per testarlo:

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

    L'output previsto è:

     42 2 6 42 

    Sui pochi compilatori che ho provato funziona come previsto.

    Sebbene non esista “sovraccarico di constexpr” in C ++ 11, è comunque ansible utilizzare GCC / Clang __builtin_constant_p intrinsic. Nota che questa ottimizzazione non è molto utile per double pow(double) , perché sia ​​GCC che Clang possono già ottimizzare la potenza per esponenti interi costanti, ma se si scrive una libreria multiprecisione o vettoriale, allora questa ottimizzazione dovrebbe funzionare.

    Controlla questo esempio:

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

    In questo esempio my_pow(a, x) sarà espanso in a*a*a*a (grazie all’eliminazione del codice morto), e my_pow(a, b) sarà espanso per indirizzare la chiamata generic_pow senza alcuna verifica preliminare.

    Il problema, come affermato, è sbagliato .


    Una std::string , per costruzione, possiede la memoria. Se vuoi un semplice riferimento ad un buffer esistente, puoi usare qualcosa di simile 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; }; 

    Certo, c’è il fiasco che strlen e tutte le altre funzioni C non sono constexpr . Questo sembra un difetto dello Standard (pensa a tutte le funzioni matematiche …).


    Per quanto riguarda lo stato, puoi (un po ‘), a patto che tu capisca come memorizzarlo. Ricorda che i loop sono equivalenti alle ricorsioni? Allo stesso modo, è ansible “memorizzare” lo stato passandolo come argomento a una funzione di supporto.

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

    Naturalmente, questa forma di questo stato è alquanto limitata (non è ansible utilizzare costrutti complicati) e questa è una limitazione di constexpr . Ma è già un enorme balzo in avanti. Andare oltre significherebbe andare più in profondità nella purezza (che è difficilmente ansible in C ++).

    È ansible implementare C ++ 11 conforms allo standard per consentire il sovraccarico della funzione in base agli argomenti di constexpr o richiedere l’aggiornamento dello standard? Se non è permesso, è stato intenzionalmente vietato?

    Se lo standard non dice che puoi fare qualcosa, allora permettere a qualcuno di farlo sarebbe un comportamento non standard. E quindi, un compilatore che permetteva di implementare un’estensione di linguaggio.

    Non è necessariamente una brutta cosa, dopotutto. Ma non sarebbe compatibile con C ++ 11.

    Possiamo solo intuire le intenzioni del comitato degli standard. Potrebbero aver deliberatamente vietato, o potrebbe essere stato qualcosa di una svista. Il fatto è che lo standard non prevede il sovraccarico, quindi non lo è.

    Un’altra opzione per rilevare la compilazione in fase di compilazione utilizzando 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 { } }