Как скопировать значения из std::map в std::vector с помощью std::copy

Цель – в одну строку скопировать все значения из std::map в std::vector.

Да, вместо использования std::copy можно просто в цикле перебрать все значения словаря и закинуть их в вектор. Тем не менее, интересует именно реализация с помощью std::copy.

Минимальный пример:

#include <iostream>
#include <string>
#include <map>
#include <algorithm>


int main()
{
    std::map<int, std::string> m;
    m[0] = "Hello";
    m[7] = "Heey";
    m[-59] = "R";
    m[1024] = "Rrrrrrrrroooooooooocccccccckkkkkk";

    std::vector<std::string> res;
    
    //  Хочется, чтобы строка работала как-то так, но проблема в том, что элементы словаря – пары, а не только значения
    // std::copy(m.begin(), m.end(), res.begin());

    // Здесь планирую получить вывод в виде всех значений словаря m
    for (std::vector<std::string>::const_iterator elem = res.begin(); elem != res.end(); ++elem)
        std::cout << *elem << std::endl;

    return 0;
}

Таким образом, с моей точки зрения, вопрос сводится к следующему: как получить итератор на последовательность значений словаря, не беря во внимание ключи? То есть, чтобы, разыменовав итератор, получать не std::pair, а std::string в моём случае.

P.S. Использую C++07, то есть, мне не доступен функционал, появившийся в C++11 (Просьба не спрашивать, почему. Так нужно и нужно не мне.)


Ответы (2 шт):

Автор решения: Alexey Nikolaev

К сожалению std::copy для этой цели не годится. Рекомендую воспользоваться алгоритмом std::for_each или std::transform. Для старого стандарта придется написать функтор, а для С++11 и выше достаточно будет лямбды:

  1. Пример решения с for_each:

    #include <iostream>
    #include <string>
    #include <map>
    #include <algorithm>
    
    //Функтор для вставки в вектор (в С++11 в нем нет необходимости)
    class BackInserter
    {
    public:
        explicit BackInserter(std::vector<std::string>& vec) :
            m_ref_vec(vec)
         {}
    
        void operator()(const std::pair<int, std::string>& element)
        {
            m_ref_vec.push_back(element.second);
        }
    
    private:
        std::vector<std::string>& m_ref_vec;
    };
    
    int main()
    {
        std::map<int, std::string> m;
        m[0] = "Hello";
        m[7] = "Heey";
        m[-59] = "R";
        m[1024] = "Rrrrrrrrroooooooooocccccccckkkkkk";
    
        std::vector<std::string> res;
        BackInserter fInserter(res);
    
        std::for_each(m.begin(), m.end(), fInserter);
    
        // C++11 
        //std::for_each(
        //   m.begin(), 
        //   m.end(), 
        //   [&res](const std::pair<int, std::string>& element)
        //{
        //   res.push_back(element.second);
        //});
    
        // Здесь планирую получить вывод в виде всех значений словаря m
        for (std::vector<std::string>::const_iterator elem = res.begin(); elem != res.end(); ++elem)
            std::cout << *elem << std::endl;
    
        return 0;
    }
    
  2. Пример решения с transform:

    #include <iostream>
    #include <string>
    #include <map>
    #include <algorithm>
    
    class TransformFunctor
    {
    public:
        std::string operator()(const std::pair<int, std::string>& element)
        {
            return element.second;
        }
    };
    
    int main()
    {
        std::map<int, std::string> m;
        m[0] = "Hello";
        m[7] = "Heey";
        m[-59] = "R";
        m[1024] = "Rrrrrrrrroooooooooocccccccckkkkkk";
    
        std::vector<std::string> res;
        res.resize(m.size());
        std::transform(m.begin(), m.end(), res.begin(), TransformFunctor());
    
        //C++11
        //std::transform(
        //    m.begin(), 
        //    m.end(),
        //    res.begin(),
        //    [](const std::pair<int, std::string>& element)->std::string
        //{
        //    return element.second;
        //});
    
    
        // Здесь планирую получить вывод в виде всех значений словаря m
        for (std::vector<std::string>::const_iterator elem = res.begin(); elem != res.end(); ++elem)
            std::cout << *elem << std::endl;
    
        return 0;
    }
    
→ Ссылка
Автор решения: Fat-Zer

В С++ итератора, для значений словаря из коробки нет до сих пор (ещё не смотрел, поправят ли что-то в C++20). Печально, но факт. Зато своя обёртка для этого делается на коленке довольно просто. В элементарном случае это будет выглядеть как-то так:

template <typename T>
class second_unwrap_iterator : public T {
public:
  second_unwrap_iterator(T base)
    : T(base)
  { }

  const typename T::value_type::second_type& operator*() {
    return T::operator*().second;
  }
};

template <typename T>
second_unwrap_iterator<T> second_unwraper(T base) {
  return second_unwrap_iterator<T>(base);
}

// ...

std::copy(second_unwraper(m.cbegin()), second_unwraper(m.cend()),
          std::back_inserter(res));

second_unwraper() необходим только для того чтобы не указывать параметры шаблона в конструкторе. Начиная с С++17 вывод типов в шаблонах улучшили и можно обойтись без оного:

std::copy(second_unwrap_iterator(m.begin()), second_unwrap_iterator(m.end()), 
          std::back_inserter(res));
→ Ссылка