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.