I'm writing a C backend for a C library and I want the C code to be able to iterate over the individual items of a forward iterator. In C the code which iterates over the items looks like this:
auto rng = wks.range(XLCellReference("A1"), XLCellReference("Q1"));
for (auto& cell : rng) {
// do something with "cell"
}
Now I want to export this code so that it is accessible from C. I'd like to be able to iterate over the items from C using a set of functions like this:
void *startiteration(void *cpp_obj);
void *getnextitem(void *cpp_obj, void *iter);
void finishiteration(void *cpp_obj, void *iter);
I'd imagine startiteration()
to return an iterator pointer to the C code which would then be passed along with the C object pointer for all successive calls to getnextitem()
and finishiteration()
.
But the problem here is that I can easily pass pointers of objects created in C using new
between C and C code but I don't see how I could do the same with iterators since the iterators are returned by a class method and I don't think there's any way to turn the iterator into a pointer that I could pass to C code which would then pass it back to C during the iteration. Also, I don't know how I would "free" such an iterator.
Does anybody have some tips how I can iterate over a C forward iterator from C code? How should this be implemented?
EDIT
Implementation based on Silvio's suggestions:
struct myiter
{
XLCellIterator begin;
XLCellIterator end;
};
void *xlsx_startiteration(void *handle, int idx)
{
XLDocument *doc = (XLDocument *) handle;
XLWorkbook wb = doc->workbook();
auto wks = wb.worksheet(wb.sheetNames()[idx-1]);
XLCellRange cr = wks.range(XLCellReference("A1"), XLCellReference("Q1"));
myiter *it = new myiter();
it->begin = std::begin(static_cast<XLCellRange*>(cr));
it->end = std::end(static_cast<XLCellRange*>(cr));
return it;
}
CodePudding user response:
auto rng = wks.range(XLCellReference("A1"), XLCellReference("Q1")); for (auto& cell : rng) { // do something with "cell" }
I like doing 1:1 relationship between C and C. The following code outputs 3 lines var=1
var=2
var=3
.
By wrapping the objects inside structures, the C side only forward declarations of structures and pointers. The C side sees all the rest. Additionally, C side will get a warning when passing invalid pointer to the wrong function.
#include <cstdio>
#include <vector>
typedef int do_not_know_what_is_the_type;
typedef std::vector<do_not_know_what_is_the_type> XLCellRange;
typedef XLCellRange::iterator XLCellIterator;
// header file
// C side
#ifdef __cplusplus
extern "C" {
#endif
struct wks_range_it_s;
void wks_range_it_inc(struct wks_range_it_s *);
bool wks_range_it_ne(struct wks_range_it_s *, struct wks_range_it_s *);
do_not_know_what_is_the_type wks_range_it_deref(struct wks_range_it_s *);
void wks_range_it_destruct(struct wks_range_it_s *);
struct wks_range_s;
struct wks_range_s *wks_range_construct();
void wks_range_destruct(struct wks_range_s *);
struct wks_range_it_s *wks_range_begin(struct wks_range_s *);
struct wks_range_it_s *wks_range_end(struct wks_range_s *);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
// C side
struct wks_range_s {
XLCellRange rng;
};
struct wks_range_it_s {
XLCellIterator it;
};
extern "C"
void wks_range_it_inc(struct wks_range_it_s *t) {
t->it;
}
extern "C"
bool wks_range_it_ne(struct wks_range_it_s *a, struct wks_range_it_s *b) {
return a->it != b->it;
}
extern "C"
do_not_know_what_is_the_type wks_range_it_deref(struct wks_range_it_s *t) {
return *t->it;
}
extern "C"
void wks_range_it_destruct(struct wks_range_it_s *t) {
delete t;
}
extern "C"
struct wks_range_s *wks_range_construct() {
// return new struct wks_range_s(wks.range(XLCellReference("A1"), XLCellReference("Q1")));
return new wks_range_s{{1,2,3}};
}
extern "C"
void wks_range_destruct(struct wks_range_s *t) {
delete t;
}
extern "C"
struct wks_range_it_s *wks_range_begin(struct wks_range_s *t) {
return new wks_range_it_s{t->rng.begin()};
}
extern "C"
struct wks_range_it_s *wks_range_end(struct wks_range_s *t) {
return new wks_range_it_s{t->rng.end()};
}
#endif
int main() {
// C code example
struct wks_range_s *rng = wks_range_construct();
// Almost 1:1 relationship with C range loop.
for(struct wks_range_it_s *begin = wks_range_begin(rng),
*end = wks_range_end(rng);
wks_range_it_ne(begin, end) ? 1 : (
// Yes, I'm sneaky.
wks_range_it_destruct(begin),
wks_range_it_destruct(end),
0);
wks_range_it_inc(begin)
) {
do_not_know_what_is_the_type var = wks_range_it_deref(begin);
// do something with var here?
printf("var=%d\n", var);
}
wks_range_destruct(rng);
}
CodePudding user response:
Forward iterators in C are regular, which means they can be copied. In fact, in our case, we would only need to move them. Specifically, assuming the type of the iterator in question is I
, then startiteration
could do something like this.
struct Range {
I begin;
I end;
};
void* startiteration(void* cpp_obj) {
Range* my_range = new Range();
my_range->begin = std::begin(*static_cast<MyCppType*>(cpp_object));
my_range->end = std::end(*static_cast<MyCppType*>(cpp_object));
return my_range;
}
Just because the forward iterator is originally returned by value doesn't prohibit us from putting it into a pointer, either directly or indirectly. In our case, we grab both the beginning and the end and store them in a (heap-allocated) structure. Then finishiteration
, of course, would be responsible for delete
ing the constructed range object.