4

I'm trying to generate a random unordered_multimap of size 10 using the following code:

#include <algorithm>
#include <unordered_map>
#include <cstdlib>

int main()
{
    auto m = std::unordered_multimap<int, int>(10);
    std::generate(
        m.begin(),
        m.end(),
        [](){return std::pair{std::rand(),std::rand()};}
    );
} 

But it won't compile with an error

 In file included from /usr/include/c++/7/algorithm:62:0,
                 from main.cpp:2:
/usr/include/c++/7/bits/stl_algo.h: In instantiation of ‘void std::generate(_FIter, _FIter, _Generator) [with _FIter = std::__detail::_Node_iterator<std::pair<const int, int>, false, false>; _Generator = main()::<lambda()>]’:
<span class="error_line" onclick="ide.gotoLine('main.cpp',11)">main.cpp:11:5</span>:   required from here
/usr/include/c++/7/bits/stl_algo.h:4438:11: error: use of deleted function ‘std::pair<_T1, _T2>& std::pair<_T1, _T2>::operator=(typename std::conditional, std::is_copy_assignable<_T2> > >::value, const std::pair<_T1, _T2>&, const std::__nonesuch_no_braces&>::type) [with _T1 = const int; _T2 = int; typename std::conditional, std::is_copy_assignable<_T2> > >::value, const std::pair<_T1, _T2>&, const std::__nonesuch_no_braces&>::type = const std::pair&]’
  *__first = __gen();
  ~~~~~~~~~^~~~~~~~~
In file included from /usr/include/c++/7/utility:70:0,
                 from /usr/include/c++/7/unordered_map:38,
                 from main.cpp:1:
/usr/include/c++/7/bits/stl_pair.h:378:7: note: declared here
       operator=(typename conditional<
       ^~~~~~~~

Q: Is it possible to generate a random unordered_multimap using std::generate? If not, what's the best way to do it?

PS: I know I should be using std::default_random_engine instead of std::rand, and I do in the real code, this is just for demonstrative purposes.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    Probably not `std::default_random_engine`, but `std::mt19937` instead. Some implementations made [interesting choices](https://stackoverflow.com/q/21843172/3002139) on that front... – Baum mit Augen Jan 30 '19 at 13:05
  • Even if that compiled, `m` is still empty container, i.e. there is nothing between m.begin(), m.end(). `10` is bucket count in this case. Just use simple for loop and avoid all the trouble. – Dan M. Jan 30 '19 at 13:07

2 Answers2

5

Your code can't work with a map.

From documentation for std::generate:

The type Ret must be such that an object of type ForwardIt can be dereferenced and assigned a value of type Ret. ​

You can't assign a map item's key. The container "owns" the key. You can only assign the mapped value. This method of "setting each element" is simply unavailable for associative containers.

Furthermore, you constructed an unordered_multimap with bucket size 10, but no actual elements, so your range is empty anyway (maps aren't vectors!).

You could do this with std::inserter:

#include <algorithm>
#include <iterator>
#include <unordered_map>
#include <cstdlib>

int main()
{
    std::unordered_multimap<int, int> m;
    std::generate_n(
        std::inserter(m, m.begin()),
        10,
        [](){ return std::pair{std::rand(), std::rand()}; }
    );
}

…but, honestly, just do this in a loop and move on. ;)

#include <unordered_map>
#include <cstdlib>

int main()
{
    std::unordered_multimap<int, int> m;
    for (size_t i = 0; i < 10; i++)
        m.emplace(std::rand(), std::rand());
}

Everybody can read this.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
5

You can use the std::insert_iterator class template to achieve what you're looking for:

auto m = std::unordered_multimap<int, int>{};

std::generate_n(std::insert_iterator(m, m.begin()),
     10, [](){ return std::pair{std::rand(),std::rand()}; }); 
lubgr
  • 37,368
  • 3
  • 66
  • 117
  • 1
    `std::inserter` also compiles here. What's the difference between these two? – Przemysław Czechowski Jan 30 '19 at 13:17
  • 1
    @PrzemysławCzechowski None, now that the template argument can be deduced. [It was convenient before that](https://en.cppreference.com/w/cpp/iterator/inserter) – Lightness Races in Orbit Jan 30 '19 at 13:18
  • Ok, in `std::inserter` example, filling a set is done with: `std::fill_n(std::inserter(s, s.end()), 5, 2);`. So should I use `begin` or `end` of the container? – Przemysław Czechowski Jan 30 '19 at 13:20
  • 1
    Yes, it only saves typing. In pre-C++17, the class templates were the ones harder to type, as everyone would use the function template creating the type (e.g. `std::back_inserter` vs. `std::back_insert_iterator`). With class template argument deduction, this has changed. I would actually recommend using `std::inserter`. – lubgr Jan 30 '19 at 13:22