1
votes

Consider this example of a function template that takes a function reference as its first argument. It is overloaded based on the function signature of that first argument. The body of each overload feeds the 1st argument function appropriately for it's signature.

template<typename T>
struct MapTtoT { typedef T (type)(const T); };

template<typename T>
std::vector<T> map_vec(
        const typename MapTtoT<T>::type& fnc,
        const std::vector<T>& source)
{
    std::vector<T> dest;
    dest.reserve(source.size());
    for (const auto i : source)
    {
        dest.emplace_back(fnc(i));
    }
    return dest;
}

template<typename T>
struct MapTandVectoT { typedef T (type)(const T, const std::vector<T>&); };

template<typename T>
std::vector<T> map_vec(
        const typename MapTandVectoT<T>::type& fnc,
        const std::vector<T>& source)
{
    std::vector<T> dest;
    dest.reserve(source.size());
    for (const auto i : source)
    {
        dest.emplace_back(fnc(i, source));
    }
    return dest;
}

Because of the overload, a reference to either of these functions can be passed as the 1st arg:

int foo(const int x);
int bar(const int x, const std::vector<int>& v);

And doing so is transparent:

const auto a = map_vec(foo, v);
const auto b = map_vec(bar, v);

The overloading strategy used above won't work for function objects since the object itself doesn't have a signature per se. Suppose the function objects of interest are the following.

class AddNum
{
public:
    AddNum(const int num) : num_(num) {}

    int operator()(const int x) const
    {
        return x + num_;
    }

private:
    const int num_;
};

class AddNumMulSize
{
public:
    AddNumMulSize(const int num) : num_(num) {}

    int operator()(const int x, const std::vector<int>& v) const
    {
        return (x + num_) * v.size();
    }

private:
    const int num_;
};

How can I change the function templates to accept both the function objects and the functions and overload based on how the call should be made?

Specifically, I want this to compile:

const AddNum add2(2);
const auto c = map_vec(add2, v);

const AddNumMulSize add2mulsz(2);
const auto d = map_vec(add2mulsz, v);

The error message that clang gives is quite clear and matches what you would expect.

error: no matching function for call to 'map_vec'

candidate function [with T = int] not viable: no known conversion from 'const AddNum' to 'typename MapTtoT::type &' (aka 'int (&)(const int)') for 1st argument

Update: C++98 version of this question

“Overload” function template based on function object operator() signature in C++98

1
I don't have time to write a full answer now, but you could employ SFINAE: use std::enable_if and create two traits: is_callable_with_T and is_callable_with_T_and_vector.Angew is no longer proud of SO

1 Answers

6
votes

Change your signature to generically take a function object of type F, then you can use expression-SFINAE to restrict overloads based on what F can be called with:

template<typename F, typename T>
auto map_vec(F&& fnc, const std::vector<T>& source)
    -> decltype(void(fnc(std::declval<T>())), std::vector<T>{});

template<typename F, typename T>
auto map_vec(F&& fnc, const std::vector<T>& source)
    -> decltype(void(fnc(std::declval<T>(), source)), std::vector<T>{});

Demo