Inserting Values to a Map with Boost.Assign
Boost.Assign is a library that allows for a natural syntax to add elements to a container:
std::vector<int> v; v += 1,2,3,4,5,6,7,8,9,10;
We’ve seen how it works with vectors and sets, now we will focus on maps. Indeed, maps don’t work the same way. The following code, despite that it looks nice, doesn’t compile:
#include <boost/assign/std/map.hpp> using namespace boost::assign; int main() { std::map<int, std::string> m; m += {1,"one"}, {2,"two"}, {3,"three"}; }
Indeed, operator+=
takes a template argument, which is for example {1,"one"}
here. But since std::pair
doesn’t support initialization from a std::initializer_list
, this argument is not convertible into a std::pair
, that the map expects as an element to insert.
To make this code compile, we could write this:
#include <boost/assign/std/map.hpp> using namespace boost::assign; int main() { std::map<int, std::string> m; m += std::pair{1,"one"}, std::pair{2,"two"}, std::pair{3,"three"}; }
This is the C++17 version that uses type deduction for template classes constructors. The pre-C++17 version would use std::make_pair
:
#include <boost/assign/std/map.hpp> using namespace boost::assign; int main() { std::map<int, std::string> m; m += std::make_pair(1,"one"), std::make_pair(2,"two"), std::make_pair(3,"three"); }
But Boost.Assign offers another syntax for inserting into a map:
#include <boost/assign/std/map.hpp> using namespace boost::assign; int main() { std::map<int, std::string> m; insert(m)(1, "one")(2, "two")(3, "three"); }
This expression relies on operator()
. insert(m)
constructs an object that binds to the map m
and supports an operator()
that takes two elements (a key and a value) and inserts them in m
. To chain up the successive insertions, this operator()
must return an object that also supports operator()
, and so on.
Let’s now see how this can implemented.
Implementation of insert
Like when we looked at the implementation of the insertion in a vector, note that we will build an implementation that is slightly different from the one in Boost.Assign, because Boost.Assign is more generic as it accommodates other features and containers, and also because we will use modern C++ (Boost.Assign was written in C++98).
insert
must return an object that has a operator()
and that is somehow connected to the map. In the implementation of Boost.Assign, this object is called list_inserter
:
template<typename Key, typename Value> auto insert(std::map<Key, Value>& m) { return list_inserter(call_insert(m)); }
list_inserter
is parametrized with a policy (a template parameter) in charge of performing insertions. Indeed, we already used list_inserter
to append to std::vector
s, and delegating the responsibility of inserting to a container to a dedicated class decouples list_inserter
from that particular container. Decoupling improves the design here.
We use call_insert
for this policy, that we’ve already used to insert into std::set
s:
template<typename Container> struct call_insert { public: explicit call_insert(Container& container) : container_(container) {} template<typename T> void operator()(T const& value) { container_.insert(value); } private: Container& container_; };
Implementation of operator()
Let’s now see how the operator()
of list_inserter
is implemented. The code in Boost uses elaborate macros to be very generic. Here is a simplified equivalent code for our case of inserting into a map:
template<typename Inserter> class list_inserter { public: explicit list_inserter(Inserter inserter) : inserter_(inserter) {} template<typename Key, typename Value> list_inserter& operator()(Key const& key, Value const& value) { inserter_(std::make_pair(key, value)); return *this; } private: Inserter inserter_; };
list_inserter
receives its insertion policy in its constructor (the insert
function passed it a call_inserter
bound to our map) and stores it. Its operator()
takes a key and a value, packs them up into a std::pair
, and sends that pair to the insertion policy call_insert
. call_insert
, as its names suggests, calls the .insert
method of the map and passes it the pair.
Note how operator()
returns a reference to the list_inserter
object itself (line 11). This allows to chain up successive calls to operator()
and thus to insert an arbitrary number of entries into the map.
Here is all the code put together:
#include <iostream> #include <map> template<typename Inserter> class list_inserter { public: explicit list_inserter(Inserter inserter) : inserter_(inserter) {} template<typename Key, typename Value> list_inserter& operator()(Key const& key, Value const& value) { inserter_(std::make_pair(key, value)); return *this; } private: Inserter inserter_; }; template<typename Container> struct call_insert { public: explicit call_insert(Container& container) : container_(container) {} template<typename T> void operator()(T const& value) { container_.insert(value); } private: Container& container_; }; template<typename Key, typename Value> auto insert(std::map<Key, Value>& m) { return list_inserter(call_insert(m)); } int main() { std::map<int, std::string> m; insert(m)(1, "one")(2, "two")(3, "three"); for (auto& [key, value] : m) std::cout << key << '-' << value << '\n'; }
Studying Boost libraries
Looking into the implementation of the Boost libraries is often instructive, and Boost.Assign is an interesting library that allows to write expressive code to insert multiple elements into a collection.
We’ve covered some of the major features of Boost.Assign to insert into a vector, a set or a map. But that library also has other components, that we will make interesting explorations for future articles.
We could also explore other Boost libraries that allow to write expressive code and/or have instructive implementations. What is your favourite Boost library?
You will also like
- Appending Values to a Vector in a Natural Way with Boost.Assign
- The BooSTL algorithms
- Introduction to Boost.Phoenix
- The Complete Guide to Building Strings In C++: From “Hello World” Up To Boost Karma
Share this post!