2015-01-07 10 views
13

Sto sperimentando con le funzioni di constexpr in C++ 14. Il codice seguente, che calcola le opere fattoriali come previsto:C++ 14: tipi di ritorno (auto) dedotti da constexpr con espressioni ternarie

template <typename T> 
constexpr auto fact(T a) { 
    if(a==1) 
     return 1; 
    return a*fact(a-1); 
} 

int main(void) { 
    static_assert(fact(3)==6, "fact doesn't work"); 
} 

quando viene compilato come segue con clang:

> clang++ --version 
clang version 3.5.0 (tags/RELEASE_350/final) 
Target: x86_64-unknown-linux-gnu 
Thread model: posix 
> clang++ -std=c++14 constexpr.cpp 

Tuttavia, quando cambio la definizione fact di utilizzare la ? operatore ternario:

template <typename T> 
constexpr auto fact(T a) { 
    return a==1 ? 1 : a*fact(a-1); 
} 

ottengo il seguente errore del compilatore:

> clang++ -std=c++14 constexpr.cpp 
constexpr.cpp:12:31: fatal error: recursive template instantiation exceeded maximum depth of 
     256 
    return a==T(1) ? T(1) : a*fact(a-1); 
     ... snip ... 
constexpr.cpp:16:19: note: in instantiation of function template specialization 'fact<int>' 
     requested here 
    static_assert(fact(3)==6, "fact doesn't work"); 

Il problema è stato risolto se affermo in modo esplicito il tipo di ritorno T (invece di utilizzare auto per dedurre il tipo di ritorno)

template <typename T> 
constexpr T fact(T a) { 
    return a==1 ? 1 : a*fact(a-1); 
} 

Se rimuovo il parametro del modello, lo schema si ripete (la versione ternario non riesce, e la versione if funziona)

// this works just fine 
constexpr auto fact(int a) { 
    if(a==1) 
     return 1; 
    return a*fact(a-1); 
} 

che tale non riesce

constexpr auto fact(int a) { 
    return a==1 ? 1 : a*fact(a-1); 
} 

con il seguente errore

> clang++ -std=c++14 constexpr.cpp 
constexpr.cpp:16:25: error: function 'fact' with deduced return type cannot be used before it 
     is defined 
    return a==1 ? 1 : a*fact(a-1); 
         ^
constexpr.cpp:15:16: note: 'fact' declared here 
constexpr auto fact(int a) { 
      ^
constexpr.cpp:20:26: error: invalid operands to binary expression ('void' and 'int') 
    static_assert(fact(3)==6, "fact doesn't work"); 
        ~~~~~~~^ ~ 
2 errors generated. 

cosa sta succedendo qui?

risposta

11

Il tipo risultante dalla valutazione di un'espressione ternaria è common type of its second and third arguments.

Il compilatore deduce il tipo restituito, lo impone per valutare entrambi questi argomenti sull'espressione ternaria. Ciò significa che la ricorsione non termina anche quando viene raggiunta la condizione di chiusura, poiché quando a==1, per capire il tipo di ritorno di fact(0), il compilatore deve continuare a valutare ulteriori chiamate ricorsive a fact e ne consegue una ricorsione infinita.

Dichiarando il tipo di reso, fact(0) non deve essere valutato quando a==1 e la ricorsione è in grado di terminare.


Come per il caso delle due return istruzioni, la clausola standard rilevante è —

(da N4296) §7.1.6.4/9[dcl.spec.auto]

If a function with a declared return type that contains a placeholder type has multiple return statements, the return type is deduced for each return statement. If the type deduced is not the same in each deduction, the program is ill-formed.

Nell'esempio, nella chiamata a fact<int>(1), il tipo di reso dedotto dalla prima istruzione return è int, quindi il tipo di ritorno di fact<int>(0) nella seconda dichiarazione return non può essere altro che int. Ciò significa che il compilatore non ha bisogno di valutare il corpo di fact<int>(0) e la ricorsione può terminare.

In effetti, se si forza la valutazione della chiamata alla fact nel secondo return dichiarazione così, per esempio cambiando il primo esempio in modo che T è un non-tipo di modello argomento

template <unsigned T> 
constexpr auto fact() { 
    if(T==1) 
     return 1; 
    return T*fact<T-1>(); 
} 

clang fa fallire con l'errore

fatal error: recursive template instantiation exceeded maximum depth of 256

Live demo

+0

la versione se ha due istruzioni return. Il compilatore usa/standard specifica una specie di evaulazione pigra, dove considera solo l'istruzione return nel blocco if quando a == 1? Altrimenti avremmo lo stesso problema con entrambe le versioni. – bcumming

+0

Grazie @Praetorian. Posso cercarlo da solo (anche se non sono un avvocato di lingua). – bcumming

+0

@Praetorian Non è l'opposto di quello che hai affermato in precedenza. Il tipo di ritorno è dedotto per ogni dichiarazione di ritorno, ma una volta dedotto per * qualsiasi * dichiarazione di ritorno, il tipo di reso è noto e può essere utilizzato come parte della deduzione di dichiarazioni di reso successive. La ragione per cui il tuo esempio con 'template ' fallisce perché il fatto che il tipo di reso 'sia noto non dice nulla sul fatto '' tipo di ritorno, ma è 'fact ' che chiama 'fact ' nel codice dell'OP . – hvd

Problemi correlati