pythonc++stdpython-bindingscppyy

Using cppyy with rvalue pointers and maps


I would love to love cppyy. However the codebase I am using has heavy use of std.unique_ptr, rvalue pointers, and templates. I am confused about how to translate these into something I can call from python.

For instance, I am stuck on how to create an std::map from classes.

I understand that I can make an std::map by doing the following:

test_map = Cpp.std.map[Cpp.std.string, Cpp.std.string]()
test_string = "value"
test_map["key"] = test_string
print(test_map["key"])

However, when I do:

test_map = Cpp.std.map[Cpp.std.string, Cpp.std.string]()
test_string = Cpp.std.string("value")
test_map["key"] = Cpp.std.move(test_string)
print(test_map["key"])

I get

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[34], line 3
      1 test_map = Cpp.std.map[Cpp.std.string, Cpp.std.string]()
      2 test_string = Cpp.std.string("value")
----> 3 test_map["key"] = Cpp.std.move(test_string)
      4 print(test_map["key"])

TypeError: none of the 2 overloaded methods succeeded. Full details:
  std::string& std::map<std::string,std::string>::operator[](std::map<std::string,std::string>::key_type&& __k) =>
    logic_error: basic_string::_M_construct null not valid
  std::string& std::map<std::string,std::string>::operator[](const std::map<std::string,std::string>::key_type& __k) =>
    logic_error: basic_string::_M_construct null not valid

I am not sure why this fails.

What I actually want to construct is a map from a string to a templated class, see:

import cppyy
import cppyy.gbl as Cpp

cppyy.cppdef(r"""\
template<typename T>
class MyClass {
public:
    MyClass(T t) : m_data(t) {}
    T m_data;
};
""")

But when I try:

test_map = Cpp.std.map[Cpp.std.string, Cpp.MyClass['double']]()
myClass  = Cpp.MyClass['double'](5.0)
test_map["key"] = Cpp.std.move(myClass)
print(test_map["key"])

I get a long error:

input_line_50:6:86: error: no member named 'operator[]' in 'std::map<std::__cxx11::basic_string<char>, MyClass<double>, std::less<std::__cxx11::basic_string<char> >, std::allocator<std::pair<const std::__cxx11::basic_string<char>, MyClass<double> > > >'
      new (ret) (MyClass<double>*) (&((std::map<std::string,MyClass<double> >*)obj)->operator[]((std::string&&)*(std::string*)args[0]));
                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  ^
input_line_50:10:55: error: no member named 'operator[]' in 'std::map<std::__cxx11::basic_string<char>, MyClass<double>, std::less<std::__cxx11::basic_string<char> >, std::allocator<std::pair<const std::__cxx11::basic_string<char>, MyClass<double> > > >'
      ((std::map<std::string,MyClass<double> >*)obj)->operator[]((std::string&&)*(std::string*)args[0]);
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  ^
input_line_51:6:86: error: no member named 'operator[]' in 'std::map<std::__cxx11::basic_string<char>, MyClass<double>, std::less<std::__cxx11::basic_string<char> >, std::allocator<std::pair<const std::__cxx11::basic_string<char>, MyClass<double> > > >'
      new (ret) (MyClass<double>*) (&((std::map<std::string,MyClass<double> >*)obj)->operator[]((const std::string&)*(const std::string*)args[0]));
                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  ^
input_line_51:10:55: error: no member named 'operator[]' in 'std::map<std::__cxx11::basic_string<char>, MyClass<double>, std::less<std::__cxx11::basic_string<char> >, std::allocator<std::pair<const std::__cxx11::basic_string<char>, MyClass<double> > > >'
      ((std::map<std::string,MyClass<double> >*)obj)->operator[]((const std::string&)*(const std::string*)args[0]);
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  ^

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[45], line 3
      1 test_map = Cpp.std.map[Cpp.std.string, Cpp.MyClass['double']]()
      2 myClass  = Cpp.MyClass['double'](5.0)
----> 3 test_map["key"] = Cpp.std.move(myClass)
      4 print(test_map["key"])

TypeError: none of the 2 overloaded methods succeeded. Full details:
  MyClass<double>& std::map<std::string,MyClass<double> >::operator[](std::map<std::string,MyClass<double> >::key_type&& __k) =>
    ReferenceError: none of the 2 overloaded methods succeeded. Full details:
  attempt to access a null-pointer
  attempt to access a null-pointer
  MyClass<double>& std::map<std::string,MyClass<double> >::operator[](const std::map<std::string,MyClass<double> >::key_type& __k) =>
    TypeError: none of the 2 overloaded methods succeeded. Full details:
  MyClass<double>& MyClass<double>::operator=(MyClass<double>&&) =>
    ValueError: could not convert argument 1 (object is not an rvalue)
  attempt to access a null-pointer

What am I doing wrong?


Solution

  • You can insert Key/Value pairs using .emplace and you can lookup a value from a Key using .at.

    Example:

    #!/bin/python
    
    import cppyy
    import cppyy.gbl as Cpp
    
    # the class with an added operator<< overload to support printing:
    cppyy.cppdef(r"""\
    template<typename T>
    class MyClass {
    public:
        MyClass(T t) : m_data(t) {}
        T m_data;
        friend std::ostream& operator<<(std::ostream& os, const MyClass& mc) {
            return os << mc.m_data;
        }
    };
    """)
    
    from cppyy.gbl import MyClass
    
    test_map = Cpp.std.map[Cpp.std.string, MyClass['double']]()
    
    myObj = MyClass['double'](5.0)
    
    # add a key/value pair
    test_map.emplace("key", Cpp.std.move(myObj))
    
    # print the value mapped to the key:
    print(test_map.at("key"))
    
    # loop and print keys and values
    for key, value in test_map:
        print(key, value)
    

    Output:

    5
    key 5