I am writing a meta function replace_type<C, X, Y>
that is supposed to replace all matches of type X
in a compound type C
with Y
. I am currently working on properly getting this to work with callables in C
.
This works:
template replace_type<
typename C, typename X, typename Y,
typename First
>
struct replace_type<C(First), X, Y>
{
typedef typename replace_type<
C, X, Y
>::type type(
typename replace_type<
First, X, Y
>::type
);
};
template replace_type<
typename C, typename X, typename Y,
typename First, typename Second
>
struct replace_type<C(First, Second), X, Y>
{
typedef typename replace_type<
C, X, Y
>::type type(
typename replace_type<
First, X, Y
>::type,
typename replace_type<
Second, X, Y
>::type
);
};
But this obviously is very limited. In my head it seemed obvious that I should be using a variadic template here instead, but when I actually tried applying it, I quickly noticed I have no idea of how to fit it into this scheme.
I thought of implementing it like this:
template replace_type<
typename C, typename X, typename Y,
typename First, typename... Args
>
struct replace_type<C(First, Args...), X, Y>
{
typedef typename replace_type<
C, X, Y
>::type type(
typename replace_type<
First, X, Y
>::type,
// How to recursively do the same with the rest of the arguments?
);
};
This way, I could always access the first parameter to replace it appropriately, then move on to the next, and have another specialized metafunction ready for handling nullary functions as terminal condition for my recursion. Question is, as stated in the source code, how do I start the recursion in this context?
Update
Minimal example:
#include <type_traits>
namespace type_replace_helper
{
template <typename, typename, typename>
struct type_replace_base;
}
template <typename C, typename X, typename Y>
struct type_replace
{
typedef typename std::conditional<
std::is_same<C, X>::value,
Y,
typename type_replace_helper::type_replace_base<
C, X, Y
>::type
>::type type;
};
namespace type_replace_helper
{
template <typename C, typename X, typename Y>
struct type_replace_base
{
typedef C type;
};
template <typename C, typename X, typename Y>
struct type_replace_base<C(), X, Y>
{
typedef typename type_replace<
C, X, Y
>::type type();
};
template <
typename C, typename X, typename Y,
typename First
>
struct type_replace_base<C(First), X, Y>
{
typedef typename type_replace<
C, X, Y
>::type type(
typename type_replace<
First, X, Y
>::type
);
};
template <
typename C, typename X, typename Y,
typename First, typename Second
>
struct type_replace_base<C(First, Second), X, Y>
{
typedef typename type_replace<
C, X, Y
>::type type(
typename type_replace<
First, X, Y
>::type,
typename type_replace<
Second, X, Y
>::type
);
};
}
int main()
{
static_assert(std::is_same<
type_replace<int(int, int), int, long>::type,
long(long, long)
>::value, "int should be replaced by long");
return 0;
}
Update 2
Thanks to Crazy Eddie I've been able to achieve what I wanted. Because it took me so long to understand this beast answer, I figured it might be helpful for others to read a more verbose solution.
The thing I took probably the longest to actually realize: the problem is not really how to separate the function parameters, but transform them into a variadic list of replaced arguments. Thus, the primary objective here is to find a way to replace each argument properly, and store it into another separate argument list. Eddy's solution uses the stack
structure as a wrapper to distinct the two parameter lists, one replaced, one with things left to do.
Once the parameter list has been replaced one by one and was stored in the stack
structure, all that's left to do is pull them out again as a list and construct the function like so: typedef T type(Params...);
, and that's it.
In my coding style this displays as:
template <typename...>
struct stack {};
// Definition only to specialize for actual stacks
template <
typename X, typename Y,
typename Stack, typename... Todo
>
struct list_converter;
// No more arguments to convert, return the gathered stack
template <
typename X, typename Y,
typename... Elems
>
struct list_converter<X, Y, stack<Elems...>>
{
typedef stack<Elems...> type;
};
// Push replaced argument to stack and go to the next argument
template <
typename X, typename Y,
typename... Elems,
typename First, typename... Todo
>
struct list_converter<X, Y, stack<Elems...>, First, Todo...>
{
typedef typename list_converter<
X, Y,
stack<
typename replace_type<First, X, Y>::type,
Elems...
>,
Todo...
>::type type;
};
// Definition only again for stack specialization
template <
typename C, typename X, typename Y,
typename Stack
>
struct function_builder;
// Pull out argument list from the stack and build a function
template <
typename C, typename X, typename Y,
typename... Elems
>
struct function_builder<C, X, Y, stack<Elems...>>
{
typedef typename replace_type<
C, X, Y
>::type type(Elems...);
};
// Specialization for function replacements
// Builds function with replaced return type, and converted
// argument list (recursion starts with empty stack)
template <
typename C, typename X, typename Y,
typename... Params
>
struct replace_type<C(Params...), X, Y>
{
typedef typename function_builder<
C, X, Y,
typename list_converter<
X, Y,
stack<>,
Params...
>::type
>::type type;
};
Please forgive me if there are some syntactical errors in the code above; it is quite a large file already and I tried to only extract relevant information.