2015-01-07 5 views
13

Ich experimentiere mit conexpr Funktionen in C++ 14. Der folgende Code, der die Fakultäts Werke berechnet wie erwartet:C++ 14: abgeleitete (automatische) Rückgabetypen von constexpr mit ternären Ausdrücken

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

, wenn es kompiliert wird, wie mit Klirren folgt:

> 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 

Allerdings, wenn ich die fact Definition ändern, um den ternären ? Operator zu verwenden:

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

erhalte ich den folgenden Compiler-Fehler:

> 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"); 

das Problem behoben ist, wenn ich ausdrücklich den Rückgabetyp T-Zustand (statt Auto für die Verwendung des Rückgabetyp abzuleiten)

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

Wenn ich die Template-Parameter entfernen, wird das Muster (die ternäre Version wiederholt schlägt fehl, und die if Version funktioniert)

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

während dies nicht gelingt

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

mit dem folgenden Fehler

> 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. 

Was ist hier los?

Antwort

11

Der resultierende Typ aus der Auswertung eines ternären Ausdrucks ist der common type of its second and third arguments.

durch den Compiler den Rückgabetyp abzuleiten ist, zwingen Sie es beide Argumente zu dem ternären Ausdruck auszuwerten. Dies bedeutet, dass die Rekursion nicht einmal endet, wenn die Abbruchbedingung erreicht ist, denn wenn a==1, muss der Compiler den Rückgabetyp von fact(0), um herauszufinden, zu fact Auswertung weiter rekursive Aufrufe fortzusetzen und endlose Rekursion erfolgt.

Durch Angabe des Rückgabetyps muss fact(0) nicht ausgewertet werden, wenn a==1, und die Rekursion beendet werden kann.


Wie für den Fall mit den beiden return Aussagen, die relevante Standardklausel ist —

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

In Ihrem Beispiel, in dem Aufruf von fact<int>(1), ist der Rückgabetyp, abgeleitet von der ersten return-Anweisung, int, so kann der Rückgabetyp von fact<int>(0) in der zweiten return Anweisung nichts anderes als int auch sein. Dies bedeutet, dass der Compiler den Body von fact<int>(0) nicht auswerten muss und die Rekursion beendet werden kann.

die Tat, wenn Sie als auch Auswertung des Anrufs fact in der zweiten return Aussage zwingen, zum Beispiel durch das erste Beispiel zu ändern, so dass T ein nicht-Typ Argument Vorlage ist

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

Klirren tun scheitern mit dem Fehler

fatal error: recursive template instantiation exceeded maximum depth of 256

Live demo

+0

die if-Version zwei return-Anweisungen hat. Verwendet der Compiler/standard eine Art fauler Bewertung, bei der nur die Rückgabeanweisung im if-Block berücksichtigt wird, wenn a == 1? Sonst hätten wir das gleiche Problem mit beiden Versionen. – bcumming

+0

Danke @Praetorian. Ich kann es selbst nachschlagen (obwohl ich kein Sprachanwalt bin). – bcumming

+0

@Praetorian Es ist nicht das Gegenteil von dem, was Sie zuvor behauptet haben. Der Rückgabetyp wird für jede return-Anweisung abgeleitet, aber sobald er für * any * return-Anweisungen abgeleitet ist, ist der Rückgabetyp bekannt und kann als Teil der Ableitung späterer return-Anweisungen verwendet werden. Der Grund, warum Ihr Beispiel mit 'template ' fehlschlägt, ist, weil 'fact 's Rückgabetyp, der bekannt ist, nichts über 'fact ' 's Rückgabetyp sagt, aber es ist' fact ', das' fact ' im OP-Code aufruft . – hvd