2015-07-23 14 views
5

Ich möchte eine Funktionsvorlage definieren:Template-Funktion, die nur bestimmten Typen entspricht?

template<typename T> 
void foo(T arg) 

Aber ich will T nur bestimmte Arten entsprechen. Insbesondere sollte T eine bestimmte Basisklasse ableiten (vielleicht durch Mehrfachvererbung). Andernfalls sollte diese Vorlage nicht in der Überladungsgruppe enthalten sein.

Wie kann ich das tun?

+0

Related to (http://stackoverflow.com/ q/16976720/1708801) –

+0

@ShafikYaghmour Beachten Sie, dass es sich nicht um ein Duplikat handelt. Ich möchte eine * Funktion * Vorlage einschränken, während die verknüpfte Frage sich auf eine * Klasse * Vorlage bezieht. – becko

+0

@ShafikYaghmour Hätten Sie es nicht geschlossen, wenn es sich um ein exaktes Duplikat handelt und Sie eine Antwort auf das andere erhalten haben? – Barry

Antwort

13

Verwendung SFINAE mit std::is_base_of:

template <typename T, 
      typename = std::enable_if_t< 
       std::is_base_of<Foo, T>::value 
      >> 
void foo(T arg); 

dass nur foo in der Überlastung gesetzt, wenn T erbt von Foo enthalten wird. Beachten Sie, dass dies auch mehrdeutige und unzugängliche Basen beinhaltet. Wenn Sie eine Lösung wollen, die nur für T s ermöglicht, die öffentlich und eindeutig von Foo erben, dann können Sie stattdessen std::is_convertible verwenden:

template <typename T, 
      typename = std::enable_if_t< 
       std::is_convertible<T*, Foo*>::value 
      >> 
void foo(T arg); 

Notiere die Umkehrung von Argumenten.

Unabhängig davon, welche Form Sie wählen, kann es aus Gründen der Kürze aliased werden:

template <typename T> 
using enable_if_foo = std::enable_if_t<std::is_base_of<Foo, T>::value>; 

template <typename T, 
      typename = enable_if_foo<T>> 
void foo(T arg); 

Dies funktioniert, weil std::enable_if eine verschachtelte Art type benannt hat, wenn und nur wenn der boolean bestand in true ist. Also, wenn std::is_base_of<Foo, T>::valuetrue ist, wird enable_if_t zu void instanziiert, als ob wir geschrieben hatte:

template <typename T, 
      typename = void> 
void foo(T arg); 

Aber wenn T erbt nicht von Foo, dann wird der Typ Eigenschaft als false bewerten und std::enable_if_t<false> eine Substitution Versagen - Es gibt keine typename enable_if<false>::type. Man könnte dies zu einem Compiler-Fehler erwarten, aber s ubstitution f ailure i s n ot a n e rror (sfinae). Es ist nur ein Schablonenabzugsfehler. Der Effekt ist also, dass foo<T> in diesem Fall einfach aus der Menge der brauchbaren Überladungskandidaten entfernt wird, nicht anders als bei jedem anderen Vorlagenabzugsfehler.

+0

Ich finde 'std :: enable_if_t' nicht. Sollte es 'std :: enable_if' sein? – becko

+1

'template using enable_if_t = typenname std :: enable_if :: type;' ist eine einzeilige Implementierung von 'enable_if_t'. Stick es in 'Namespace notstd', und wenn Ihr Compiler aktualisiert, wechseln Sie' notstd :: enable_if_t' zu 'std :: enable_if_t'.Der Alias ​​ist das extra bisschen Boilerplate wert, die Syntax von 'enable_if' ohne sie ist nervig. – Yakk

+0

Das ist was ich will, danke (+1). Macht es Ihnen etwas aus, zumindest oberflächlich zu erklären, wie das funktioniert? – becko

5

SFINAE-basierte Techniken wie die folgenden;

template <typename T, 
    typename Test = std::enable_if_t<std::is_base_of<Foo, T>::value>> 
void foo(T arg); 

Sind gut, die Funktion aus der Überladungsliste zu entfernen - was ein allgemeiner Fall wäre.

Wenn Sie möchten, dass die Funktion in der Liste bleibt und als beste Überlast ausgewählt wird, die fehlschlägt, wenn der Typ einigen Kriterien entspricht (z. B. eine Basisanforderung), kann die verwendet werden;

template <typename T> 
void foo(T arg) 
{ 
    static_assert(std::is_base_of<Foo, T>::value, "failed type check"); 
    // ... 
} 
3

In C++ 1z mit Konzepten lite, können Sie dies tun:

template<class T> 
requires std::is_base_of<Foo, T>{}() 
void foo(T arg) { 
} 

unter der aktuellen (experimentell) Umsetzung. Welches ist ziemlich sauber und klar. Es kann ein Weg sein, um so etwas wie:

template<derived_from<Foo> T> 
void foo(T arg) { 
} 

aber ich habe es nicht geklappt hat. Sie können auf jeden Fall tun:

template<derived_from_foo T> 
void foo(T arg){ 
} 

, wo wir ein eigenes Konzept derived_from_foo genannt haben, die Art gilt genau dann, wenn von foo abgeleitet ist. Was ich nicht kann, sind Template-Konzepte - Konzepte, die aus Template-Typ-Parametern generiert werden.


In C++ 14, hier sind zwei Methoden. Zuerst normalen SFINAE:

template<class T, 
    class=std::enable_if_t<std::is_base_of<Foo, T>{}> 
> 
void foo(T arg) { 
} 

hier schaffen wir eine Vorlage, die den Typ T von seinem Argument folgert. Es versucht dann, sein zweites Argument vom ersten Argument abzuleiten.

Das Argument des zweiten Typs hat keinen Namen (daher class=), weil wir es nur für einen SFINAE-Test verwenden.

Der Test ist enable_if_t<condition>. enable_if_t<condition> generiert den Typ void, wenn condition wahr ist. Wenn condition falsch ist, schlägt es in "dem unmittelbaren Kontext" fehl und erzeugt einen Substitutionsfehler.

SFINAE ist "Substitutionsfehler ist kein Fehler" - wenn Ihr Typ T einen Fehler im "unmittelbaren Kontext" der Signatur der Funktionsvorlage generiert, generiert dies keinen Fehler bei der Kompilierung, sondern führt stattdessen dazu Die Funktionsvorlage wird in diesem Fall nicht als gültige Überladung betrachtet.

"Sofortiger Kontext" ist ein technischer Begriff hier, aber im Grunde bedeutet es, dass der Fehler "früh genug" sein muss, um abgefangen zu werden. Wenn es erforderlich ist, Körper von Funktionen zu kompilieren, um den Fehler zu finden, ist das nicht im "unmittelbaren Kontext".

Nun, dies ist nicht die einzige Möglichkeit. Ich persönlich mag es, meinen SFINAE-Code hinter einem Glanz der Anständigkeit zu verstecken. Im Folgenden verwende ich Tag woanders zu „verstecken“ das Scheitern Disposition, anstatt sie ganz vorne in der Funktion Signatur setzen:

template<class T> 
struct tag { 
    using type=T; 
    constexpr tag(tag const&) = default; 
    constexpr tag() = default; 
    template<class U, 
    class=std::enable_if_t<std::is_base_of<T,U>{}> 
    > 
    constexpr tag(tag<U>) {} 
}; 

struct Base{}; 
struct Derived:Base{}; 

template<class T> 
void foo(T t, tag<Base> = tag<T>{}) { 
} 

wir hier eine tag Versandart erstellen, und es ermöglicht die Konvertierung zur Basis. tag können wir mit den Typen als Werte Wert, und verwenden Sie mehr normale C++ -Operationen auf ihnen (anstelle von Template-wie Metaprogrammierung <> s überall).

Wir geben dann foo ein zweites Argument vom Typ tag<Base>, dann konstruieren Sie es mit einem tag<T>. Dies kann nicht kompiliert werden, wenn T kein abgeleiteter Typ von Base ist.

live example.

Das Schöne an dieser Lösung ist, dass der Code, der es nicht funktioniert, intuitiver erscheint - tag<Unrelated> kann nicht in tag<Base> konvertiert werden. Dies verhindert jedoch nicht, dass die Funktion für eine Überladungsauflösung in Betracht gezogen wird, was ein Problem sein kann.

Ein Weg mit weniger Kesselblech ist:

template<class T> 
void foo(T t, Base*=(T*)0) { 
} 

wo wir die Tatsache nutzen, dass Zeiger umgewandelt werden können iff eine Ableitungsbeziehung zwischen ihnen.


In C++ 11 (und ohne constexpr Unterstützung), schreiben wir zunächst einen Helfer:

namespace notstd { 
    template<bool b, class T=void> 
    using enable_if_t=typename std::enable_if<b,T>::type; 
} 

dann:

template<class T, 
    class=notstd::enable_if_t<std::is_base_of<Foo, T>::value> 
> 
void foo(T arg) { 
} 

wenn der Helfer nicht mögen, wir bekommen dieses hässliche Extra:

template<class T, 
    class=typename std::enable_if<std::is_base_of<Foo, T>::value>::type 
> 
void foo(T arg) { 
} 

t Die obige zweite C++ 14-Technik kann auch in C++ 11 übersetzt werden.


Sie können einen Alias ​​schreiben, die den Test nicht, wenn Sie wollen: [? Wie ich eine Template-Klasse, um bestimmte Typen beschränken]

template<class U> 
using base_test=notstd::enable_if_t<std::is_base_of<Base, U>::value>; 

template<class T, 
    class=base_test<T> 
> 
void foo(T arg) { 
} 
+0

+1 Große Antwort, aber bitte sehen Sie meinen letzten Kommentar zu Barry's Antwort. Gibt es eine Kurzschrift, um zu vermeiden, die gleiche Vorlage viele Male zu schreiben? Ich verwende CLion und ich denke, dass es C++ 14 nicht vollständig versteht, also bin ich mit C++ 11 festgefahren. – becko

+0

@becko Kurzform-Alias ​​hinzugefügt. Beachten Sie jedoch, dass die "Tag" -Lösung ziemlich kurz ist. – Yakk

+0

"Glanz der Seriosität", * snort *. Deine [exzellent wie immer] Antworten lösen mich immer auf. – Barry

Verwandte Themen