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