3

I want to write a template which accepts a pointer to a member function (possibly CV-qualified and/or ref-qualified) while also matching all the relevant types (return value's type, class type and types of the arguments).

Simple version could look like:

template <typename ReturnValue, typename Class, typename... Arguments>
void foo(ReturnValue(Class::* function_ptr)(Arguments...))
{
    // do something with |function_ptr|
}

This works nicely for non-CV-qualified non-ref-qualified member function pointers, but fails to match with pointers to CV-qualified and/or ref-qualified member functions.

Is there a nice way to keep this in a single template and handle all cases by nicely deducing qualifiers somehow?


I could downgrade to a simple template that accepts everything (possibly with some SFINAE around std::is_member_function_pointer). But I would still need to extract the types (return, class and arguments) somewhere, so that would not actually save me any work.

JeJo
  • 30,635
  • 6
  • 49
  • 88
RippeR
  • 1,472
  • 1
  • 12
  • 23
  • I'm not sure the language has improved a lot in this regard. In the past I found myself writing a lot of boilerplate to detect different callable types (because you might also want to accept a lambda, or a std::function, or a class with a function call operator) – AndyG Aug 05 '21 at 21:09
  • How do you expect it to works when you have CV or ref qualified member functions as there is not information about `this` that would allows to select the proper overload. – Phil1970 Aug 05 '21 at 21:41
  • 1
    @AndyG I'm specifically asking for member-function-ptrs here, not any other type of callables. – RippeR Aug 05 '21 at 22:12
  • @Phil1970 CV- or ref-qualifiers are part of the method's type. You're always free to take pointer of the method. If there are multiple ones with the same name you can always cast received pointer to specify which one you want to take. The same thing happens when there are multiple overloads of a functions, e.g. with different types or even number of arguments. Here I don't care how you get the actual member-function-pointer, I only care about receiving it and deducing context inside the template. – RippeR Aug 05 '21 at 22:12

1 Answers1

3

How about pass the member function pointer to the foo and using a helper trait we retrieve the types of class, return, and arguments!

Provide a trait something like:

template<typename Class> struct class_traits final {};
template<typename ReType, typename Class, typename... Args>
struct class_traits<ReType(Class::*)(Args...)> final
{
    using class_type = Class;
    using ret_type = ReType;
};    

// traits helpers
template<typename MemFunctionPtr> using  class_type = typename class_traits<MemFunctionPtr>::class_type;
template<typename MemFunctionPtr> using  ret_type = typename class_traits<MemFunctionPtr>::ret_type;

Now in the foo

#include <functional> // std::invoke

template <typename MemFuncType, typename... Args>
void foo(MemFuncType&& func, Args&&... args) 
{
    // class type can be retrieved!
    class_type<MemFuncType> obj{};

    // types other than void!!
    ret_type<MemFuncType> res = std::invoke(func, obj, std::forward<Args>(args)...); 

    // do something with |function_ptr|

}

Now the main-part of the story. The above will only resolve the non-const, non-volatile, and non-noexcept, etc versions of the member function pointers. Exactly what you asked is missing!

For the rest, we need to provide different traits. Inspired from this post, we can do it by macro, which will do the boilerplate duplications of the traits for us:

template<typename Class> struct class_traits final {};
#define CREATE_CLASS_TRAITS(...) \
template<typename ReType, typename Class, typename... Args> \
struct class_traits<ReType(Class::*)(Args...)__VA_ARGS__> final  \
{ \
    using class_type = Class; \
    using ret_type = ReType; \
}

CREATE_CLASS_TRAITS();
CREATE_CLASS_TRAITS(const);
CREATE_CLASS_TRAITS(volatile);
CREATE_CLASS_TRAITS(const volatile);
CREATE_CLASS_TRAITS(&);
CREATE_CLASS_TRAITS(const&);
CREATE_CLASS_TRAITS(volatile&);
CREATE_CLASS_TRAITS(const volatile&);
CREATE_CLASS_TRAITS(&&);
CREATE_CLASS_TRAITS(const&&);
CREATE_CLASS_TRAITS(volatile&&);
CREATE_CLASS_TRAITS(const volatile&&);
CREATE_CLASS_TRAITS(noexcept);
CREATE_CLASS_TRAITS(const noexcept);
CREATE_CLASS_TRAITS(volatile noexcept);
CREATE_CLASS_TRAITS(const volatile noexcept);
CREATE_CLASS_TRAITS(&noexcept);
CREATE_CLASS_TRAITS(const& noexcept);
CREATE_CLASS_TRAITS(volatile& noexcept);
CREATE_CLASS_TRAITS(const volatile& noexcept);
CREATE_CLASS_TRAITS(&& noexcept);
CREATE_CLASS_TRAITS(const&& noexcept);
CREATE_CLASS_TRAITS(volatile&& noexcept);
CREATE_CLASS_TRAITS(const volatile&& noexcept);
#undef CREATE_CLASS_TRAITS

// traits helpers
template<typename MemFunctionPtr> using  class_type = typename class_traits<MemFunctionPtr>::class_type;
template<typename MemFunctionPtr> using  ret_type = typename class_traits<MemFunctionPtr>::ret_type;

Now we can:

template <typename MemFuncType, typename... Args>
void foo(MemFuncType&& func, Args&&... args) 
{
    class_type<MemFuncType> obj{};

    using Type = decltype(std::invoke(func, obj, std::forward<Args>(args)...));
    static_assert(std::is_same_v<ret_type<MemFuncType>, Type>, "are not same");

    std::invoke(func, obj, std::forward<Args>(args)...);

    // do something with |function_ptr|
}

class MyClass
{
public:
    void func() { std::cout << "MyClass::void func()\n"; }
    void func1() const { std::cout << "MyClass::void func1() const\n"; }
    void func2() const noexcept{ std::cout << "MyClass::void func2() const noexcept\n"; }
    int func3() const { std::cout << "MyClass::func3() const\n";  return {}; }
    int func4(int a, double b) const { std::cout << "MyClass::func3() const"<< a << " " << b << "\n";  return {}; }
};

int main()
{
    foo(&MyClass::func);
    foo(&MyClass::func1);
    foo(&MyClass::func2);
    foo(&MyClass::func3);
    foo(&MyClass::func4, 1, 2.);
    return 0;
}

(See Live Demo Online)

JeJo
  • 30,635
  • 6
  • 49
  • 88
  • Thanks! Its certainly one way to do this but I was asking if there is some syntax to actually handle this without exploding into dozens of options, which it looks like there isn't. Unless someone else will provide some other option, I'll choose this one. – RippeR Aug 05 '21 at 22:09
  • 1
    `noexcept` only is supposed to become deducible (per CWG2355). – Davis Herring Aug 06 '21 at 00:04