The bsoncxx::builder::stream facility is intended primarily for simple one-shot inline construction of BSON. If you have more complex needs, you can use either the bsoncxx::builder::basic API, or the even more primitive bsoncxx::builder::core API.
These APIs are intended to be used as building blocks to construct a BSON generation facility that is idiomatic for your usage.
I will also note that both the bsoncxx::builder::stream and the bsoncxx::builder::basic APIs can in fact support the scenario you envision, since each can accept a generic callable as an argument to operator<< or append. I'm using C++14 in a few places here when it simplifies the typing:
With the basic builder:
using bsoncxx::builder::basic::kvp;
using bsoncxx::builder::basic::sub_array;
const auto elements = {1, 2, 3};
auto doc = builder::basic::document{};
doc.append(
kvp("foo", [&elements](sub_array child) {
for (const auto& element : elements) {
child.append(element);
}
}));
std::cout << bsoncxx::to_json(doc.view()) << "\n";
With the stream builder:
using builder::stream::document;
using builder::stream::open_array;
using builder::stream::close_array;
using builder::stream::finalize;
const auto elements = {1, 2, 3};
auto result = document{} << "foo" << open_array << [&elements](auto ctx) {
for (auto&& element : elements) {
ctx << element;
}
} << close_array << finalize;
std::cout << bsoncxx::to_json(result) << "\n";
Note that the expression ctx << element discards its result, which implies that the result of appending element to ctx must yield an object with the same type as ctx, so I personally do not recommend this approach as it is fragile. The basic builder is more likely to be what you want for this sort of work.
It is also useful to see that since the callable can be anything, you can hoist the lambda into a callable type with a factory method, giving you a re-useable object which will append any sequence container:
template<typename T>
struct range_appender {
const T begin;
const T end;
void operator()(builder::basic::sub_array array) const {
std::for_each(begin, end, [&](const auto& value) {
array.append(value);
});
}
};
template<typename T>
range_appender<T> make_range_appender(T begin, T end) {
return {begin, end};
}
template<typename C>
auto make_range_appender(const C& c) {
using std::begin;
using std::end;
return make_range_appender(begin(c), end(c));
}
Using make_range_appender makes the example for the basic builder even simpler:
using bsoncxx::builder::basic::kvp;
const auto elements = {1, 2, 3};
auto doc = builder::basic::document{};
doc.append(kvp("foo", make_range_appender(elements)));
std::cout << bsoncxx::to_json(doc.view()) << "\n";
Potentially, one could do something similar to extend the range_appender type to handle the stream scenario as well by adding an overload for operator() that takes the appropriate type. Additionally, it is almost certainly possible to make an appender object that will operate on the associative container types to generate (sub)documents, though it is important to keep in mind issues related to ordering.
The overall point is that the library is layered and extensible. If the stream builder doesn't do what you want, you can customize it by writing new types. If the type-stack aspect is interfering with that, use the basic builder, which is less specialized but similarly extensible. If neither of those represents the model that you want, start with the core builder and layer your own abstractions. Beyond that, you can always make use of libbson directly, if you need even more direct control for some reason.
I'm happy to answer any additional questions.
Thanks,
Andrew