2015-07-23 11 views
5

Chcę zdefiniować szablon funkcję:Funkcja szablonu, która pasuje tylko do niektórych typów?

template<typename T> 
void foo(T arg) 

Ale chcę T dopasować tylko pewne typy. W szczególności, T powinien wyprowadzić (być może poprzez dziedziczenie wielokrotne) pewną klasę podstawową. W przeciwnym razie ten szablon nie powinien być włączony do zestawu przeciążeniowego.

Jak mogę to zrobić?

+0

Podobne do (http://stackoverflow.com/ q/16976720/1708801) –

+0

@ShafikYaghmour Pamiętaj, że to nie jest duplikat. Chcę ograniczyć szablon * funkcji *, podczas gdy połączone pytanie odnosi się do szablonu * klasy *. – becko

+0

@ShafikYaghmour Nie zamknąłbyś go, gdyby był to dokładny duplikat, a na drugiej miałeś odpowiedź? – Barry

Odpowiedz

13

Zastosowanie SFINAE z std::is_base_of:

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

który obejmie tylko foo w przeciążeniem ustawiony jeśli T dziedziczy Foo. Zauważ, że obejmuje to również niejednoznaczne i niedostępne bazy. Jeśli chcesz rozwiązanie, które pozwala jedynie na T s dziedziczących publicznie i jednoznacznie z Foo, można zamiast tego użyć std::is_convertible:

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

Uwaga Odwrócenie argumentów.

Niezależnie od tego, które tworzą wybrać, może to być alias dla zwięzłości:

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

To działa, ponieważ std::enable_if ma typ zagnieżdżony nazwie type wtedy i tylko wtedy, gdy wartość logiczna przekazany jest true. Więc jeśli std::is_base_of<Foo, T>::value jest true, enable_if_t pobiera wystąpienia do void, jakbyśmy pisali:

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

Ale jeśli T nie dziedziczą Foo, to cecha typ oceni jako false i std::enable_if_t<false> awaria podstawienie - nie ma typename enable_if<false>::type. Można spodziewać się tego błędu kompilacji, ale s ubstitution f ailure i s n ot n e rror (sfinae). To po prostu błąd w odliczaniu szablonu. Efekt jest taki, że foo<T> jest po prostu usunięty z zestawu możliwych do przeciążenia kandydatów w tym przypadku, nie różni się od innych błędów odliczania szablonu.

+0

Nie znajduję 'std :: enable_if_t'. Czy powinno to być 'std :: enable_if'? – becko

+1

'template using enable_if_t = nazwa-pliku std :: enable_if :: type;' to jednoliniowa implementacja 'enable_if_t'. Przyklej go w 'namespace notstd', a gdy twój kompilator zaktualizuje, przełącz' notstd :: enable_if_t' na 'std :: enable_if_t'.Pseudonim jest wart dodatkowej części tablicy, składnia 'enable_if' bez tego jest denerwująca. – Yakk

+0

To jest to, co chcę, dzięki (+1). Czy możesz wyjaśnić, przynajmniej na pierwszy rzut oka, jak to działa? – becko

5

Techniki oparte na SFINAE, takie jak następujące;

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

Czy dobrze jest usunąć funkcję z listy przeciążenia - co byłoby przypadkiem ogólnym.

Jeśli chcesz zachować funkcję na liście i jeśli wybrano ją jako najlepsze przeciążenie, aby zakończyć się niepowodzeniem, jeśli typ spełnia pewne kryteria (np. Wymagania podstawowe tutaj), można użyć static_assert;

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

w C++ 1z z pojęciami lite, można to zrobić:

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

ramach obecnego (doświadczalnie) realizacji. Co jest całkiem czyste i jasne. Nie może być sposób zrobić coś takiego:

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

ale nie działało to na zewnątrz. na pewno można zrobić:

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

gdzie mamy własny koncept o nazwie derived_from_foo że stosuje IFF typu pochodzi od foo. Nie wiem, jak to zrobić, to koncepcje szablonów - pojęcia generowane z parametrów typu szablonu.


W C++ 14, są dwie metody. Po pierwsze, normalne SFINAE:

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

tutaj tworzymy szablon, który rozpoznaje typ T ze swojego argumentu. Następnie próbuje wyprowadzić swój argument typu sekund z pierwszego argumentu.

Drugi argument typu nie ma nazwy (stąd class=), ponieważ używamy go tylko do testu SFINAE.

Test to enable_if_t<condition>. enable_if_t<condition> generuje typ void, jeśli condition jest prawdziwy. Jeśli condition ma wartość false, nie powiedzie się w "bezpośrednim kontekście", generując błąd zastąpienia.

SFINAE to "Błąd podstawienia nie jest błędem" - jeśli typ T generuje błąd w "bezpośrednim kontekście" sygnatury szablonu funkcji, nie powoduje to błędu podczas kompilacji, ale powoduje szablon funkcji nie jest w tym przypadku uważany za prawidłowe przeciążenie.

"Natychmiastowy kontekst" jest terminem technicznym, ale w zasadzie oznacza to, że błąd musi być "wystarczająco wcześnie", aby można go było złapać. Jeśli wymaga skompilowania ciał funkcji, aby znaleźć błąd, nie jest to "w bezpośrednim kontekście".

To nie jest jedyny sposób. Osobiście lubię ukrywać mój kod SFINAE za błyszczącym szacunkiem. Poniżej używam tag wysyłający do „ukrycia” awarię gdzieś indziej, zamiast uczynić ją aż przodu w podpisie funkcji:

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>{}) { 
} 

tu utworzyć typ tag wysyłki, a to umożliwia konwersję do bazy. tag pozwala nam z wartościami jako wartości i używać bardziej normalnych operacji C++ na nich (zamiast szablonu jak metaprogramowanie <> s wszędzie).

Następnie podajemy foo drugi argument typu tag<Base>, a następnie konstruujemy go za pomocą tag<T>. Nie uda się skompilować, jeśli T nie jest typem pochodnym od Base.

live example.

Zaletą tego rozwiązania jest to, że kod, który sprawia, że ​​nie działa, wydaje się bardziej intuicyjny - tag<Unrelated> nie można przekonwertować na tag<Base>. Nie uniemożliwia to jednak rozważenia tej funkcji dla rozdzielczości przeciążenia, co może stanowić problem.

Droga z mniejszą płytą kotła wynosi:

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

gdzie wykorzystujemy fakt, że wskaźniki mogą być zamienione wtw istnieje związek wyprowadzenie między nimi.


w C++ 11 (i bez constexpr wsparcia), najpierw napisać pomocnika:

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

następnie:

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

jeśli nie podoba ci się pomocnika, otrzymujemy to brzydkie dodatkowe:

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

t on druga technika C++ 14 powyżej może być również przetłumaczona na C++ 11.


Możesz napisać alias, który wykonuje badanie, jeśli chcesz [? Jak mogę ograniczyć klasę szablonu do niektórych rodzajów]

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 Świetna odpowiedź, ale proszę zobaczyć mój ostatni komentarz do odpowiedzi Barry'ego. Czy istnieje skrót, aby uniknąć wielokrotnego pisania tego samego szablonu? Używam CLion i myślę, że nie w pełni rozumie C++ 14, więc utknąłem w C++ 11. Dodano – becko

+0

skrócony alias skrótu. Zauważ jednak, że rozwiązanie 'tag' jest dość krótkie. – Yakk

+0

"połysk szacunku", * prychnięcie *. twoje [doskonałe jak zwykle] odpowiedzi zawsze mnie psują. – Barry

Powiązane problemy