4

I have a map of vectors:

std::map<int, std::vector<bool>> mymap

At times, I need to insert a new element:

auto& newvec = mymap[42];
// Add stuff to newvec

As far is I understand (and assuming that 42 is not yet in the map), this will give me newvec with length 0 (constructed as std::vector<bool> {}) which I can then extend.

Is there a way to initialize the vector to some size n right away?

(I am not concerned about performance, just wondering if there is a way to do this).

Nikratio
  • 2,338
  • 2
  • 29
  • 43
  • 2
    You can wrap the vector bool in your own class that initializes it at the size you want. – Matthieu Brucher Nov 17 '18 at 11:08
  • `auto & newvec = (mymap[42] = std::vector(desired_size));` will do it, albeit by introducing a temporary that may (notionally) be elided. It is arguably more readable to either do `mymap[42].resize(desired_size)` before defining `newvec`, or simply do `newvec.resize(desired_size)` immediately after. – Peter Nov 17 '18 at 11:16
  • 1
    Doesn't [map::emplace](http://www.cplusplus.com/reference/map/map/emplace/) do what you want? You can use it as mymap.emplace(42, std::vector(125, false)); – eozd Nov 17 '18 at 11:23

3 Answers3

4

Wrapping the std::vector<bool>

You could wrap the std::vector<bool> you want to initialise in the following way:

template<size_t N>
struct myvector {
   myvector(): data(N) {}
   std::vector<bool> data;
};

Then, declare mymap as a map whose value type is of this wrapper type, myvector<N>, instead of std::vector<bool>. For example, for N equal to 100:

std::map<int, myvector<100>> mymap;

If the key 42 does not exist in the map yet, then:

auto& newvec = mymap[42];

will create an instance of type myvector<100>, which in turns, initialises an std::vector<bool> of size 100.

You could access the created std::vector<bool> object either through myvector's data data member or by performing reinterpret_cast<std::vector<bool>&>(newvec).


Using std::map::find() and std::map::emplace()

Another approach would be to use std::map::find() instead of std::map::operator[]() to first find out whether a given key already exists in the map by comparing its returned iterator against the one returned by std::map::end(). If the given key does not exist, then construct the vector using std::map::emplace().

In your example, the newvec could be initialized for this approach by means of the ternary opererator:

auto it = mymap.find(42); // search for an element with the key 42
bool is_key_in_map = it != mymap.end();
// if the element with the given key exists, then return it, otherwise
// construct it
auto& newvec = is_key_in_map? it->second: 
            mymap.emplace(42, std::vector<bool>(100, true)).first->second;

Actually, you can directly call std::map::emplace() without checking whether the given key already exists, but that will cost the useless creation of a temporary object (i.e., the std::vector<bool> object) if the key is already present in the map:

auto& newvec = mymap.emplace(42, std::vector<bool>(100, true)).first->second;

Since C++17: std::map::try_emplace()

You could use std::map::try_emplace() instead of std::map::emplace():

auto& newvec = mymap.try_emplace(42, 100, true).first->second;

This way, the temporary object, std::vector<bool>(100, true), won't be constructed if the map already contains the given key (i.e., if it already contains the key 42). This is, therefore, more efficient than using std::map::emplace(), since no temporary object will be constructed if not necessary. However, it does require C++17.

JFMR
  • 23,265
  • 4
  • 52
  • 76
3

You can use map::emplace member function:

mymap.emplace(42, std::vector<bool>(125, false));

to create a value of std::vector<bool>(125, false) for the key 42.

As ネロク mentions, the above emplace call will construct the value std::vector<bool>(125, false) even if the key 42 already exists in the map (this is also documented in the cppreference page I linked above). If this is to be avoided, you can first check if the value already exists using map::find and insert the value only if the key doesn't exist. That is:

if (mymap.find(42) == mymap.end()) {
    mymap.emplace(42, std::vector<bool>(125, false));
}

Both map::find and map::emplace has logarithmic time complexity; hence, calling find before emplace should not hurt the performance too much in performance critical scenarios.

eozd
  • 1,153
  • 1
  • 6
  • 15
  • 1
    It is worth to mention that the temporary `std::vector(125, false)` will be created even if `mymap` already contains that key. – JFMR Nov 17 '18 at 11:43
3

Use map::try_emplace() (or map::emplace() before C++17)

std::vector has a constructor which takes an initial size and an initial uniform value. In your case, suppose you want 125 as the initial size. With a stand-alone vector, you would use:

size_t num_bools_we_want = 1234;
std::vector<bool> my_vec(num_bools_we_want, false);

Now, std::map has a method named map::try_emplace() which forwards arguments to a constructor of the value type, which effectively allows you to choose the constructor it will use for a new element. Here's how to use it

mymap.try_emplace(42, num_bools_we_want, false);

to create a value of std::vector<bool>(num_bools_we_want, false) for the key 42. No temporary vectors are created (regardless of compiler optimizations).

The only "problem" with this solution is that try_emplace() only exists since C++17. Since you asked about C++11 - that version of the standard introduced map::emplace(), which does almost the same thing except for an issue with making a copy of the key. See this question for a discussion of the difference between emplace() and try_emplace().

einpoklum
  • 118,144
  • 57
  • 340
  • 684