wlav / cppyy

Other
391 stars 40 forks source link

TypedefPointerToClass cannot be used as template argument #104

Open N-Coder opened 1 year ago

N-Coder commented 1 year ago

Using a type name defined via using as template argument currently fails, while using class objects works fine:

>>> import cppyy
>>> cppyy.cppdef("""
... struct Test {};
... using testptr = Test*;
... """)
True
>>> g = cppyy.gbl
>>> g.std.vector[g.Test]
<class cppyy.gbl.std.vector<Test> at 0x556fe3f41d70>
>>> g.std.vector[g.testptr]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/finksim/GitLab/ogdf/ogdf-python/.venv/lib64/python3.10/site-packages/cppyy/_cpython_cppyy.py", line 79, in __getitem__
    pyclass = _backend.MakeCppTemplateClass(*newargs)
SyntaxError: could not construct C++ name from provided template argument.

Furthermore, inspecting the TypedefPointerToClass in Python is pretty hard, as its only exposed method is the constructor.

>>> g.testptr
<cppyy.TypedefPointerToClass object at 0x7f8c08ec0ca0>
>>> g.testptr.__dir__()
['__call__', '__getattribute__', '__setattr__', '__delattr__', '__doc__', '__new__', '__repr__', '__hash__', '__str__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__init__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
>>> g.testptr.__cpp_name__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'cppyy.TypedefPointerToClass' object has no attribute '__cpp_name__'
>>> g.testptr()
<cppyy.gbl.Test object at 0x(nil)>

A simple fix might be defining __cpp_name__ as the name used in the using declaration for the TypedefPointerToClass, i.e. having g.testptr.__cpp_name__ == "testptr", as that would be recognized correctly by the AddTypeName function used for constructing the template arguments. A second method for getting the name the declaration points to (and translating that string into a type object) would of course also be nice.

N-Coder commented 1 year ago

This also affects bind_object, although that function doesn't use AddTypeName and only checks for __name__, so my fix wouldn't work there:

>>> cppyy.bind_object(cppyy.nullptr, g.Test)
<cppyy.gbl.Test object at 0x(nil)>
>>> cppyy.bind_object(cppyy.nullptr, g.testptr)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bind_object expects a valid class or class name as an argument
N-Coder commented 1 year ago

Background on this is that I have a method similar to the testfun below, which is pretty hard to call when you want to pass a nullptr:

>>> cppyy.cppdef("template<typename T> void testfun(T const& x) {}")
True
>>> g.testfun["testptr"](cppyy.nullptr)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Could not find "testfun<testptr>" (set cppyy.set_debug() for C++ errors):
  none of the 2 overloaded methods succeeded. Full details:
  void ::testfun(Test*const& x) =>
    TypeError: could not convert argument 1
  void ::testfun(Test*const& x) =>
    TypeError: could not convert argument 1
  none of the 3 overloaded methods succeeded. Full details:
  void ::testfun(Test*const& x) =>
    TypeError: could not convert argument 1
  void ::testfun(Test*const& x) =>
    TypeError: could not convert argument 1
  void ::testfun(Test*const& x) =>
    TypeError: could not convert argument 1
>>> g.testfun["testptr"](cppyy.bind_object(cppyy.nullptr, g.Test)) # succeeds

It would be super awesome if I could just write g.testfun[g.testptr](cppyy.nullptr) or even g.testfun[g.testptr](None).

wlav commented 1 year ago

That last error is separate from the problem of the using: nullptr pointer is simply not accepted for T* const&, whereas the typed pointer is. But yes, TypedefPointerToClass should have a __cpp_name__ (and a __name__ for that matter :) ).

wlav commented 1 year ago

The original issue is fixed in repo.

N-Coder commented 1 year ago

The original issue is fixed in repo.

Awesome, thanks a lot!

nullptr pointer is simply not accepted for T* const&

Could you elaborate a little on this? I don't quite understand why casting the nullptr with cppyy.bind_object(cppyy.nullptr, g.Test) should behave differently from cppyy.nullptr here...

wlav commented 1 year ago

cppyy.nullptr is a unique Python object, so that it can be pointer compared, which is nice and fast. It's not a CPPInstance type, which you get by binding nullptr. The code that handles T*const& does not have a check for nullptr to let it pass. Might have been a quick fix, except that there wasn't already a distinction between T*const& and T*&, which for nullptr there should be.