2012-01-05 12 views
7

Questa è solo una domanda sullo stile: non mi piace il modo di C++ per la metaprogrammazione del modello che richiede di usare il tipo restituito o di aggiungere un argomento fittizio extra per i trucchi con SFINAE . Così, l'idea mi è venuta è quello di mettere la cosa SFINAE nella definizione argomenti modello stesso, in questo modo:boost :: enable_if non in funzione firma

#include <iostream> 
#include <boost/type_traits/is_array.hpp> 
#include <boost/utility/enable_if.hpp> 
using namespace std; 

template <typename T, typename B=typename boost::enable_if< boost::is_array<T> >::type > void asd(){ 
    cout<<"This is for arrays"<<endl; 
} 

template <typename T, typename B=typename boost::disable_if< boost::is_array<T> >::type > void asd(){ 
    cout<<"This is for NON arrays"<<endl; 
} 

int main() { 
    asd<int>(); 
    asd<int[]>(); 
} 

Questo esempio rende g ++ si lamentano:

../src/afg.cpp:10:97: error: redefinition of ‘template void asd()’

SFINAE ci si lavora, perché se cancello per esempio quello con disable_if, l'errore del compilatore è:

../src/afg.cpp:15:12: error: no matching function for call to ‘asd()’

che è quello che voglio.

Quindi, esiste un modo per eseguire SFINAE non nella firma "normale" di una funzione, vale a dire tipo di ritorno + elenco di argomenti?

EDIT: Questo è alla fine ciò che ho intenzione di provare il codice vero e proprio:

#include <iostream> 
#include <type_traits> 
using namespace std; 

template <typename T, typename enable_if< is_array<T>::value, int >::type =0 > void asd(){ 
    cout<<"This is for arrays"<<endl; 
} 

template <typename T, typename enable_if< !is_array<T>::value, int >::type =0 > void asd(){ 
    cout<<"This is for NON arrays"<<endl; 
} 

int main() { 
    asd<int[]>(); 
    asd<int>(); 
} 

Io uso C++ 0x roba invece di spinta perché fino a quando ho bisogno di C++ 0x per usare i valori predefiniti degli argomenti del template, non vedo alcun motivo per usare boost, che è il suo precursore.

risposta

6

argomenti modello predefinito non fanno parte della firma di modelli di funzione. Ma il tipo di parametri del modello è.Così si può fare quanto segue e essere in grado di sovraccaricarlo

template < 
    typename T, 
    typename boost::enable_if< 
    boost::is_array<T>, int 
    >::type = 0 
> 
void asd() { 
    cout<<"This is for arrays"<<endl; 
} 

template < 
    typename T, 
    typename boost::disable_if< 
    boost::is_array<T>, int 
    >::type = 0 
> 
void asd() { 
    cout<<"This is for arrays"<<endl; 
} 
+0

wow, non sapevo che anche il tipo di modello poteva essere dinamico! Non accettando ancora la tua risposta perché devo verificare prima che questo trucco possa essere applicato al mio codice, ma sono abbastanza sicuro che lo farà. –

8

Dal momento che C++ 11 ha reso possibile, io uso sempre solo enable_if (o al contrario disable_if) all'interno degli argomenti del modello, come si sta facendo. Se/quando ci sono diversi sovraccarichi, allora utilizzo argomenti fittizi, di default del modello che rendono gli elenchi dei parametri del modello diversi per l'aritmetica. Quindi, per riutilizzare il vostro esempio che potrebbe essere:

template< 
    typename T 
    , typename B = typename boost::enable_if< 
     boost::is_array<T> 
    >::type 
> 
void asd() { 
    cout << "This is for arrays" << endl; 
} 

template< 
    typename T 
    , typename B = typename boost::disable_if< 
     boost::is_array<T> 
    >::type 
    , typename = void 
> 
void asd() { 
    cout << "This is for arrays" << endl; 
} 

Un'altra alternativa per non rovinare il tipo di ritorno (che non è disponibile in alcuni casi, ad esempio operatori di conversione) che esiste dal C++ 03 è quello di usare argomenti di default :

template<typename T> 
void 
foo(T t, typename std::enable_if<some_trait<T>::value>::type* = nullptr); 

non faccio uso di questa forma come non mi piace 'scherzi' con i tipi di argomenti tanto quanto con il tipo di ritorno, e per ragioni di coerenza (dal momento che non è fattibile in tutti i casi).

2

Questo potrebbe non essere esattamente quello che stai chiedendo, ma che ne dici di una buona vecchia specializzazione template?

template<typename T> 
struct asd 
{ 
    static void fgh() 
    { 
     std::cout << "not an array\n"; 
    } 
}; 

template<typename T> 
struct asd<T[]> 
{ 
    static void fgh() 
    { 
     std::cout << "an array of unknown size\n"; 
    } 
}; 

template<typename T, size_t N> 
struct asd<T[N]> 
{ 
    static void fgh() 
    { 
     std::cout << "an array of known size\n"; 
    } 
}; 

int main() 
{ 
    asd<int>::fgh(); 
    asd<int[]>::fgh(); 
    asd<int[42]>::fgh(); 
} 
+0

buono, ma è più lungo dell'altra risposta. –

+3

Almeno stampa cose diverse per array e non-array ;-) – fredoverflow

2

So, is there a way to accomplish SFINAE not in the "normal" signature of a function, that is return type + argument list?

Beh, c'è un modo per ottenere lo stesso risultato senza utilizzare SFINAE affatto — sovraccarico:

#include <iostream> 
#include <type_traits> 

void asd_impl(std::true_type&&) 
{ 
    std::cout << "This is for arrays\n"; 
} 

void asd_impl(std::false_type&&) 
{ 
    std::cout << "This is not for arrays\n"; 
} 

template<typename T> 
void asd() 
{ 
    asd_impl(std::is_array<T>()); 
} 

int main() 
{ 
    asd<int>(); 
    asd<int[]>(); 
} 

Questo stile è molto più leggibile IMO, ed è ampiamente utilizzato in template- librerie pesanti come Boost. Spirit perché tende a compilare più velocemente e funziona meglio con i compilatori con supporto modello/SFINAE meno che stellare (ad esempio VC++ e Sun Studio).

Online demo.

+0

+1, per una semplice domanda vero/falso, non è necessario impiegare SFINAE. Diventerà peloso per più di un tratto, dal momento che non puoi semplicemente combinarli con operatori logici. (Il che mi fa pensare che si possa sovraccaricare l'operatore || 'e l'operatore &&' per 'true_type' e' false_type' ... hm.) – Xeo

+0

@Xeo: 'std :: integral_constant 'dovrebbe avere la semantica corretta. – ildjarn

+0

Sì, ma sembra un po 'confuso, ecco perché mi sono interrogato sul sovraccarico dell'operatore. :) Fondamentalmente lo farebbe solo sembrare più bello. – Xeo

8

Beh, io di solito uso queste macro per fare enable_if costruisce molto più pulito (hanno anche il lavoro nella maggior parte dei compilatori C++ 03):

#define ERROR_PARENTHESIS_MUST_BE_PLACED_AROUND_THE_RETURN_TYPE(...) __VA_ARGS__>::type 
#define FUNCTION_REQUIRES(...) typename boost::enable_if<boost::mpl::and_<__VA_ARGS__, boost::mpl::bool_<true> >, ERROR_PARENTHESIS_MUST_BE_PLACED_AROUND_THE_RETURN_TYPE 
#define EXCLUDE(...) typename boost::mpl::not_<typename boost::mpl::or_<__VA_ARGS__, boost::mpl::bool_<false> >::type >::type 

allora si sarebbe definire la funzione come questa:

template <typename T > 
FUNCTION_REQUIRES(is_array<T>) 
(void) asd(){ 
    cout<<"This is for arrays"<<endl; 
} 

template <typename T > 
FUNCTION_REQUIRES(EXCLUDE(is_array<T>)) 
(void) asd(){ 
    cout<<"This is for NON arrays"<<endl; 
} 

l'unica cosa è, è necessario mettere tra parentesi tutto il tipo di ritorno. Se li dimentichi, il compilatore dirà qualcosa come "ERROR_PARENTHESIS_MUST_BE_PLACED_AROUND_THE_RETURN_TYPE" non definito.

Problemi correlati