I'm somewhat new to SWIG and C++, so this may be obvious, but I cannot seem to figure it out. I have a C++ collection object (in the MWE, a Pantry
), that in C++ is indexed by a std::string
, but iterates over the objects. This works in C++ using a ranged-based for loop with the implementations of begin()
and end()
.
Here's a MWE:
pantry.i
%module(directors="1") my_collection
%include "stdint.i"
%include "std_string.i"
%include "std_shared_ptr.i"
%include "std_vector.i"
%{
#include "my_collection.cpp"
%}
%nodefaultctor;
%shared_ptr(Food);
%include "my_collection.cpp"
pantry.cpp
#pragma once
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <sstream>
#ifdef SWIG
%define TO_STRING()
%extend {
%feature("python:slot", "tp_repr", functype="reprfunc") to_string;
}
std::string to_string()
%enddef
%define SUBSCRIPT(return_type, subscript_type)
%feature("python:slot", "mp_subscript", functype="binaryfunc") __getitem__;
%extend {
return_type __getitem__(subscript_type key) {
return (*($self))[key];
};
};
return_type operator[](subscript_type key)
%enddef
#else
#define TO_STRING() \
std::string to_string()
#define SUBSCRIPT(return_type, subscript_type) \
return_type operator[](subscript_type key)
#endif
class Food {
public:
std::string name;
Food(std::string name) {
this->name = name;
};
TO_STRING() {
std::ostringstream stream;
stream << "<Food " << this->name << ">";
return stream.str();
};
};
class Pantry {
private:
// _foods is not grammatically correct
std::map<std::string, std::shared_ptr<Food>> _food_map;
std::vector<std::shared_ptr<Food>> _food_vec;
public:
Pantry() {
};
std::shared_ptr<Food> add(std::string name) {
auto food = std::shared_ptr<Food>(new Food(name));
this->_food_map[food->name] = food;
this->_food_vec.push_back(food); // p.s., how do I prevent making a copy?
return food;
};
SUBSCRIPT(std::shared_ptr<Food>, std::string) {
return this->_food_map.at(key);
};
TO_STRING() {
return "<Pantry>";
};
std::vector<std::shared_ptr<Food>>::const_iterator begin() {
return this->_food_vec.begin();
};
std::vector<std::shared_ptr<Food>>::const_iterator end() {
return this->_food_vec.end();
};
size_t size() {
return this->_food_vec.size();
};
};
I've also put together a short Python script:
import pantry
pant = mc.Pantry()
print(pant.add('Bacon'))
print(pant.add('Cheese'))
print('subscript[Bacon]: {}'.format(pant['Bacon']))
for food in pant:
print('iter: ... {}'.format(food))
Then, depending on whether the -builtin
flag is passed to SWIG, I get the error:
- without
-builtin
:TypeError: in method 'Pantry___getitem__', argument 2 of type 'std::string'
-- which makes sense because I've chosen to index by strings - with
-builtin
:TypeError: 'my_collection.Pantry' object is not iterable
-- I think this is caused by SWIG not implicitly creating methods I haven't declared, which is seems correct in this instance.
How can I iterate over the collection contents without explicitly calling a method? I realize I could just make the _food_vec
public and iterate over that, but my actual code is more complicated and I'd prefer not to do that. Likewise, I could implement some other .iterate()
method, but I've already done the work with begin()
and end()
.
Edit: What would be a target-language agnostic method that I could implement similar to the macros I made for TO_STRING()
and SUBSCRIPT
, and what would be the correct template to add to the interface file (something like %template(FoodIterator) std::vector<Food>::iterator;
?