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?

  • args is PyObject* but fn takes a PyObject** as 2nd parameter

    – 

  • PyObject*[] args is also incorrect syntax. godbolt.org/z/q3csvcMje. Please post a minimal reproducible example

    – 

  • Fixed the example. I just didn’t want to bombard the post with huge functions. Args is actually a tuple that gets converted to an array.

    – 




  • you should not bombard the question with huge functions. You should prepare a minimal reproducible example

    – 

  • PyObject* foo(PyObject* self, PyObject*[] args, Py_ssize_t args_length); is still bogus

    – 

… 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).

Leave a Comment