Zizheng Tai
Zizheng Tai

Reputation: 6616

Achieve functor overloading through composition

Given some existing functors:

struct incr {
    int operator()(int x) const { return x + 1; }
};

struct rep_str {
    std::string operator()(const std::string& s) const { return s + s; }
};

I'm wondering if it's possible to achieve something like this:

auto f = overload<incr, rep_str>();
f(1);        // returns 2
f("hello");  // returns "hellohello"

Multiple overloads may look like:

auto f = overload<fa, fb, fc, ...>();
// or...
auto g = overload<fa, overload<fb, overload<fc, ...>>>();

I'm thinking maybe use SFINAE with std::result_of_t or something like that, but haven't figured out how.

Upvotes: 4

Views: 157

Answers (3)

rahnema1
rahnema1

Reputation: 15837

this can be done using template specialization:

#include <string>
#include <iostream>
template <typename...Args>
struct overload{

};
template <> struct overload<int>{
    int operator()(int x) const { return x + 1; }
};
template <> struct overload< std::string>{
    std::string operator()(const std::string& s) const { return s + s; }
};
template <typename...Args >
auto f(Args...arg){
    overload<Args...> func;
    return func(arg...);
}
int main()
{
    std::cout << f(3) << std::endl << f(std::string("Hello"));    
}

Note: two answers by @Brian and @md5i more general and elegant and perfect and better than this.

Upvotes: 0

md5i
md5i

Reputation: 3083

Brian's answer is better, IMHO, but since I worked on it, here's mine:

#include <type_traits>
#include <utility>

template <typename... Fns>
struct overload;

template <typename Fn, typename... Fns>
struct overload<Fn, Fns...>
{
    template <typename... T>
    std::result_of_t<Fn(T...)> operator()(T && ... args) const {
        return Fn()(std::forward<T>(args)...);
    }

    using next = overload<Fns...>;

    template <typename... T>
    std::result_of_t<next(T...)> operator()(T && ... args) const {
        return next()(std::forward<T>(args)...);
    }
};

Upvotes: 2

Brian Bi
Brian Bi

Reputation: 119099

You don't need anything too fancy: just inherit from all the arguments and use using-declarations to bring in operator() from the base classes. However, in the variadic case, you can't have a pack expansion in a using-declaration, so you have to use a recursive approach, like so:

template <class... Ts>
struct overload {}; // only used for empty pack

template <class T>
struct overload<T> : private T {
    using T::operator();
};

template <class T1, class T2, class... Ts>
struct overload<T1, T2, Ts...> : private T1, overload<T2, Ts...> {
    using T1::operator();
    using overload<T2, Ts...>::operator();
};

Upvotes: 8

Related Questions