Search code examples
c++templatesc++20

Make Template Function Pointer generic


I have the following code: https://godbolt.org/z/e31EcTbeb

template<PyObject* (*fn)(PyObject*, PyObject*[], Py_ssize_t)>
PyObject* python_fastcall(PyObject* self, PyObject* args)
{
    typedef struct
    {
        PyObject_VAR_HEAD
        PyObject *ob_item[1];
    } PyTupleObject;

    return fn(self, reinterpret_cast<PyTupleObject*>(args)->ob_item, PyTuple_Size(args));
}

and a macro to call it:

using PyCFunction = PyObject* (*)(PyObject*, PyObject*[])
#define PYTHON_FASTCALL(f) (PyCFunction)python_fastcall<f>

Finally I have a few functions like:

PyObject* foo(PyObject* self, PyObject* args[], Py_ssize_t args_length);
PyObject* bar(PySomeObject* self, PyObject* args[], Py_ssize_t args_length);
PyObject* meh(PyDifferentObject* self, PyObject* args[], Py_ssize_t args_length);

and I want to be able to do:

PYTHON_FASTCALL(foo)
PYTHON_FASTCALL(bar)
PYTHON_FASTCALL(meh)

But I get the errors:

error: address of overloaded function 'python_fastcall' does not match required type 'PyObject* (PyObject*, PyObject*)' -> PYTHON_FASTCALL(bar)

error: address of overloaded function 'python_fastcall' does not match required type 'PyObject* (PyObject*, PyObject*)' -> PYTHON_FASTCALL(meh)

No error for foo as the signature matches of course.

Is there a way to make the function accept such function pointers where only the FIRST parameter differs?

I tried something like:

template<typename T, typename... Ts>
struct is_any_of : std::bool_constant<(std::is_same<T, Ts || ...)> { };

template<typename fn>
typename std::enable_if<
    is_any_of<fn, 
            PyObject* (*)(PyObject*, PyObject*[], Py_ssize_t),
            PyObject* (*)(PySomeObject*, PyObject*[], Py_ssize_t),
            PyObject* (*)(PyDifferentObject*, PyObject*[], Py_ssize_t)
    >::value,
    PyObject*
    >::type
python_fastcall(PyObject* self, PyObject* args)
{
    return fn(self, args, args_size(args));
}

But it doesn't work because fn becomes a type instead of a function pointer. Any ideas how I can achieve this?


Solution

  • ... because fn becomes a type ...

    Its a type because you declared it to be a type here template<typename fn>. You can use a non-type template parameter just like you did before.

    Without restricting the template parameter you can do this:

    struct A {};
    struct B : A {};
    struct C : A {};
    
    template <auto f> 
    void call(auto* a, int x) {
        f(a,x);
    }
    
    void foo(A*,int){}
    void bar(B*,int) {}
    void moo(C*,int) {}
    
    
    int main() {
        A* a;
        B* b;
        C* c;
        call<foo>(a,42);
        call<bar>(b,42);
        call<moo>(c,42);
    }
    

    Live Demo

    If you want to restrict the parameter to a function pointer with one of the three signatures you can still use the auto template argument and then SFINAE on the decltype of it. I also fixed the first arg of call to the first arg of the function pointer:

    #include <type_traits>
    
    struct A {};
    struct B : A {};
    struct C : A {};
    
    
    
    void foo(A*,int){}
    void bar(B*,int) {}
    void moo(C*,int) {}
    
    template<typename T, typename U, typename... Ts>
    struct is_any_of : is_any_of<T,Ts...> { };
    template<typename T, typename... Ts>
    struct is_any_of<T,T, Ts...> : std::true_type { };
    template<typename T, typename U>
    struct is_any_of<T,U> : std::false_type { };
    
    template <typename F> struct first_arg;
    template <typename R,typename T,typename ...More> struct first_arg<R(*)(T,More...)> {
        using type = T;
    };
    
    template <auto f> 
    std::enable_if_t<
        is_any_of<decltype(f),decltype(&foo),decltype(&bar),decltype(&moo),void>::value
    > call(typename first_arg<decltype(f)>::type a, int x) {
        f(a,x);
    }
    
    
    
    int main() {
        A* a;
        B* b;
        C* c;
        call<foo>(a,42);
        call<bar>(b,42);
        call<moo>(c,42);
    }
    

    Live Demo

    I didn't get your is_any_of to compile, so I used the one from here. It has an issue when the last argument matches, here it can be fixed by using void as last argument (decltype(fn) cannot be void).