2016-04-09 15 views
11

Suppongo di ricevere due argomenti per un modello, T1 e T2. Se so T1 è di per sé una classe su modelli (ad esempio, un contenitore), e T2 posso essere qualsiasi cosa, è possibile per me per determinare il tipo di modello base per il T1 e ricostruirlo usando T2 come argomento?È possibile districare un modello dai suoi argomenti in C++?

Ad esempio, se ricevo std::vector<int> e std::string, vorrei creare automaticamente std::vector<std::string>. Tuttavia, se mi fosse data std::set<bool> e double, produrrebbe std::set<double>.

Dopo aver esaminato type_traits, blog pertinenti, e altre domande qui, non vedo un approccio generale per risolvere questo problema. L'unico modo che posso attualmente vedere per eseguire questa attività è creare adattatori di template per ogni tipo che può essere passato come T1.

Per esempio, se ho avuto:

template<typename T_inner, typename T_new> 
std::list<T_new> AdaptTemplate(std::list<T_inner>, T_new); 

template<typename T_inner, typename T_new> 
std::set<T_new> AdaptTemplate(std::set<T_inner>, T_new); 

template<typename T_inner, typename T_new> 
std::vector<T_new> AdaptTemplate(std::vector<T_inner>, T_new); 

dovrei essere in grado di utilizzare decltype e si basano su l'overloading degli operatori per risolvere il mio problema. Qualcosa sulla falsariga di:

template <typename T1, typename T2> 
void MyTemplatedFunction() { 
    using my_type = decltype(AdaptTemplate(T1(),T2())); 
} 

Mi manca qualcosa? C'è un approccio migliore?

Perché voglio fare questo?

Sto costruendo una libreria C++ in cui voglio semplificare ciò che gli utenti devono fare per creare modelli modulari. Ad esempio, se un utente desidera creare una simulazione basata su agenti, potrebbe configurare un modello Mondo con un tipo di organismo, un gestore della popolazione, un gestore dell'ambiente e un gestore sistematico.

Ognuno dei manager hanno bisogno anche di conoscere il tipo organismo, in modo da una dichiarazione potrebbe essere simile:

World< NeuralNetworkAgent, EAPop<NeuralNetworkAgent>, 
     MazeEnvironment<NeuralNetworkAgent>, 
     LineageTracker<NeuralNetworkAgent> > world; 

Preferirei mille volte gli utenti non devono ripetere NeuralNetworkAgent ogni volta. Se io sono in grado di cambiare gli argomenti di template, quindi gli argomenti di default possono essere utilizzate e quanto sopra possono essere semplificate a:

World< NeuralNetworkAgent, EAPop<>, MazeEnvironment<>, LineageTracker<> > world; 

Inoltre è più facile convertire da un tipo ad un altro mondo, senza preoccuparsi di errori di tipo.

Naturalmente, non posso che fare con la maggior parte degli errori usando static_assert e trattare solo con le dichiarazioni più lunghi, ma mi piacerebbe sapere se la soluzione migliore è possibile.

+0

Tutte e tre le risposte che ho ricevuto erano eccellenti. Quello di @Barry è il più completo e quello di T.C. risolve il mio problema di fondo al meglio, ma penso che la risposta di Sam Varshavchik (che ho accettato) sia la più elegante per risolvere la domanda come ho chiesto. Grazie mille a tutti! –

+0

Dato che questo post è un duplicato, penso che quello indicato da @Ben Voight sia molto simile, ma in particolare è focalizzato sull'STL. Se tutti i modelli in questione provengono dalla stessa libreria, ci sono sicuramente trucchi extra che potrebbero essere possibili. Detto questo, l'altra domanda ha anche alcune risposte interessanti e utili. Detto questo, trovo le risposte qui fornite più mirate a questa domanda e più immediatamente utili. –

+0

Sebbene l'altra domanda sia meno ampia, le risposte coprono completamente il tuo caso. Ecco perché la mia bandiera risulta in un banner che dice "La tua domanda ha già una risposta qui". –

risposta

3

Questo sembra funzionare nel modo che stai chiedendo circa, testato con gcc 5.3.1:

#include <vector> 
#include <string> 

template<typename T, typename ...U> class AdaptTemplateHelper; 

template<template <typename...> class T, typename ...V, typename ...U> 
class AdaptTemplateHelper<T<V...>, U...> { 
public: 

    typedef T<U...> type; 
}; 

template<typename T, typename ...U> 
using AdaptTemplate=typename AdaptTemplateHelper<T, U...>::type; 

void foo(const std::vector<std::string> &s) 
{ 
} 

int main() 
{ 
    AdaptTemplate<std::vector<int>, std::string> bar; 

    bar.push_back("AdaptTemplate"); 
    foo(bar); 
    return 0; 
} 

Miglior C++ questione questa settimana.

+0

Ogni volta che penso di sentirmi a mio agio nel trattare con sottili problemi di meta-programmazione e conoscere i limiti del C++, faccio una domanda qui e apprendo qualcosa di completamente nuovo per me. Grazie! –

3

Questo è fondamentalmente due problemi separati: come decomporre un'istanza di un modello di classe nel modello di classe e quindi come prendere un modello di classe e istanziarlo. Andiamo con il principio secondo cui la metaprogrammazione dei modelli è più semplice se tutto è sempre un tipo.

In primo luogo, la seconda parte.Dato un modello di classe, cerchiamo di trasformarla in una classe di metafunction:

template <template <typename...> class F> 
struct quote { 
    template <typename... Args> 
    using apply = F<Args...>; 
}; 

Qui, quote<std::vector> è una classe metafunction. È un tipo concreto con un modello membro apply. Quindi quote<std::vector>::apply<int> ti dà std::vector<int>.

Ora è necessario decomprimere un tipo. Chiamiamolo unquote (almeno mi sembra appropriato). Si tratta di un metafunction che richiede un tipo e produce una classe metafunction:

template <class > 
struct unquote; 

template <class T> 
using unquote_t = typename unquote<T>::type; 

template <template <typename...> class F, typename... Args> 
struct unquote<F<Args...>> { 
    using type = quote<F>; 
}; 

Ora tutto quello che dovete fare è passare la creazione di istanze in unquote e fornire nuovi argomenti desiderati nella classe metafunction sputa fuori:

unquote_t<std::vector<int>>::apply<std::string> 

Per il vostro caso specifico, basta quote tutto:

// I don't know what these things actually are, sorry 
template <class Agent, class MF1, class MF2, class MF3> 
struct World { 
    using t1 = MF1::template apply<Agent>; 
    using t2 = MF2::template apply<Agent>; 
    using t3 = MF3::template apply<Agent>; 
}; 


World< NeuralNetworkAgent, 
    quote<EAPop>, 
    quote<MazeEnvironment>, 
    quote<LineageTracker> 
> w; 
+1

Il problema con questo approccio è che non è possibile utilizzare allocatori, comparatori, ecc. Non predefiniti –

+0

@ T.C. I comparatori sono discutibili di scambiare comunque, cosa succede se il comparatore non è un modello? Meglio che venga fornito esplicitamente. Con l'allocatore, certo, potrei semplicemente scrivere un diverso tipo di "preventivo", non è un grosso problema. – Barry

+1

@ T.C. Lo stesso commento per l'altra soluzione no? – Barry

3

Il tuo problema reale può essere risolto semplicemente prendendo i parametri del modello di modello.

template <class Agent, template<class...> class F1, 
         template<class...> class F2, 
         template<class...> class F3> 
struct World { 
    // use F1<Agent> etc. 
}; 

World<NeuralNetworkAgent, EAPop, MazeEnvironment, LineageTracker > world; 

@ Barry quote è un modo più elegante di fare questo, che è utile per metaprogrammazione più complesso, ma è IMO eccessivo per un caso d'uso questo semplice.

Non è possibile eseguire il binding di specializzazioni di modello arbitrarie a un diverso insieme di argomenti del modello in C++; al massimo puoi gestire un sottoinsieme (principalmente i modelli prendono solo i parametri del tipo, più alcune altre combinazioni che puoi scegliere di supportare), e anche allora ci sono numerosi problemi. La ripetizione corretta std::unordered_set<int, my_fancy_hash<int>, std::equal_to<>, std::pmr::polymorphic_allocator<int>> richiede conoscenze specifiche per i modelli utilizzati.

Problemi correlati