Search code examples
c++stdmapstdset

How to initialize a map from a set


I want to set the keys of a std::map using the elements of a std::set (or a std::vector).

Something like the following...

std::set<int> keys = { 3,4,6 };
std::map<int,string> results(keys); // syntax error

Can this be done without explicitly iterating over the set?


Solution

  • You can't. A map is not a set. They're fundamentally different containers, even if the underlying structure is similar.


    That said, anything's possible. The range constructor of std::map is linear time if the elements of the range are already sorted, which set guarantees for us. So all you need to do is apply a transformer to every element to produce a new range. The simplest would be to just use something like boost::make_transform_iterator (or roll your own):

    template <class K, class F
        class V = decltype(std::declval<F&>()(std::declval<K const&>()))::second_type>
    std::map<K, V> as_map(std::set<K> const& s, F&& f) {
        return std::map<K,V>(
            boost::make_transform_iterator(s.begin(), f),
            boost::make_transform_iterator(s.end(), f));
    }
    
    std::map<int,string> results =
        as_map(keys, [](int i){
            return std::make_pair(i, std::string{});
        });
    

    which if you always will want default initialization, can just reduce to:

    template <class V, class K>
    std::map<K, V> as_map_default(std::set<K> const& s) {
        auto f = [](K const& k) { return std::make_pair(k, V{}); }
        return std::map<K,V>(
            boost::make_transform_iterator(s.begin(), f),
            boost::make_transform_iterator(s.end(), f)); 
    }
    
    std::map<int,string> results = as_map_default<string>(keys);