Как инициализировать карту из набора

Я хочу установить ключи std::map, используя элементы std::set (или std::vector).

Что-то вроде следующего...

std::set<int> keys = { 3,4,6 };
std::map<int,string> results(keys); // syntax error

Можно ли это сделать без явного перебора набора?


person Brent Bradburn    schedule 01.10.2016    source источник
comment
1) Есть ли какая-то конкретная причина, по которой вы хотите? 2) Какими должны быть строки значений?   -  person Beta    schedule 01.10.2016
comment
Я был бы очень удивлен, если бы это было возможно, поскольку доступ к неинициализированным значениям, полученным в результате этого оператора, не должен быть допустимым поведением.   -  person ApproachingDarknessFish    schedule 01.10.2016
comment
@Beta: 1) Я хочу создать API, который позволяет кратко указывать ключи (без учета значений). 2) Я хочу, чтобы значение было инициализировано по умолчанию.   -  person Brent Bradburn    schedule 01.10.2016
comment
Но почему вы не хотите явно перебирать набор? Если у вас нет конкретной причины, я думаю, что это больше вопрос о стиле кодирования.   -  person Brian Bi    schedule 01.10.2016
comment
@Brian: Да, это в основном для стиля. Мне просто интересно, есть ли очень краткий способ сделать задание. Кажется, что это может быть естественным, исходя из сходства между двумя контейнерами.   -  person Brent Bradburn    schedule 01.10.2016
comment
Я думаю, что это можно сделать с помощью какого-нибудь очень искусного STL-дзюцу, но это будет гораздо менее лаконично — и читабельно, и удобно — по сравнению с простой итерацией по набору.   -  person Beta    schedule 01.10.2016
comment
@ApproachingDarknessFish Но это именно то, что позволяет map::operator[] -- инициализация по умолчанию.   -  person n.caillou    schedule 01.10.2016


Ответы (2)


Вы не можете. map не является set. Это принципиально разные контейнеры, даже если базовая структура схожа.


Тем не менее, все возможно. Конструктор диапазона std::map имеет линейное время, если элементы диапазона уже отсортированы, что set нам гарантирует. Так что все, что вам нужно сделать, это применить трансформатор к каждому элементу, чтобы получить новый диапазон. Проще всего было бы просто использовать что-то вроде boost::make_transform_iterator (или свернуть самостоятельно):

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{});
    });

который, если вам всегда нужна инициализация по умолчанию, может просто уменьшить до:

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);
person Barry    schedule 01.10.2016
comment
Каждый раз, когда кто-то говорит, что самым простым решением будет X, а X включает строку, определяющую class V, кто-то клянется никогда не изучать C++. :-) Примечание: мне нравится ответ, но чувак, так много угловых скобок. - person ShadowRanger; 01.10.2016
comment
Ответ начинается с того, что вы не можете, затем следует демонстрация того, как это сделать. Итерация скрыта внутри конструктора карты, что я и надеялся увидеть. Так что это правильный ответ, за исключением первых двух слов. :) - person Brent Bradburn; 02.10.2016

Можно ли это сделать без явного перебора набора?

Нет. Невозможно узнать ключи в наборе, не перебирая его. Вы можете написать функции, которые будут отображаться так, как если бы имело место неявное преобразование, но в конечном итоге эти функции должны выполнять итерацию исходной коллекции.

Простой способ заключается в следующем:

#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);
}

Конечно, мы можем использовать шаблоны, если это улучшит читабельность:

#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});
}
person Richard Hodges    schedule 01.10.2016
comment
Хороший пример того, как очень просто выполнить инициализацию на основе итераций. - person Brent Bradburn; 02.10.2016