2016-04-09 15 views
11

Załóżmy, że otrzymuję dwa argumenty do szablonu, T1 i T2. Jeśli wiem, że T1 jest samą klasą szablonową (np. Kontenerem), a T2 może być cokolwiek, czy jest możliwe dla mnie określenie podstawowego typu szablonu dla T1 i przebudowanie go za pomocą T2 jako jego argumentu?Czy można rozplątać szablon z jego argumentów w C++?

Na przykład, jeśli otrzymam std::vector<int> i std::string, będę chciał automatycznie zbudować std::vector<std::string>. Jednakże, gdybym otrzymał std::set<bool> i double, to by wyprodukować std::set<double>.

Po przejrzeniu typ_traits, odpowiednich blogów i innych pytań tutaj, nie widzę ogólnego podejścia do rozwiązania tego problemu. Jedynym sposobem, jaki mogę obecnie zobaczyć, aby wykonać to zadanie, jest zbudowanie adapterów szablonów dla każdego typu, które można przekazać jako T1.

Na przykład, gdybym miał:

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

powinienem móc korzystać decltype i polegać na operatora przeciążenia, aby rozwiązać mój problem. Coś na wzór:

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

Czy czegoś brakuje? Czy istnieje lepsze podejście?

DLACZEGO chcę to zrobić?

Buduję bibliotekę C++, w której chcę uprościć to, co użytkownicy muszą zrobić, aby zbudować modułowy szablon. Na przykład, jeśli użytkownik chce zbudować symulację opartą na agentach, może skonfigurować szablon World z typem organizmu, menedżerem populacji, menedżerem środowiska i menedżerem systematyki.

Każdy z menedżerów również wiedzieć, rodzaj organizmu, więc deklaracja może wyglądać mniej więcej tak:

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

Wolałbym użytkownicy nie muszą powtarzać NeuralNetworkAgent każdym razem. Jeśli jestem w stanie zmienić argumenty szablonu, a następnie domyślne argumenty mogą być używane, a powyżej można uprościć do:

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

Plus to łatwiejsze do konwersji z jednego typu do innego świata, nie martwiąc się o błędy typu.

Oczywiście, mogę poradzić sobie z większością błędów za pomocą static_assert i po prostu radzić sobie z dłuższymi deklaracjami, ale chciałbym wiedzieć, czy lepsze rozwiązanie jest możliwe.

+0

Wszystkie trzy odpowiedzi dostałem były doskonałe. Ten przez @Barry jest najbardziej dokładny i jeden przez T.C. najlepiej rozwiązuje mój podstawowy problem, ale myślę, że odpowiedź Sama Varshavchika (którą zaakceptowałem) jest najbardziej elegancka, jeśli chodzi o rozwiązanie tego pytania. Bardzo wam wszystkim dziękuję! –

+0

Co do tego, że ten post jest duplikatem, uważam, że ten, który został wskazany przez @Ben Voight, jest bardzo podobny, ale koncentruje się w szczególności na STL. Jeśli wszystkie omawiane szablony pochodzą z tej samej biblioteki, są z pewnością dodatkowe sztuczki, które mogą być możliwe. To powiedziawszy, inne pytanie ma również kilka interesujących i użytecznych odpowiedzi. To powiedziawszy, uważam, że udzielone tutaj odpowiedzi są bardziej ukierunkowane na to pytanie i są bardziej przydatne. –

+0

Chociaż inne pytanie jest mniej obszerne, odpowiedzi całkowicie pokrywają twoją sprawę. Dlatego moja flaga skutkuje banerem z napisem "Twoje pytanie ma już tutaj odpowiedź". –

Odpowiedz

3

To wydaje się działać w sposób jesteś z prośbą o reagujące 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; 
} 

Najlepsza C++ pytanie w tym tygodniu.

+0

Za każdym razem, gdy myślę, że czuję się komfortowo, zajmując się subtelnymi problemami z meta-programowaniem i znając granice C++, zadaję tutaj pytanie i uczę się czegoś zupełnie nowego dla mnie. Dzięki! –

3

Jest to zasadniczo dwa oddzielne problemy: jak rozłożyć instancję szablonu klasy na szablon klasy, a następnie jak wziąć szablon klasy i utworzyć instancję. Przejdźmy do zasady, że metaprogramowanie szablonów jest łatwiejsze, jeśli wszystko jest zawsze typem.

Po pierwsze, druga część.Biorąc pod szablon klasy, niech przekształcić go w klasie metafunkcji:

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

Tutaj quote<std::vector> jest klasą metafunkcji. Jest to konkretny typ, który ma szablon członkowski apply. Tak więc quote<std::vector>::apply<int> daje std::vector<int>.

Teraz musimy rozpakować typ. Nazwijmy to unquote (przynajmniej wydaje mi się to odpowiednie). Jest to metafunkcji że trwa typu i daje klasę metafunkcji:

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

Teraz wszystko co musisz zrobić, to przekazać instancji do unquote i dostarczyć nowe argumenty, które mają do klasy metafunkcji to wypluwa:

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

dla konkretnego przypadku, po prostu quote wszystko:

// 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

Problem z tym podejściem polega na tym, że nie można używać domyślnych podzielników, komparatorów itp. –

+0

@ T.C. Komparatory są wątpliwe do zamiany, a co jeśli komparator nie jest szablonem? Lepiej mieć to wyraźnie. Przy pomocy przydziału, na pewno mógłbym napisać inny rodzaj "cytatu", to nie jest wielka sprawa. – Barry

+1

@ T.C. Taki sam komentarz do innego rozwiązania nie? – Barry

3

Rzeczywisty problem można rozwiązać, po prostu biorąc parametry szablonu szablonu.

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 to hodowcy sposobem osiągnięcia tego, co jest przydatne dla bardziej złożonego metaprogramowanie, ale to przesada IMO dla przypadku użycia tej prostej.

Ponowne wiązanie dowolnych specjalizacji z szablonem z innym zestawem argumentów szablonu nie jest możliwe w C++; co najwyżej możesz sobie poradzić z podzbiorem (głównie szablony przyjmujące tylko parametry typu plus niektóre inne kombinacje, które możesz wspierać), a nawet wtedy istnieje wiele problemów. Prawidłowe ponowne powiązanie std::unordered_set<int, my_fancy_hash<int>, std::equal_to<>, std::pmr::polymorphic_allocator<int>> wymaga znajomości konkretnych szablonów.

Powiązane problemy