3

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.

nikolas
  • 8,707
  • 9
  • 50
  • 70

2 Answers2

3

Expansion of variadic packs can handle a so-called pattern. So you can use one partial specialization for everything.

template replace_type<
    typename R, typename... Args,
    typename X, typename Y
>
struct replace_type<R(Args...), X, Y>
{
    typedef typename replace_type<
        R, X, Y
    >::type type(
        typename replace_type<
            Args, X, Y
        >::type...
    );
};
Luc Danton
  • 34,649
  • 6
  • 70
  • 114
2
template < typename ... A >
struct stack { };

template < typename Stack, typename T >
struct push_front;

template < typename T, typename ... A >
struct push_front<stack<A...>,T> {
    typedef stack<T, A ... > type;
};

template < typename Ret, typename Args >
struct build_fun;

template < typename Ret, typename ... A >
struct build_fun<Ret, stack<A...> > {
    typedef Ret(*fptr)(A...);
    typedef decltype(*static_cast<fptr>(0)) type;
};

template < typename Match, typename Rep, typename Target >
struct replace_match { typedef Target type; };

template < typename Match, typename Rep >
struct replace_match<Match, Rep, Match> { typedef Rep type; };

template < typename Match, typename Rep, typename ... Types >
struct replace;

template < typename Match, typename Rep, typename Head, typename ... Tail >
struct replace<Match,Rep,Head,Tail...>
{
    typedef typename replace_match<Match,Rep,Head>::type my_match;

    typedef typename replace<Match, Rep, Tail...>::type next_set;

    typedef typename push_front<next_set, my_match>::type type;
};

template < typename Match, typename Rep >
struct replace<Match,Rep>
{
    typedef stack<> type;
};

template < typename Sig, typename Match, typename Rep>
struct replace_fun_args;

template < typename R, typename Match, typename Rep, typename ... Args >
struct replace_fun_args
{
    typedef typename replace<Match, Rep, Args...>::type arg_stack;
    typedef typename build_fun<R,arg_stack>::type type;
};

#include <iostream>
#include <typeinfo>

int main() {

    replace<int,char,double,unsigned int, int, char*>::type t;

    std::cout << typeid(build_fun<void,decltype(t)>::type).name() << std::endl;
}

There's probably a way to just use packs instead of the stack template...need to look up how to build a pack from types.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
  • "Thanks for figuring that out for me, Eddie!" :P – Edward Strange May 08 '12 at 18:01
  • Thanks for figuring that out for me, Eddie! Sorry for laying low, but I have been playing around with what you've done there. To be honest, I still haven't gotten a grasp of everything you do, especially what the function pointer is doing in that context, but I'll manage it at some point (hopefully) – nikolas May 08 '12 at 19:16
  • Okay I think I've got it. `build_fun>::type` maps out to `T (&)(Params...)` - this is nice! Thank you – nikolas May 08 '12 at 19:59
  • I couldn't find another way to create the function type than making a function pointer type first unfortunately. Various syntaxes I tried did not work. Sorry for late response - was writing boost.bind in C :P – Edward Strange May 08 '12 at 20:24
  • I am done working out a version of your approach in my coding style. I have also updated my post with a somewhat verbose explanation of how this solution works. I hope I does still represent the essence of your idea quite well. Thank you for your help! – nikolas May 08 '12 at 22:39