1

I want to be able to defer calls to member functions of some object. Therefore, I need to be able to store member function pointers plus parameters as data. My approach uses C++ templates that treat the member function pointers as part of the type. The function pointer should be a template parameter, as I want to be able to select via static_assert at compile time, which member functions are allowed (ie. only setters);

I am using gcc (GCC) 14.1.1 20240507 and C++ 23.

Example Code:

struct Object {
    void set();
    void set(int test);
    int get();
};

// Utils:

template <typename Function>
struct FunctionTraits;

template <
    typename Return,
    typename... Args
> struct FunctionTraits
<Return (Object::*)(Args...)>
{
    using args_tuple_t = std::tuple<Args...>;
    using ret_t = Return;
};

// The member function wrapper:

template <
    typename Function,
    Function function
>
struct SetterTask
{
    Object* obj;
    typename FunctionTraits<decltype(function)>::args_tuple_t args;
};

// pseudo constructor

template <
    typename Return,
    typename... Args
>
auto makeSetter(
        Object* obj,
        Return (Object::*setter)(Args...),
        Args... args
)
{
    auto tuple = std::tuple( args... );
    using SetterType = Return (Object::*)(Args...);  // <-- ERROR
    return SetterTask<SetterType,setter>{            // <-- ERROR
        .obj = obj,
        .args = tuple
    };
}

template <
    typename Function,
    Function function
>
auto run(
        SetterTask<Function, function> setter
)
{
    return std::apply(setter.function,
            std::tuple_cat(
                std::make_tuple(setter.obj),
                setter.args
            )
    );
}

Test program:

void test() {
    void (Object::*pFunc)(int) = &Object::set;
    Object obj;
    auto setter = makeSetter( &obj, pFunc, 10 );
    run(setter);
}

This is the error I get:

template_test.cpp:10:26:   required from here
   10 |         auto setter = makeSetter( &obj, pFunc, 10 );
      |                       ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~
template_test.h:61:9: error: ‘setter’ is not a valid template argument for type ‘void (Object::*)(int)’
   61 |         };
      |         ^
template_test.h:61:9: note: it must be a pointer-to-member of the form ‘&X::Y’

What am I doing wrong? To my understanding, the types are all clear, even overly explicit. Or is this a compiler bug?

This is the critical section:

template <
    typename Return,
    typename... Args
>
auto makeSetter(
        Object* obj,
        Return (Object::*setter)(Args...),
        Args... args
)
{
    auto tuple = std::tuple( args... );
    using SetterType = Return (Object::*)(Args...);  // <-- ERROR
    return SetterTask<SetterType,setter>{            // <-- ERROR
        .obj = obj,
        .args = tuple
    };
}

I also tried:

    using SetterType = decltype(setter);  <-- decltype of pointer
    return SetterTask<SetterType,setter>{
            ...
    };

...and...

    using SetterType = Return (Object::*)(Args...);
    return SetterTask<SetterType,(SetterType)setter>{ <-- explicitly cast pointer
            ...
    };
6
  • 1
    Is there anything wrong with std::function? Commented May 17, 2024 at 10:33
  • 1
    Also, concerning the "plus parameters": std::function is pretty much the big thing that can store "anything" that remotely looks or smells like it's something that can be called. This includes objects created by the std::bind family, or lambda functions, as ways to attach data to the callables. Commented May 17, 2024 at 10:38
  • std::function is wonderful, but to permissive. I want to use the type system to be able to assert at compile time, that only certain member functions to a specific object are valid. Therefore, the function pointer should be part of the type. Commented May 17, 2024 at 10:49
  • 3
    In C++, a runtime value, in this case the function call argument setter: Return (Object::*setter)(Args...), cannot be used as template instantation argument. It does not only apply to pointers to members - it applies to all non-type, non-template template parameters (if nothing changes in C++23 in this area)... Commented May 17, 2024 at 11:15
  • "I want to use the type system to be able to assert at compile time, that only certain member functions to a specific object are valid." - use concepts? Commented May 19, 2024 at 11:02

1 Answer 1

1

As PiotrNycz correctly pointed out, a template "value" parameter cannot be set from a function argument, neither from any runtime value. Quite obvious, if you think about it - template parameters are compile-time constants.

This is my solution:

template_test.h:

#include <tuple>
#include <type_traits>


struct Object {
    double set();
    void set(int test);
    void set2(int test);
    int get();
};

// Allowed member functions:

template <typename Function, Function function>
struct IsAllowedFunction : std::false_type {};

template <> struct IsAllowedFunction
<double (Object::*)(), (double (Object::*)())&Object::set>
: std::true_type {};

template <> struct IsAllowedFunction
<void (Object::*)(int), (void (Object::*)(int))&Object::set>
: std::true_type {};

template <> struct IsAllowedFunction
<void (Object::*)(int), (void (Object::*)(int))&Object::set2>
: std::true_type {};

// Utils:

template <typename Function>
struct FunctionTraits;

template <
    typename Return,
    typename... Args
> struct FunctionTraits
<Return (Object::*)(Args...)>
{
    // the tuple must be saved
    // as full values, not just
    // as reference:
    using args_tuple_t = std::tuple<std::remove_cvref_t<Args>...>;
    using ret_t = Return;
};

// The member function wrapper:

template <
    typename Function,
    Function function
>
struct SetterTask
{
    Object* obj;
    typename FunctionTraits<Function>::args_tuple_t args;
};

// pseudo constructor

template <
    typename Function,
    Function function,
    typename... Args
>
auto makeSetter(
        Object* obj,
        Args... args
)
{
    static_assert(
            IsAllowedFunction<Function,function>::value,
            "Invalid function. Valid functions functionare: 'set', 'set2'"
    );
    auto tuple = std::tuple( args... );
    return SetterTask<Function,function>{
        .obj = obj,
        .args = tuple
    };
}

template <
    typename Function,
    Function function
>
auto run(
        SetterTask<Function, function> setter
)
{
    return std::apply(
            function,
            std::tuple_cat(
                std::make_tuple(setter.obj),
                setter.args
            )
    );
}

#define MAKE_SETTER(obj, Type, Function, ... ) \
    makeSetter<Type, (Type )&Object::Function>( obj, ## __VA_ARGS__ )

main.cpp:

#include "template_test.h"
#include <iostream>


void test() {
    Object obj;
    // Example 1: `double set()`:
    {
        auto setter = MAKE_SETTER(&obj,double (Object::*)(),set);
        double ret = run(setter);
        std::cout << "function returned: " << ret;
    }
    // Example 2: `void set(int)`:
    {
        auto setter = MAKE_SETTER(&obj,void (Object::*)(int),set, 10);
        run(setter);
    }
    // Example 3: `void set(int)`:
    {
        auto setter = MAKE_SETTER(&obj,void (Object::*)(int),set2, 10);
        run(setter);
    }
    // the following is not allowed
    // and raises a compile-time error:
    // auto getter = MAKE_SETTER(&obj,double (Object::*)(),get);
}

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.