2016-06-28 39 views
7

Io do i seguenti codici per mostrare la mia domanda:Come posso sapere che il modello C++ è un contenitore o un tipo?

template<T> 
void my_fun(T &obj) 
{ 
    if(obj is a type like float, std::string, double) 
    { 
     perform1() 
    } 
    if(obj is a container like std::vector, std::list) 
    { 
     perform2() 
} 
} 
std::vector<int> abc; 
my_fun(abc); 
int d; 
my_fun(d); 

Quindi le mie domande, come posso conoscere il modello si riferisce ad un tipo semplice o di un contenitore? Grazie.

+0

Oltre a guardare come una cattiva progettazione e mi raccomandando comunque di leggere il disegno, invece, è possibile utilizzare [ * Tipo-tratti *] (http://en.cppreference.com/w/cpp/types#Type_traits_.28since_C.2B.2B11.29). Potrebbe essere necessario implementare specifici tratti del tipo che ti si addicono, come "is_container". –

+1

'std :: string' è ovviamente un contenitore di caratteri, quindi questa domanda presuppone una falsa dicotomia. – MSalters

risposta

2

Avete diverse opzioni a vostra disposizione.

  • Se si desidera un comportamento di default, e cambiare solo per un tipo (o pochi), utilizzare modello di specializzazione per la funzione.

Ad esempio:

template<typename T> 
void myfunc() { /*default*/ } 


template<> 
void myfunc<int>() { /*specialized version for int */} 
  • Se si desidera modificare il comportamento delle vostre funzioni per gruppi generici di tipi, è possibile utilizzare Type Traits (qualcosa come std::is_fundamental). Potrebbe essere necessario implementare i propri tratti del tipo in thiscase.
+0

Una possibile implementazione per tale tratto sarebbe un controllo SFINAE se 'std :: begin (std :: declval ())' è valido. – HolyBlackCat

1

Un contenitore (parametrizzato) è un tipo. È possibile, tuttavia, sovraccaricarlo:

#include <iostream> 
#include <vector> 

template<typename T> 
void my_fun(T &obj) 
{ 
    perform1(); 
} 

template<typename T> 
void my_fun(std::vector<T> &obj) 
{ 
    perform2(); 
} 

int main(void) 
{ 
    int    a; 
    std::vector<int> b; 

    my_fun(a); 
    my_fun(b); 
} 

Se questo non è sufficiente, si potrebbe anche utilizzare std::enable_if<> quindi non c'è bisogno di scrivere che parte due volte.

+1

Non è possibile specializzare parzialmente una funzione. Quello che stai facendo nel tuo codice sta sovraccaricando la funzione con modelli diversi. – Holt

+0

Non è così che specializzi un modello. – rustyx

+0

Se fornisci il codice in una risposta, fornisci il codice con la sintassi corretta che compila effettivamente. –

3

È possibile scrivere il proprio tratto e enable_if su di esso tramite expression SFINAE con sovraccarichi multipli. Ecco una soluzione che utilizza il void_ttrick (che presumibilmente appariranno in C++ 17):

#include <iostream> 
#include <type_traits> 
#include <vector> 

template<typename ...> 
using to_void = void; // maps everything to void, used in non-evaluated contexts 

template<typename T, typename = void> 
struct is_container : std::false_type 
{}; 

template<typename T> 
struct is_container<T, 
     to_void<decltype(std::declval<T>().begin()), 
       decltype(std::declval<T>().end()), 
       typename T::value_type 
     >> : std::true_type // will be enabled for iterable objects 
{}; 

template<typename T> 
void f(T param, typename std::enable_if<is_container<T>::value>::type* = nullptr) 
{ 
    std::cout << "Container\n"; 
} 

template<typename T> 
void f(T param, typename std::enable_if<std::is_fundamental<T>::value>::type* = nullptr) 
{ 
    std::cout << "Fundamental\n"; 
} 

template<typename T> 
void f(T param, 
    typename std::enable_if<!std::is_fundamental<T>::value>::type* = nullptr, 
    typename std::enable_if<!is_container<T>::value>::type* = nullptr) 
{ 
    std::cout << "Other\n"; 
} 

struct Foo{}; 

int main() 
{ 
    int x{};   // fundamental 
    std::vector<int> v; // container 
    Foo s{}; // other 

    f(x); 
    f(v); 
    f(s); 
} 

Live on Coliru

1

my_fun può essere implementata come segue usando SFINAE.

namespace details{ 
    struct A{}; 
    struct B:A{}; 

    // A container will have a begin and an end. Also make it first prerference 
    template<typename T> 
    auto my_fun_impl(T const & obj, B *) -> decltype(obj.begin(),obj.end(),void()) 
    { 
     std::cout<<"Container\n"; 
    } 

    // Default choice 
    template<typename T> 
    auto my_fun_impl(T const & obj,A*) -> void 
    { 
     std::cout<<"Other than Container\n"; 
    } 
} 
template<typename T> 
auto my_fun(T const & obj) -> void 
{ 
    details::my_fun_impl(obj,static_cast<details::B *>(0)); 
} 

nota il passaggio di un puntatore classe Base o Derived qui, altrimenti compilatore si lamentano definizione della funzione ambigua.

Il compilatore cercherà di abbinare la firma esatta di my_fun_impl con B pointer, riuscirà in caso di contenitore. Perché un container avrà begin() e end(), previsto nel tipo di ritorno finale.

In caso di tipo non contenitore, la prima opzione non corrisponde. E come sappiamo che un puntatore della classe Base può contenere un oggetto classe derivato, quindi la corrispondenza predefinita avrà esito positivo.

e di uscita del seguente codice di prova

int main() 
{ 
    my_fun(std::vector<int>{1,2,3}); 
    my_fun(1); 
} 

sarà

Container 
Other than Container 

Demo on coliru

+0

[FYI] Non tutti i contenitori forniscono funzioni membro iniziale e finale. 'std :: queue' mi viene fuori dalla testa. – NathanOliver

+0

@NathanOliver, FWIW, 'std :: queue' non è un contenitore, ma un adattatore contenitore. – chris

+0

@chris Ah si. Quindi penso che tutti i contenitori attuali abbiano inizio e fine. – NathanOliver