Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to disentangle a template from its arguments in C++? [duplicate]

Assume I receive two arguments to a template, T1 and T2. If I know T1 is itself a templated class (e.g., a container), and T2 can be anything, is it possible for me to determine the base template type for T1 and rebuild it using T2 as its argument?

For example, if I receive std::vector<int> and std::string, I would want to automatically build std::vector<std::string>. However if I were given std::set<bool> and double, it would produce std::set<double>.

After reviewing type_traits, relevant blogs, and other questions here, I don't see a general approach to solving this problem. The only way I can currently see to accomplish this task is to build template adapters for each type that could be passed in as T1.

For example, if I had:

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

I should be able to use decltype and rely on operator overloading to solve my problem. Something along the lines of:

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

Am I missing something? Is there a better approach?

WHY do I want to do this?

I'm building a C++ library where I want to simplify what users need to do to build modular templates. For example, if a user wants to build an agent-based simulation, they might configure a World template with an organism type, a population manager, an environment manager, and a systematics manager.

Each of the managers also need to know the organism type, so a declaration might look something like:

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

I'd much rather users not have to repeat NeuralNetworkAgent each time. If I am able to change template arguments, then default arguments can be used and the above can be simplified to:

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

Plus it's easier to convert from one world type to another without worrying about type errors.

Of course, I can deal with most errors using static_assert and just deal with the longer declarations, but I'd like to know if a better solution is possible.

like image 640
Charles Ofria Avatar asked Apr 09 '16 02:04

Charles Ofria


3 Answers

This seems to work in the manner you're asking about, tested with 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;
}

Best C++ question this week.

like image 82
Sam Varshavchik Avatar answered Nov 15 '22 06:11

Sam Varshavchik


This is basically two separate problems: how to decompose an instantiation of a class template into the class template, and then how to take a class template and instantiate it. Let's go with the principle that template metaprogramming is easier if everything is always a type.

First, the second part. Given a class template, let's turn it into a metafunction class:

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

Here, quote<std::vector> is a metafunction class. It is a concrete type that has a member template apply. So quote<std::vector>::apply<int> gives you std::vector<int>.

Now, we need to unpack a type. Let's call it unquote (at least that seems appropriate to me). This is a metafunction that takes a type and yields a metafunction class:

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

Now all you need to do is pass the instantiation into unquote and provide the new args you want into the metafunction class it spits out:

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

For your specific case, just quote everything:

// 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;
like image 3
Barry Avatar answered Nov 15 '22 06:11

Barry


Your actual problem can be solved by just taking template template parameters.

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's quote is a fancier way of doing this, which is useful for more complex metaprogramming, but is IMO overkill for a use case this simple.

Rebinding arbitrary template specializations to a different set of template arguments is not possible in C++; at most you can deal with a subset (primarily templates only taking type parameters, plus some other combinations you may choose to support), and even then there are numerous problems. Correctly rebinding std::unordered_set<int, my_fancy_hash<int>, std::equal_to<>, std::pmr::polymorphic_allocator<int>> requires knowledge specific to the templates used.

like image 3
T.C. Avatar answered Nov 15 '22 04:11

T.C.