2015-07-23 12 views
5

voglio definire un modello di funzione:Funzione modello che corrisponde solo a determinati tipi?

template<typename T> 
void foo(T arg) 

Ma io voglio T a corrispondere solo a certi tipi. Nello specifico, T dovrebbe derivare (magari attraverso l'ereditarietà multipla) da una certa classe base. Altrimenti questo modello non dovrebbe essere incluso nel set di sovraccarico.

Come posso fare questo?

+0

correlati a (http://stackoverflow.com/ q/16976720/1708801) –

+0

@ShafikYaghmour Si noti che non è un duplicato. Voglio limitare un modello * function *, mentre la domanda collegata fa riferimento a un modello * class *. – becko

+0

@ShafikYaghmour Non lo avresti chiuso se questo era un duplicato esatto e avevi una risposta sull'altro? – Barry

risposta

13

Usa SFINAE con std::is_base_of:

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

che includerà solo foo nel sovraccarico di impostare se T eredita da Foo. Si noti che questo include anche basi ambigue e inaccessibili. Se si desidera una soluzione che consente solo per T s che ereditano pubblicamente e senza ambiguità da Foo, allora si può invece utilizzare std::is_convertible:

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

nota l'inversione di argomenti.

Indipendentemente da quale forma si sceglie, si può essere alias per brevità:

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

Questo funziona perché std::enable_if ha un tipo annidato nome type se e solo se il booleano passato è true. Quindi, se std::is_base_of<Foo, T>::value è true, enable_if_t ottiene istanziato a void, come se avessimo scritto:

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

Ma, se T non eredita da Foo, quindi il tipo di tratto valuterà come false, e std::enable_if_t<false> è un fallimento di sostituzione - non c'è typename enable_if<false>::type. Ci si potrebbe aspettare questo per un errore di compilazione, ma s ubstitution f ailure i s n ot un n e rror (sfinae). È solo un errore di deduzione del modello. Pertanto, l'effetto è che foo<T> viene semplicemente rimosso dal set di candidati a sovraccarichi validi in questo caso, non diverso da qualsiasi altro errore di deduzione del modello.

+0

Non trovo 'std :: enable_if_t'. Dovrebbe essere 'std :: enable_if'? – becko

+1

'template using enable_if_t = typename std :: enable_if :: type;' è un'implementazione su una riga di 'enable_if_t'. Attaccalo in 'namespace notstd', e quando il tuo compilatore si aggiorna, cambia' notstd :: enable_if_t' in 'std :: enable_if_t'.L'alias merita il bit extra di boilerplate, la sintassi di 'enable_if' senza di essa è fastidiosa. – Yakk

+0

Questo è quello che voglio, grazie (+1). Ti spiace spiegare, almeno in superficie, come funziona? – becko

5

Tecniche basate su SFINAE come le seguenti;

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

Sono buoni per rimuovere la funzione dall'elenco di overload, che sarebbe un caso generale.

Se si desidera mantenere la funzione nell'elenco, e se scelto come sovraccarico migliore per fallire se il tipo corrisponde a alcuni criteri (come ad esempio un requisito di base qui), è possibile utilizzare static_assert;

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

In C++ 1Z con i concetti lite, si può fare questo:

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

sotto l'attuale implementazione (sperimentale). Che è abbastanza pulito e chiaro. Ci può essere un modo per fare qualcosa di simile:

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

ma non ho lavorato fuori. Si può sicuramente fare:

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

dove abbiamo un concetto personalizzato chiamato derived_from_foo che si applica se e solo se il tipo è derivato da foo. Quello che non so come fare sono i concetti modello - concetti generati da parametri tipo di modello.


In C++ 14, ecco due metodi. In primo luogo, normale SFINAE:

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

qui creiamo un modello che deduce il tipo T dal suo argomento. Prova quindi a dedurre il suo argomento di tipo secondo dal primo argomento.

Il secondo argomento di tipo non ha nome (quindi class=), perché lo stiamo solo utilizzando per un test SFINAE.

Il test è enable_if_t<condition>. enable_if_t<condition> genera il tipo void se condition è vero. Se condition è falso, fallisce in "il contesto immediato", generando un errore di sostituzione.

SFINAE è "Errore di sostituzione non è un errore" - se il tuo tipo T genera un errore nel "contesto immediato" della firma del modello di funzione, questo non genera un errore in fase di compilazione, ma produce invece un errore in questo caso il modello di funzione non viene considerato un sovraccarico valido.

"Contesto immediato" è un termine tecnico qui, ma in pratica significa che l'errore deve essere "abbastanza presto" da essere rilevato. Se richiede la compilazione di corpi di funzioni per trovare l'errore, ciò non è nel "contesto immediato".

Ora, questo non è l'unico modo. Personalmente mi piace nascondere il mio codice SFINAE dietro un gloss di rispettabilità. Qui di seguito, io uso tag dispacciamento a "nascondere" il fallimento da qualche altra parte, invece di metterlo destra sulla parte anteriore nella firma della funzione:

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

qui si crea un tipo di tag spedizione, e permette la conversione alla base. tag ci consente di utilizzare i tipi come valori e utilizzare più normali operazioni C++ su di essi (invece di metaprogrammazione modello simile a <> s in tutto il luogo).

Diamo quindi foo un secondo argomento di tipo tag<Base>, quindi lo costruisco con un tag<T>. Questo non riesce a compilare se T non è un tipo derivato da Base.

live example.

La cosa bella di questa soluzione è che il codice che lo rende non funziona sembra più intuitivo - tag<Unrelated> non può convertire in tag<Base>. Ciò, tuttavia, non impedisce che la funzione venga considerata per la risoluzione del sovraccarico, il che può essere un problema.

Un modo con meno piastra caldaia è:

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

dove si usa il fatto che i puntatori possono essere convertiti sse esiste una relazione derivazione tra di loro.


In C++ 11 (e senza constexpr supporto), per prima cosa scriviamo un aiutante:

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

poi:

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

se non ti piace l'aiutante, otteniamo questa brutta in più:

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

t la seconda tecnica C++ 14 sopra può anche essere tradotta in C++ 11.


È possibile scrivere un alias che fa il test se si desidera: [? Come ho limitare una classe template per alcuni tipi]

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 Ottima risposta, ma per favore vedi il mio ultimo commento alla risposta di Barry. Esiste una scorciatoia per evitare di scrivere lo stesso modello molte volte? Sto usando CLion e penso che non comprenda pienamente C++ 14, quindi sono bloccato con C++ 11. – becko

+0

Alias ​​short-form @becko aggiunto. Si noti tuttavia che la soluzione 'tag' è piuttosto breve. – Yakk

+0

"gloss of rispettabilità", * snort *. le tue risposte [eccellenti come al solito] mi fanno sempre arrabbiare. – Barry

Problemi correlati