apramc
apramc

Reputation: 1386

Passing an overloaded member function to function template

I would like to have a function, that calls a given member function with the provides variadic input argument. I wrote something like this:

#include <type_traits>
#include <utility>    

struct A {
    constexpr int show(int a, int b) const noexcept {return a + b;}
};

template <typename T, typename MemFn, typename ... Args>
int show(T && obj, MemFn Fn, Args&&... args)
{
    return (obj.*Fn)(std::forward<Args>(args)...);
}

int main()
{
    constexpr A a;
    return show(a, &A::show, 1, 2);
}

and it works just fine, as long as I only have one definition of show method in my struct. As soon as I add something like

struct A {
    constexpr int show(int a, int b) const noexcept {return a + b;}
    constexpr int show(int a) const noexcept {return a * 3;}
};

The compiler can not deduce the type of the member function and it really makes all the sense, but I was wondering if there is a workaround for this problem, like embedding the input arguments types in member function template or something?

Sample code can be found here.

Upvotes: 3

Views: 122

Answers (3)

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136208

I was wondering if there is a workaround for this problem, like embedding the input arguments types in member function template or something?

Use lambdas instead of an object and a member function pointer. E.g.:

struct A {
    constexpr int show(int a, int b) const noexcept {return a + b;}
    constexpr int show(int a) const noexcept {return a * 3;}
};

template <typename F, typename ... Args>
int show(F&& f, Args&&... args) {
    return std::forward<F>(f)(std::forward<Args>(args)...);
}

int main() {
    constexpr A a;
    auto f = [&a](auto... args) { return a.show(std::forward<decltype(args)>(args)...); };
    show(f, 1);
    show(f, 1, 2);
}

Upvotes: 1

Barry
Barry

Reputation: 302748

This is an annoyingly difficult problem, which continuously leads to language proposals in an attempt to address it (P0119, P0834, P1170).

Until then, the question of how to wrap invoking a particular member function on a type, where that member function is either overloaded or a template or takes default arguments, is pretty difficult.

The easiest way to do this is just to write a lambda:

[](A& a, auto&&... args) -> decltype(a.show(FWD(args)...)) { return a.show(FWD(args)...); }

But this is actually not that easy, nor is it particularly convenient - and it really only handles the case where show is invokable on a non-const A. What if we had const and non-const overloads? Or & and &&?

The most complete way to implement this, in my opinion, is to use Boost.HOF with this macro:

#define CLASS_MEMBER(T, mem) boost::hof::fix(boost::hof::first_of(\
    boost::hof::match(                                            \
        [](auto, T& s, auto&&... args)                            \
            BOOST_HOF_RETURNS(s.mem(FWD(args)...)),               \
        [](auto, T&& s, auto&&... args)                           \
            BOOST_HOF_RETURNS(std::move(s).mem(FWD(args)...)),    \
        [](auto, T const&& s, auto&&... args)                     \
            BOOST_HOF_RETURNS(std::move(s).mem(FWD(args)...)),    \
        [](auto, T const& s, auto&&... args)                      \
            BOOST_HOF_RETURNS(s.mem(FWD(args)...))),              \
    [](auto self, auto&& this_, auto&&... args)                   \
        BOOST_HOF_RETURNS(self(*FWD(this_), FWD(args)...))        \
    ))

which in your case, you want: CLASS_MEMBER(A, show). That will give you a function object that you can properly invoke:

auto show_fn = CLASS_MEMBER(A, show);
show_fn(a, 1);       // ok, calls a.show(1)
show_fn(a, 1, 2);    // ok, calls a.show(1, 2)
show_fn(a, 1, 2, 3); // error, no matching call - but sfinae friendly

Upvotes: 4

r3mus n0x
r3mus n0x

Reputation: 6144

You can constrain your function using a more specific type for a member function:

template <typename T, typename... Args>
int show(T && obj, int(std::remove_reference_t<T>::*Fn)(int, int) const, Args&&... args)
{
    return (obj.*Fn)(std::forward<Args>(args)...);
}

This definition, however, might be too constrained depending on your use-cases, since now Fn parameter has to exactly match the int(int, int) const signature including possible cv and ref-qualifiers.

Upvotes: 0

Related Questions