I'm trying to wrap an unordered map in a python dictionary using swig:
// UsingAnUnorderedMap.h
#ifndef LEARNSWIG_USINGUNORDEREDMAP_H
#define LEARNSWIG_USINGUNORDEREDMAP_H
#include <unordered_map>
#include <string>
std::unordered_map<std::string, int> makeStringToIntMap(){
return std::unordered_map<std::string, int>{
{"first", 4},
{"second", 5},
};
}
#endif //LEARNSWIG_USINGUNORDEREDMAP_H
//UsingAnUnorderedMap.i
%module UsingUnorderedMap
%{
#include "UsingUnorderedMap.h"
#include <iostream>
#include <unordered_map>
%}
%include "std_string.i"
%include "std_pair.i"
%include "std_unordered_map.i"
%template(StringToIntMap) std::unordered_map<std::string,int>;
%include "UsingUnorderedMap.h"
%typemap(out) StringToIntMap {
PyDictObject* dict = PyDict_New($input);
for (auto &item: StringToIntMap){
PyDict_SetItem(dict, PyUnicode_FromString(item.first), item.second);
}
$result = dict;
}
# testUsingAnUnorderedMap.py
import sys
sys.path += [
r"D:\LearnSwig\install-msvc\UsingUnorderedMap"
]
import UsingUnorderedMap
print(type(UsingUnorderedMap.makeStringToIntMap()))
This produces
<class 'UsingUnorderedMap.StringToIntMap'>
i.e. it just ignores the typemap. Technically the StringToIntMap
behaves pretty much the same as a Python dict - at far as I can tell, but I think there's confort for Python users in Python dictionaries and so it would be better if this were a straight up dictionary. Does anybody have any pointers on how to achieve this?
For convenience, you can build this code using the following CMake code. Note that I build this using the command -DSWIG_EXECUTABLE=/path/to/swig.exe
.
# CMakeLists.txt
set(Python_ROOT_DIR "C:/Miniconda3")
find_package(Python COMPONENTS Interpreter Development NumPy)
message("Python_EXECUTABLE ${Python_EXECUTABLE}")
find_package(SWIG 4.0.0 REQUIRED
COMPONENTS python
)
include(UseSWIG)
set_property(SOURCE UsingUnorderedMap.i PROPERTY CPLUSPLUS ON)
swig_add_library(UsingUnorderedMap
LANGUAGE python
TYPE MODULE
SOURCES UsingUnorderedMap.i UsingUnorderedMap)
set_target_properties(UsingUnorderedMap
PROPERTIES LANGUAGE CXX)
target_include_directories(UsingUnorderedMap PUBLIC
${Python_INCLUDE_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(UsingUnorderedMap PUBLIC ${Python_LIBRARIES})
install(TARGETS UsingUnorderedMap DESTINATION UsingUnorderedMap)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/UsingUnorderedMap.py
${CMAKE_CURRENT_SOURCE_DIR}/testUsingUnorderedMap.py
DESTINATION UsingUnorderedMap)
Here goes a small example showing conversion of std::unordered_map
to a python dictionary
%module dictmap
%{
#include "test.h"
%}
%typemap(out) std::unordered_map<std::string, std::string> (PyObject* obj) %{
obj = PyDict_New();
for (const auto& n : $1) {
PyObject* strA = PyUnicode_FromString(n.first.c_str());
PyObject* strB = PyUnicode_FromString(n.second.c_str());
PyDict_SetItem(obj, strA, strB);
Py_XDECREF(strA);
Py_XDECREF(strB);
}
$result = SWIG_Python_AppendOutput($result, obj);
%}
%include "test.h"
Small inline function
#pragma once
#include <string>
#include <unordered_map>
std::unordered_map<std::string, std::string> makeStringMap() {
return std::unordered_map<std::string, std::string> {
{"first", "hello"},
{"second", "world"},
};
}
I hope that you can see what you have made wrong. You are returning an empty dictionary created using an empty $input
. I am not sure of whether you need to defined a PyObject
argument, but I do this always in case it is needed together with another typemap.