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.
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.
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;
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With