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

3

12

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.

Thorsten answered 9/4, 2016 at 2:16 Comment(3)
All three answers I got were excellent. The one by @Barry is the most thorough and the one by T.C. solves my underlying problem the best, but I think the response by Sam Varshavchik (which I accepted) is the most elegant for solving the question as I asked it. Thank you all very much!Thorsten
As for this post being a duplicate, I do think the one pointed out by @Ben Voight is very similar, but has a focus on the STL in particular. If all of the templates in question come from the same library, there are certainly extra tricks that might be possible. That said, the other question also has some interesting and useful answers. That said, I find the answers provided here more targeted to this question and more immediately useful.Thorsten
Although the other question is less broad, the answers completely cover your case. That's why my flag results in a banner saying "Your question already has an answer here".Clarinda
K
3

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.

Kheda answered 9/4, 2016 at 2:39 Comment(1)
Every time I think I'm becoming comfortable dealing with subtle meta-programming issues and knowing the bounds of C++, I ask a question here and learn something entirely new to me. Thanks!Thorsten
R
3

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;
Rainfall answered 9/4, 2016 at 2:50 Comment(3)
The issue with this approach is that you can't use non-default allocators, comparators, etc.Chorea
@Chorea Comparators are questionable to swap anyway, what if the comparator isn't a template? Better to have it be provided explicitly. With allocator, sure, I could just write a different kind of quote, that's not a big deal.Rainfall
@Chorea Same comment for the other solution no?Rainfall
C
3

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.

Chorea answered 9/4, 2016 at 3:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.