0

I'm trying to write a thin wrapper layer to interface c++ classes from python.

Python itself uses those three signatures to call a c function from py:

typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);
typedef PyObject *(*PyCFunctionWithKeywords)(PyObject *, PyObject *, PyObject *);
typedef PyObject *(*PyNoArgsFunction)(PyObject *);

Unfortunately, the PyMethodDef struct stores a function as PyCFunction and a flags member to decide at runtime which function is actually stored/called.

I wrote a function template which takes three PyObject*s and returns one PyObject*, essentially a PyCFunctionWithKeywords, calling the classes member function with either zero, one or two arguments (as the first argument is the instance itself).

template<auto Fn>
PyObject* member_wrapper(PyObject* obj, PyObject* args, PyObject* kwargs)

Fn is the member function pointer to wrap. Given a macro:

#define PY_WRAP(fn) (PyCFunction)::py::member_wrapper<fn>

I can successfully set the function pointer:

PyCFunction func = PY_WRAP(&MyClass::SomeFunc);

Above compiles as expected. However I've tried using a consteval function instead of a macro:

template<typename T>
consteval PyCFunction make_wrapper(T fn) {
    return (PyCFunction)::py::member_wrapper<fn>;
}

PyCFunction func = make_wrapper(&MyClass::SomeFunc);

This however fails with:

 error C2440: Cannot convert "PyObject *(__cdecl *)(PyObject *,PyObject *,PyObject *)" to "PyCFunction"

I'm confused why the cast works in the macro but fails in the consteval function.

tkausl
  • 13,686
  • 2
  • 33
  • 50
  • 3
    `reinterpret_cast` is not allowed at compile-time. This includes those C-style casts that use it under the hood. – HolyBlackCat Oct 31 '22 at 20:01
  • Thats unfortunate. So I'm stuck with macros? – tkausl Oct 31 '22 at 20:13
  • 2
    Just use a non-constexpr function? And let your compiler optimize it. – HolyBlackCat Oct 31 '22 at 20:24
  • I can't pass the function `fn` as template argument to `member_wrapper` in this case, however. And since I need to have a c-style function pointer in the end, I can't store the member function pointer as state anywhere. Or atleast I can't think of a way. Of course, using `::py::member_wrapper<&MyClass::SomeFunc>;` directly works aswell but then again requires a cast, and the goal is to make it as easy to use as possible, i.e. just a single "function" (or macro) call, passing the member function pointer. – tkausl Oct 31 '22 at 20:59
  • Now that I actually looked at the code, you won't be able to use a function parameter as a template argument, `consteval` or not. Function parameters are never considered to be `constexpr` inside of a function. The call syntax has to be `make_wrapper<...>()` or `make_wrapper<...>` (template variable). – HolyBlackCat Oct 31 '22 at 21:10

1 Answers1

2

consteval is not a macro; it's not just a textual copy-and-paste. It's a way of decorating a function such that using it outside of constant expression evaluation is a compile error. consteval functions therefore must follow the rules of constexpr functions.

Among those rules are that reinterpret_cast, and any C-style cast that would be equivalent to one, is expressly forbidden. Conversion of a function pointer to one signature to a different signature requires a reinterpret_cast, even if you're using a C-style cast. So it's not allowed.

You can use a non-constexpr function call if you want to avoid a macro. But you can't use a compile-time construct for something that cannot be done at compile-time.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982