2

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?

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
Brent Bradburn
  • 51,587
  • 17
  • 154
  • 173
  • 1
    1) Is there any particular reason you want to? 2) What do you want the value strings to be? – Beta Oct 01 '16 at 00:43
  • I'd be very surprised if this were possible, since accessing the uninitialized values that would result from that statement should not be permissible behavior. – ApproachingDarknessFish Oct 01 '16 at 00:46
  • @Beta: 1) I want to create an API that allows the keys to be specified concisely (without regard to values). 2) I want the value to be default initialized. – Brent Bradburn Oct 01 '16 at 00:46
  • But why do you want to not explicitly iterate over the set? Unless you have a specific reason, I think this is more a question about coding style. – Brian Bi Oct 01 '16 at 00:49
  • @Brian: Yes, it is essentially for style. I'm just wondering if there is a very concise way to make the assignment. It seems like it might be a natural thing to do based on [the similarities between the two containers](http://stackoverflow.com/a/21804128). – Brent Bradburn Oct 01 '16 at 00:54
  • I think it can be done, by some very adroit STL-jutsu, but it will much less concise -- and readable, and maintainable -- than straightforward iteration over the set. – Beta Oct 01 '16 at 00:59
  • @ApproachingDarknessFish But that's exactly what map::operator[] allows -- default-initialization. – n.caillou Oct 01 '16 at 01:43

2 Answers2

2

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);
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Every time someone says "the simplest solution would be X", and X involves the line that defines `class V`, someone swears to never learn C++. :-) Note: I like the answer, but man, so many angle brackets. – ShadowRanger Oct 01 '16 at 02:02
  • The answer starts by saying "you can't", then proceeds to demonstrate how to do it. The iteration is hidden inside the map constructor, which is basically what I was hoping to see. So this is the correct answer, except for the first two words. :) – Brent Bradburn Oct 02 '16 at 17:52
1

Can this be done without explicitly iterating over the set?

No. There is no way to know the keys in the set without iterating over it. You may write functions to make it appear as if there is an implicit transformation, but those functions must ultimately iterate the source collection.

A simple way is as follows:

#include <set>
#include <map>
#include <string>

auto build_map(const std::set<int>& source) -> std::map<int,std::string>
{
  std::map<int,std::string> results;
  for (auto const& i : source) {
    results[i];
  }
  return results;
}

int main()
{
  std::set<int> keys = { 3,4,6 };
  auto results = build_map(keys);
}

Of course we may templatise if that improves readability:

#include <set>
#include <vector>
#include <unordered_set>
#include <map>
#include <string>
#include <utility>

template<class MappedType, class SourceContainer>
auto build_map(SourceContainer&& source)
{
  using source_type = std::decay_t<SourceContainer>;
  using key_type = typename source_type::value_type;

  std::map<key_type , MappedType> results;
  for (auto const& i : source) {
    results[i];
  }
  return results;
}

int main()
{
  std::set<int> keys = { 3,4,6 };
  auto results = build_map<std::string>(keys);

  // also
  results = build_map<std::string>(std::vector<int>{3, 4, 6});
  results = build_map<std::string>(std::unordered_set<int>{3, 4, 6});
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142