Jonathan Boccara's blog

Inserting Values to a Map with Boost.Assign

Published December 3, 2019 - 0 Comments

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::vectors, 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::sets:

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

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin