2012-12-25 4 views
9

Ho unTradurre uno std :: tupla in un parametro di modello pacchetto

typedef std::tuple<A, B> TupleType; 

e vorrei utilizzare l'elenco delle classi per un "modello".

Supponiamo che io sono:

template<typename... args> 
std::tuple<args...> parse(std::istream &stream) { 
    return std::make_tuple(args(stream)...); 
} 

e che posso usare con successo con:

auto my_tuple = parse<A, B>(ifs); 

è possibile evitare di dover specificare l'elenco di classe A, B se ho già un

typedef std::tuple<A,B> TupleType; 

dove la lista A, B è già presente?

un esempio:

#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE 
#include <iostream> // std::cerr 
#include <fstream> // std::ifstream 
#include <tuple> // std::tuple 

class A { 
public: 
    A(std::istream &); // May throw FooBaarException 
}; 

class B { 
public: 
    B(std::istream &); // May throw FooBaarException 
}; 

template<typename... args> 
std::tuple<args...> parse(std::istream &stream) { 
    return std::make_tuple(args(stream)...); 
} 

int main() { 
    std::ifstream ifs; 
    ifs.exceptions(ifstream::eofbit | ifstream::failbit | ifstream::badbit); 
    int res = EXIT_FAILURE; 
    try { 
    ifs.open("/some/file/path", std::ios::in | std::ios::binary); 
    auto my_tuple = parse<A, B>(ifs); // my_tuple is of the type std::tuple<A,B> 
    /* Here do something interesting with my_tuple */ 
    res = EXIT_SUCCESS; 
    } catch (ifstream::failure e) { 
    std::cerr << "error: opening or reading file failed\n"; 
    } catch (FooBaarException e) { 
    std::cerr << "error: parsing in a constructor failed\n"; 
    } 
    return res; 
} 
+1

Sembra che nel costruttore si voglia leggere dalle stringhe. Tieni presente che l'ordine con cui vengono chiamati i costruttori non è specificato per la tua implementazione di 'parse'. –

+0

@ JohannesSchaub-litb, punto interessante. Anche questo può essere fatto: http://liveworkspace.org/code/MTk2Nj$0 a condizione che i tipi di componenti siano distinti (possibile ma troppo lungo per essere mostrato come un esempio con tipi duplicati). – rici

risposta

5

Il problema di fondo nella vostra situazione sembra essere che si desidera specializzarsi il modello di funzione parse per il caso particolare in cui l'argomento è un modello std::tuple. Sfortunatamente, questo tipo di specializzazione non è possibile con i modelli di funzione.

Tuttavia, è possibile con i modelli di classe.

Così, come primo passo, è possibile definire parse in funzione statica di un struct, come questo:

using std::istream; 
using std::tuple; 
using std::make_tuple; 

struct A { A(const istream &) {} }; 
struct B { B(const istream &) {} }; 

template <typename... Args> 
struct parser 
{ 
    /* Your original function, now inside a struct. 
    I'm using direct tuple construction and an 
    initializer list to circumvent the order-of- 
    construction problem mentioned in the comment 
    to your question. */ 
    static tuple<Args...> parse(const istream &strm) 
    { return tuple<Args...> {Args(strm)...}; } 
}; 

template <typename... Args> 
struct parser<tuple<Args...>> 
{ 
    /* Specialized for tuple. */ 
    static tuple<Args...> parse(const istream &strm) 
    { return parser<Args...>::parse(strm); } 
}; 

È quindi possibile chiamare nel modo desiderato:

int main() 
{ 
    typedef tuple<A,B> tuple_type; 
    auto tup = parser<tuple_type>::parse(std::cin); 
    return 0; 
} 

Come seconda fase, è possibile definire un modello di funzione (di nuovo) che passa gli argomenti alla giusta specializzazione della struttura:

template <typename... Args> 
auto parse(const istream &strm) -> decltype(parser<Args...>::parse(strm)) 
{ return parser<Args...>::parse(strm); } 

E ora lo si può utilizzare in modo esattamente si voleva:

int main() 
{ 
    typedef tuple<A,B> tuple_type; 
    auto tup = parse<tuple_type>(std::cin); 
    return 0; 
} 

(E si può ancora usare alla vecchia maniera, anche:. auto tup = parse<A,B>(std::cin))


Nota. Come menzionato nel commento a parser :: parse(), ho usato la costruzione di tuple dirette invece di make_tuple per evitare problemi con l'ordine di costruzione degli elementi di tupla. Questo non è direttamente correlato alla tua domanda, ma è una buona cosa da fare. Vedi how to avoid undefined execution order for the constructors when using std::make_tuple.

+0

"Sfortunatamente, questo tipo di specializzazione non è possibile con i modelli di funzione." non è esattamente vero Bene, è vero, ma puoi ottenere lo stesso effetto dall'overloading e dalla deduzione del template. Vedi qui: http://liveworkspace.org/code/MjU4Nj$0 – rici

+0

@rici Non penso che tu possa risolvere il problema descritto nella domanda usando l'overloading, perché la versione esistente di 'parse', che dovrebbe rimanere intatta , è così generico da causare ambiguità. Ma il meccanismo utilizzato per structs/classes è quello della specializzazione di template parziale, che abilita la disambiguazione come descritto sopra. – jogojapan

+0

TBH, non ho idea di cosa voglia fare. Ho letto la domanda più volte e ancora non capisco quale interfaccia vorrebbe presentare.Tuttavia, sarebbe certamente possibile utilizzare la deduzione aggiungendo un argomento non valutato da analizzare, che a volte è una tecnica utile. (ad esempio 'auto a = parse (ifs, Into ());', che può essere aggiunto senza modificare l'interfaccia esistente.) (Sono d'accordo che in questo caso, la struttura è migliore, motivo per cui l'ho usata io stesso :)) – rici

1

L'approccio di base è quello di creare una sequenza di indici 0, ..., std::tuple_size<Tuple>::value - 1 come parametro pacchetto Indices e chiamare la funzione con parse<typename std::tuple_element<Tuple, Indices>::type...>(stream). Probabilmente racchiuderai la logica in una funzione parse_tuple<Tuple>(stream) (e alcune funzioni a cui questo delegato) che alla fine delega a parse<...>(stream).

Innanzitutto, qui è un modello di classe e una funzione per creare una sequenza di indici in base alle dimensioni di un std::tuple. Gli indici sono necessari per ottenere un elenco di tipo da std::tuple:

template <int... Indices> struct indices; 
template <> 
struct indices<-1> {    // for an empty std::tuple<> there is no entry 
    typedef indices<> type; 
}; 
template <int... Indices> 
struct indices<0, Indices...> {  // stop the recursion when 0 is reached 
    typedef indices<0, Indices...> type; 
}; 
template <int Index, int... Indices> 
struct indices<Index, Indices...> { // recursively build a sequence of indices 
    typedef typename indices<Index - 1, Index, Indices...>::type type; 
}; 

template <typename T> 
typename indices<std::tuple_size<T>::value - 1>::type const* 
make_indices() { 
    return 0; 
} 

Con questo in luogo, è abbastanza facile da estrarre la sequenza dei tipi da un std::tuple<T...>:

template<typename T, int... Indices> 
T parse_tuple(std::istream &stream, indices<Indices...> const*) { 
    return parse<typename std::tuple_element<Indices, T>::type...>(stream); 
} 
template <typename T> 
T parse_tuple(std::istream& stream) { 
    return parse_tuple<T>(stream, make_indices<T>()); 
} 
1

v'è uno standard idioma per questo genere di cose. [1]

// Define the "shape" of the template 
template<typename Tuple> struct TupleMap; 
// Specialize it for std::tuple 
template<typename...T> struct TupleMap<std::tuple<T...>> { 
    using type = std::tuple<T...>; // not necessary but saves typing 
    // ... inside here, you have access to the parameter pac 
} 

Ecco un esempio di utilizzo di esso, che potrebbe o non potrebbe soddisfare le vostre aspettative (il vostro esempio in realtà non indica l'utilizzo previsto, dal momento che manca il typedef promettete nella tua domanda): liveworkspace.org .

Poiché il punto è stato sollevato dal punto litb, è possibile forzare la costruzione dei componenti della tupla nell'ordine da sinistra a destra, illustrando un altro idioma interessante: l'ereditarietà del pettine. Vedi lws.

(Dal LWS potrebbero scomparire di nuovo, chi lo sa, io incollare il codice anche qui):

#include <iostream> 
#include <tuple> 
#include <type_traits> 
#include <utility> 

// Define the "shape" of the template 
template<typename Tuple> struct TupleMap; 
// Specialize it for std::tuple 
template<typename...T> struct TupleMap<std::tuple<T...>> { 
    using type = std::tuple<T...>; // not necessary but saves typing 

    type value; 

    template<typename Arg> 
    TupleMap(Arg&& arg) 
     : value(T(std::forward<Arg>(arg))...) { 
    } 

    operator type() { return value; } 
}; 

//Try it out: 
using std::get; // Note 2 
using Numbers = std::tuple<char, double, int>; 

// Note 3 
std::ostream& operator<<(std::ostream& out, const Numbers& n) { 
    return out << get<0>(n) << ' ' << get<1>(n) << ' ' << get<2>(n); 
} 

int main() { 
    std::cout << TupleMap<Numbers>(93.14159); 
    return 0; 
} 

[1] Almeno, penso che sia un linguaggio standard. Lo uso molto e lo considero come il modello "apriscatole".

[2] Questo è necessario (o almeno è il mio stile) per consentire l'uso di get con modelli simili a tuple definiti al di fuori di std. In questo modo, ADL può trovare la definizione appropriata di get senza costringermi ad aggiungere specializzazioni a std::get.In questo modo, è simile allo standard ADL idioma per begin e end.

[3] È possibile cercare SO per un mod fresco per specializzare operator<< per tutte le tuple. Ce n'è una più semplice che può essere usata per specifiche tuple, ma questo è tutto fuori tema per questa domanda, quindi ho fatto qualcosa di semplice e senza dipendenza. Si noti che questo funziona perché l'operatore di conversione in TupleMap

+0

+1 per l'utilizzo di una struttura per la specializzazione. (La ragione per cui ho postato la mia risposta è che penso che la funzione 'parse' dal codice dell'OP dovrebbe tradursi in una funzione statica della struct, piuttosto che una chiamata al costruttore della struct.) – jogojapan

+0

@jogojapan legittima decisione di progettazione. Non vedo che faccia molta differenza, però. Ma i gusti variano. Potresti voler pensare al punto di litb sull'ordine di costruzione, a meno che tu non abbia già guardato la mia versione su lws. – rici