5

I wonder if there is any trick to use copy with maps to copy the contents of map into an array. Because STL maps are by the combination of a key value and a mapped value an element of a map forms a key value pair. That prevents us to use standard algorithms like std::copy. For example following code gives error:

#include <iostream>
#include <algorithm>
#include <vector>
#include <map>

int
main()
{
  std::map <int, double> test(4);
  test[0] = 11;
  test[2] = 1.23;
  test[3] = 23.29;
  test[1] = 12.12;
  double *test_arr = (double *) malloc(4 * sizeof(double));
  std::copy(test.begin(), test.end(), test_arr);
  std::cout << test_arr[3] << std::endl;
  return 0;
}

Error:

stl_copy_tests.cpp: In function ‘int main()’:
stl_copy_tests.cpp:9:32: error: no matching function for call to ‘std::map<int, double>::map(int)’
/usr/include/c++/4.5/bits/stl_map.h:170:7: note: candidates are: std::map<_Key, _Tp, _Compare, _Alloc>::map(const std::map<_Key, _Tp, _Compare, _Alloc>&) [with _Key = int, _Tp = double, _Compare = std::less<int>, _Alloc = std::allocator<std::pair<const int, double> >, std::map<_Key, _Tp, _Compare, _Alloc> = std::map<int, double>]
/usr/include/c++/4.5/bits/stl_map.h:159:7: note:                 std::map<_Key, _Tp, _Compare, _Alloc>::map(const _Compare&, const allocator_type&) [with _Key = int, _Tp = double, _Compare = std::less<int>, _Alloc = std::allocator<std::pair<const int, double> >, allocator_type = std::allocator<std::pair<const int, double> >]
/usr/include/c++/4.5/bits/stl_map.h:150:7: note:                 std::map<_Key, _Tp, _Compare, _Alloc>::map() [with _Key = int, _Tp = double, _Compare = std::less<int>, _Alloc = std::allocator<std::pair<const int, double> >]
In file included from /usr/include/c++/4.5/bits/char_traits.h:41:0,
                 from /usr/include/c++/4.5/ios:41,
                 from /usr/include/c++/4.5/ostream:40,
                 from /usr/include/c++/4.5/iostream:40,
                 from stl_copy_tests.cpp:1:
/usr/include/c++/4.5/bits/stl_algobase.h: In static member function ‘static _OI std::__copy_move<<anonymous>, <anonymous>, <template-parameter-1-3> >::__copy_m(_II, _II, _OI) [with _II = std::_Rb_tree_iterator<std::pair<const int, double> >, _OI = double*, bool <anonymous> = false, bool <anonymous> = false, <template-parameter-1-3> = std::bidirectional_iterator_tag]’:
/usr/include/c++/4.5/bits/stl_algobase.h:404:70:   instantiated from ‘_OI std::__copy_move_a(_II, _II, _OI) [with bool _IsMove = false, _II = std::_Rb_tree_iterator<std::pair<const int, double> >, _OI = double*]’
/usr/include/c++/4.5/bits/stl_algobase.h:442:39:   instantiated from ‘_OI std::__copy_move_a2(_II, _II, _OI) [with bool _IsMove = false, _II = std::_Rb_tree_iterator<std::pair<const int, double> >, _OI = double*]’
/usr/include/c++/4.5/bits/stl_algobase.h:474:18:   instantiated from ‘_OI std::copy(_II, _II, _OI) [with _II = std::_Rb_tree_iterator<std::pair<const int, double> >, _OI = double*]’
stl_copy_tests.cpp:15:47:   instantiated from here
/usr/include/c++/4.5/bits/stl_algobase.h:319:6: error: cannot convert ‘std::pair<const int, double>’ to ‘double’ in assignment

Is there any easy trick/hack to overcome this problem.

Disclaimer: Not interested in solutions that iterates over map in a for loop and adds elements to the array.

systemsfault
  • 15,207
  • 12
  • 59
  • 66

5 Answers5

6

You could use std::transform instead:

template <typename T, typename U>
const U &extract_second(const std::pair<T,U> &p)
{
    return p.second;
}

std::transform(test.begin(), test.end(), test_arr, extract_second<int,double>);

And as @Andre points out in a comment below, if you want a slightly more verbose overhead, you can avoid having to explicitly state the template arguments via a functor:

struct extract_second
{
    template <typename T, typename U>
    const U operator() (const std::pair<T,U> &p) const
    {
        return p.second;
    }
};

std::transform(test.begin(), test.end(), test_arr, extract_second());

I'm sure there's a less-verbose solution using Boost binders, but I can't remember the syntax off the top of my head.

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • 2
    I like to write `extract_second` as: `struct extract_second{template const U& operator()(const std::pair&p){return p.second;}const};` so that that you can omit the template arguments in the call: `std::transform(test.begin(), test.end(), test_arr, extract_second());` – André Caron Jun 01 '11 at 11:28
  • @Andre: Thanks! I've integrated your version into my answer. – Oliver Charlesworth Jun 01 '11 at 11:38
  • Thanks Oli somehow I forgot to use std::transform, also James pointed out the boost way for extracting value out of the map. – systemsfault Jun 01 '11 at 11:50
  • 1
    @systemsfault: No problem. Note that @James' solution is really equivalent to mine; I think there's a solution with `boost::bind` that avoids needing to explicitly write a functor. – Oliver Charlesworth Jun 01 '11 at 11:53
4

Ewww, malloc? Anyway, if you want to copy a map, you have to remember the keys too.

int main()
{
    std::map <int, double> test(4);
    test[0] = 11;
    test[2] = 1.23;
    test[3] = 23.29;
    test[1] = 12.12;
    std::vector<std::pair<int, double>> test_arr(test.size());
    std::copy(test.begin(), test.end(), test_arr.begin());
    std::cout << test_arr[3] << std::endl;
    return 0;
}
Puppy
  • 144,682
  • 38
  • 256
  • 465
3

If you consider std::map an STL container, then it is a container of std::pair<key_type, mapped_type>. (This is what its value_type is defined to be, and it is designed so that it can be used as a container.) If you want simply one part of it, the correct function is std::transform, with a transformation function which maps the value_type to either the key_type or the mapped_type. (If you make much use of std::pair—or std::map, whose value_type is an std::pair, you should probably have functional objects for this in your tool kit:

struct ExtractFirst
{
    template<typename Pair>
    typename boost::remove_const<typename Pair::first_type>::type
                        operator()( Pair const& from ) const
    {
        return from.first;
    }
};

, and the same thing for ExtractSecond.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
2

Your target would be an arraystd::vector[please!] of std::pair<int,double> objects unless, yes, you unroll it yourself.

(You could create your own InputIterator as a proxy, or play with std::transform and a std::back_inserter, but that's just being silly. You'll make your code far more verbose than just looping through the map.)

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    Or if you want an array of doubles and don't want to write a loop, you could instead write an output iterator that wraps `test_arr`, with `operator=(const pair &p) { *(this->underlying_ptr) = p.second; }`. And there might be something in the Boost iterator helpers to do that, I haven't looked. – Steve Jessop Jun 01 '11 at 11:12
  • @Steve: Yep, you could apply the logic to the `OutputIterator` just as easily as to the `InputIterator`. Probably more easily, now you mention it. – Lightness Races in Orbit Jun 01 '11 at 11:13
2

The simplest way is to use std::transform in combination with boost::bind:

typedef std::map<int, double> map_t;
map_t mm;

// add elements to mm
// ...

// copy
typedef std::vector<double> vec_t;
vec_t vv;
vv.reserve( mm.size() );
std::transform( mm.begin(), mm.end(), std::back_inserter(vv), 
  boost::bind( &map_t::value_type::second, _1 ) );

If you could use C++0x (without boost):

std::transform( mm.begin(), mm.end(), back_inserter(vv), 
  [](map_t::value_type val) -> double { return val.second; } );
// or
std::for_each( mm.begin(), mm.end(), 
  [&vv](map_t::value_type val) { vv.push_back( val.second ); } );
Kirill V. Lyadvinsky
  • 97,037
  • 24
  • 136
  • 212