2013-04-23 3 views
22

Bevorzugt ist:'if' mit Template-Parametern oder SFINAE wird bevorzugt? diese

template<typename T> 
bool isNotZero(const T &a) 
{ 
    if (std::is_floating_point<T>::value) return abs(a) > std::numeric_limits<T>::epsilon(); 
    else return a; 
} 

Oder diese :?

template<typename T> 
std::enable_if<std::is_floating_point<T>::value, bool>::type 
isNotZero(const T &a) { return abs(a) > std::numeric_limits<T>::epsilon(); } 

template<typename T> 
std::enable_if<std::is_integral<T>::value, bool>::type 
isNotZero(const T &a) { return a; } 

Normalerweise verwende ich den ersten Typ, um viele Versionen der Funktion zu vermeiden.

Ich glaube, es ist genau das gleiche.

Die erste Version wurde in der Opcode-Stufe und die zweite Version in der Template-Instanziierungsstufe optimiert.

+1

Ich hätte ersteres bevorzugt, da es in diesem speziellen Fall einfacher zu verstehen ist. Letzteres kann nützlich sein, wenn der Körper der Funktion jedoch komplizierter ist. –

Antwort

18

Ich glaube, es ist genau das gleiche.

Ich würde nicht sagen, es ist genau das gleiche.

In der ersten Version, können Sie eine bedingte Anweisung verwenden, die zur Laufzeit ausgewertet, aber die Bedingung, dass die (und ist) ausgeführt wird seinen Zweig entscheidet sein kann entschieden zur Compile-Zeit.

Daher müssen beide Zweige kompiliert werden und kompiliert werden, egal was der Typ der Eingabe ist, obwohl wir zur Kompilierzeit wissen, dass nur einer von ihnen ausgeführt wird und der andere wird tot - I erwarte der Compiler würde hier eine Warnung ausgeben.

Im zweiten Fall, Sie kompilieren nur (und ausführen, natürlich) was für die Art des Eingangs geeignet ist. Dies macht meiner Meinung nach den zweiten Ansatz überlegen.

Nun, obwohl es in dieser speziellen Situation wahrscheinlich keinen Unterschied macht, welchen Ansatz Sie wählen, sollten bedingte Ausführungen, die durch Kompilierungsbedingungen entschieden werden, mittels Kompilierzeitkonstrukten ausgedrückt werden - SFINAE und Template-Überladung, während if sollte für Bedingungen verwendet werden, die vom Laufzeitstatus des Systems abhängen.

Der erste Ansatz wäre zum Beispiel nicht möglich, wenn die beiden Zweige des bedingten enthaltenen Codes nur kompiliert werden, wenn der entsprechende Zweig ausgeführt wird. Betrachten Sie diese zwei Typen:

struct X 
{ 
    X(int) { } 
}; 

struct Y 
{ 
    Y() { } 
}; 

Und die folgende Funktion Vorlage:

template<typename T> 
T foo(const T &a) 
{ 
    if (std::is_constructible<T, int>::value) 
    { 
     return T(42); 
    } 
    else 
    { 
     return T(); 
    } 
} 

Jetzt keine der folgenden Anrufe wäre legal:

foo(X()); // ERROR! X is not default-constructible 
foo(Y()); // ERROR! Y is not constructible from an int 

Dies allein deutet darauf hin, dass im Allgemeinen, Das geeignete Werkzeug für die bedingte Ausführung der Kompilierungszeit ist das Template-Überladen + SFINAE (oder äquivalente Konstrukte, die möglicherweise involviert sind Ving Klassenvorlagen Spezialisierungen).

Sicher, es gibt degeneriert Fälle (wie diese), die Sie mit anderen Tools, aber wenn wir nach konzeptionell richtigen Design-Richtlinien suchen, glaube ich, gibt es einen klaren Gewinner hier.

Die Dinge würden natürlich anders sein, wenn in C++ so etwas wie static if existiert, aber das ist im Moment nicht der Fall - und nicht einmal in naher Zukunft, so scheint es.

+1

Ich würde auch darauf hinweisen, dass alle Compiler-Fehler von inkompatiblen Typen durch die SFINAE-Methode klarer wären und definitiv gefangen würden, anstatt möglicherweise über die implizite Konvertierung das Falsche zu tun. – diverscuba23

+0

@ diverscuba23: Wenn ich richtig verstehe, was du meinst, dann wären ja im generellen Fall Fehlermeldungen klarer - Erwähnung, keine Überlastung könnte gefunden werden, und warum Ersatz fehlgeschlagen ist –

+0

ja, du verstehst mich richtig. – diverscuba23

4

Momentan würde ich lieber SFINAE verwenden. Die Verwendung von SFINAE erfordert keine Optimierung, da Sie explizit zulassen, dass je nach Situation nur eine der Funktionen aufgerufen wird. Es gibt keine Optimierung, die ausgeführt werden muss, nur weil die entsprechende Funktion zur Laufzeit ohne Entscheidung aufgerufen wird.

Mit einer if bedingten Anweisung wird das Programm angewiesen, die Entscheidung zur Laufzeit zu treffen. Natürlich könnte diese weg optimiert werden, kann aber nicht sein. Dies führt wahrscheinlich dazu, dass beide Zweige kompiliert werden, unabhängig davon, welche für ein bestimmtes Template-Argument tatsächlich ausgeführt wird. Dies bedeutet, dass der Code in jedem Zweig für jedes gegebene Template-Argument syntaktisch und semantisch korrekt sein muss.

Vielleicht werden wir eines Tages eine static if haben, mit dem Sie die if Anweisung als Kompilierung-Zustand zu schreiben erlauben würde, aber es gibt einige strong feelings about this im Moment:

Die static if Merkmal kürzlich vorgeschlagen C++ ist grundsätzlich gescheitert, und seine Einführung wäre ein Desaster für die Sprache.

jedoch in naher Zukunft (mit dem Ziel für eine Veröffentlichung etwa C++ 14) können wir constraints (aka Konzepte-lite) haben, mit dem Sie die Funktionen wie so zu schreiben, würde es ermöglichen:

template<Floating_point T> 
bool isNotZero(const T &a) { return abs(a) > std::numeric_limits<T>::epsilon(); } 

template<Integral T> 
bool isNotZero(const T &a) { return a; } 

welche zum Schreiben entspricht:

template<typename T> 
    requires Floating_point<T>() 
bool isNotZero(const T &a) { return abs(a) > std::numeric_limits<T>::epsilon(); } 

template<typename T> 
    requires Integral<T>() 
bool isNotZero(const T &a) { return a; } 

Die Floating_point und Integral Einschränkungen sind nur constexpr Prädikate auf ihre Vorlage Argumente, die bei der Kompilierung geprüft Zeit und beteiligen sich an Überladung Auflösung. Dies wird der bevorzugte Weg sein, um einen solchen Satz von Funktionen zu schreiben.

+0

Hinweis aus der Zukunft: C++ 14 hat keine Konzepte, wohl aber C++ 17. – Yay295