0

Given that a template parameter called B is of type C<D> (on instantiation), how can I construct C<A> from B?

Here is a minimalized extract from my code:

template<typename A>
class foo {
    template<typename B> // B is guaranteed to always be of the form C<D>
    // How to write a function here with return type C<A>?
}
Museful
  • 6,711
  • 5
  • 42
  • 68

2 Answers2

8

you can do this with template specialization of a template template parameter

template<typename, typename>
struct meta {};

template<typename A, template<typename> typename C, typename B>
struct meta<A, C<B>> {
    using type = C<A>;
};

meta<A, C<B>>::type will be C<A>

if you want to have handling of default argument in basic cases

template<typename...>
struct meta {};

template<typename A, template<typename...> typename C, typename B, typename ... Ts>
struct meta<A, C<B, Ts...>> {
    using type = C<A, Ts...>;
};
Tyker
  • 2,971
  • 9
  • 21
1

First we write some machinery to replace a type in a template type instance:

template<class In, class Replace>
struct replace_first_type;
template<template<class...>class Z, class T0, class...Ts, class Replace>
struct replace_first_type<Z<T0, Ts...>, Replace> {
  using type=Z<Replace, Ts...>;
};

template<class In, class Replace>
using replace_first = typename replace_first_type<In,Replace>::type;

replace_first< Z, X > takes Z, pattern matches it against any template<class...>, grabs the first argument, and replaces it with X.

This won't work for std::array<int, 7> as 7 isn't a type, but will work for std::vector<int>; vector is actually <T, A> with a default argument. The template<class...> pattern matches templates that take 0 or more classes.

We then apply it:

template<typename A>
class foo {
  template<typename B>
  using result = replace_first<B, A>;
};

now foo<int>::template result< std::vector<double> > is a std::vector<int, std::alloctor<double>>. Which admittedly is a pretty stupid type.

If you have a function:

  template<class B>
  result<B> do_something() {
    return {};
  }

actually returns a value of that type.

We can fix the problem with std::allocator<double> above by recursively replacing the type we replaced...

template<class X, class Src, class Dest>
struct subst_type {
  using type=X;
};
template<class X, class Src, class Dest>
using subst_t = typename subst_type<X, Src, Dest>::type;
template<template<class...>class Z, class...Ts, class Src, class Dest>
struct subst_type<Z<Ts...>, Src, Dest> {
  using type=Z<subst_t<Ts, Src, Dest>...>;
};
template<class Src, class Dest>
struct subst_type<Src, Src, Dest> {
  using type=Dest;
};

and apply to replace first:

template<class In, class Replace>
struct replace_first_type;
template<template<class...>class Z, class T0, class...Ts, class Replace>
struct replace_first_type<Z<T0, Ts...>, Replace> {
  using type=Z<Replace, subst_t<Ts, T0, Replace>...>;
};

and now when we foo<int>::result<std::vector<double>> we get std::vector<int, std::allocator<int>> instead of std::vector<int, std::allocator<double>> (which is a useless type).

Live example.

There is some possibility that we'll get something horribly wrong from recursively replacing A with B in the rest of the arguments of a template, but as noted we definitely get something horrible with common templates if we don't do it.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524