简体   繁体   中英

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 .

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

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. Eg:

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);
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM