C++: вложенная карта

Вот определение:

struct nmap; struct nmap: map<string, boost::variant<string, nmap*>>{};

Последняя строка ниже не работает:

nmap my_map; my_map["a"] = "b"; my_map["c"] = new nmap; my_map["c"]["d"] = "e";

Что мне нужно добавить, чтобы это работало?


person user76568    schedule 08.03.2014    source источник
comment
Попробуйте (*my_map["c"])["d"] = "e"; может быть?   -  person Kerrek SB    schedule 09.03.2014
comment
Хорошо, попробуйте (*boost::get<nmap*>(my_map["c"]))["d"] = "e";.   -  person Kerrek SB    schedule 09.03.2014
comment
@KerrekSB Я проверю это. Могу ли я добиться этого без boost::variant?   -  person user76568    schedule 09.03.2014
comment
Невозможно сказать, но интуиция подсказывает, что у вашей реальной проблемы, вероятно, есть гораздо более приятное решение...   -  person Kerrek SB    schedule 09.03.2014
comment
@KerrekSB не уходи. ошибка C2107: недопустимый индекс, косвенность не разрешена   -  person user76568    schedule 09.03.2014
comment
У меня работает, но убедитесь, что вы уловили сделанное мной редактирование.   -  person Kerrek SB    schedule 09.03.2014
comment
@KerrekSB Да, это работает, спасибо! Я хотел бы немного приукрасить его. Но после того, как я закончу реализацию. Можете ли вы дать мне представление о том, как это может быть достигнуто более приятным способом? Под этим я подразумеваю именно то, что вы думаете. Кстати, пожалуйста/возможно, опубликуйте это как ответ, чтобы мы могли как-то отплатить вам :-)   -  person user76568    schedule 09.03.2014
comment
Я должен был подумать об этом сам, теперь я вижу... :/   -  person user76568    schedule 09.03.2014
comment
Я собирался опубликовать это как ответ, но я все еще не уверен на 100%, как boost::get работает во всех ситуациях, поэтому я рад оставить это кому-то другому. Я не знаю, что такое окружающая проблема, поэтому я не могу предложить фактическое решение, только сильное чувство, что какой бы ни была проблема, у нее будет более элегантное решение.   -  person Kerrek SB    schedule 09.03.2014
comment
@KerrekSB Мой ответ относится к категории «намного приятнее»? Я все еще согласен, трудно сказать, чего здесь пытается достичь ОП. Я только что ответил на понятие /общее/ о том, что хочу сохранить древовидную структуру в современном контейнере.   -  person sehe    schedule 09.03.2014
comment
@sehe: Ну, есть верхняя граница гораздо лучшего, чего можно достичь с помощью Boost TMP, но это прекрасная демонстрация рекурсивного варианта.   -  person Kerrek SB    schedule 09.03.2014


Ответы (1)


Я бы предложил либо использовать крошечного читаемого помощника:

#include <boost/variant.hpp>
#include <map>

using std::map;

struct nmap;
struct nmap: map<std::string, boost::variant<std::string, nmap*>>
{
    typedef boost::variant<std::string, nmap*> Variant;
    typedef map<std::string, Variant> base;

    friend nmap&       as_map(Variant& v)       { return *boost::get<nmap*>(v); }
    friend nmap const& as_map(Variant const& v) { return *boost::get<nmap*>(v); }

    friend std::string&       as_string(Variant& v)       { return boost::get<std::string>(v); }
    friend std::string const& as_string(Variant const& v) { return boost::get<std::string>(v); }
};

int main()
{
    nmap my_map;
    my_map["a"] = "b";
    my_map["c"] =  new nmap;

    as_map(my_map["c"])["d"] = "e";
}

Или пойти по пути рекурсивного варианта. Позвольте мне набросать:

Рекурсивные деревья вариантов

Это ИМО более элегантно:

#include <boost/variant.hpp>
#include <map>

using nmap  = boost::make_recursive_variant<std::string, std::map<std::string, boost::recursive_variant_> >::type;
using map_t = std::map<std::string, nmap>;

#include <iostream>
static std::ostream& operator<<(std::ostream& os, nmap const& map);

int main()
{
    nmap my_map = map_t
    {
        { "a", "b" },
        { "c", map_t
            {
                { "d", "e" },
                { "f", map_t
                    {
                        { "most nested", "leaf node" },
                        { "empty", map_t { } },
                    } },
            } },
        };

    std::cout << my_map;
}

На первый взгляд это может показаться более сложным, но на самом деле имеет ряд важных преимуществ:

  • больше не наследоваться от классов, не предназначенных для наследования
  • больше нет ограничения на то, что «корневой» объект /должен/ быть картой (теперь он может быть и строкой, поэтому этот вариант более последовательно соблюдается)
  • утечек памяти больше нет Из-за того, что вариант теперь фактически заботится о выделении фактических nmap экземпляров, полная семантика значений предустановлена.
  • позволяет идиоматическое посещение дерева, нет необходимости в разыменовании ifs и buts, например подумайте, как мы могли бы сделать быструю и грязную реализацию этого operator<<:

    static std::ostream& operator<<(std::ostream& os, nmap const& map)
    {
        struct print : boost::static_visitor<void>
        {
            print(std::ostream& os, int indent = 0) : os(os), indent(indent) { }
    
            void operator()(map_t const& map) const {
                os << "{";
                for(auto& item : map)
                {
                    os << "\n";
                    do_indent();
                    os << "    " << item.first << ": ";
                    boost::apply_visitor(print(os, indent+4), item.second);
                }
                if (!map.empty()) { os << "\n"; do_indent(); };
                os << "}";
            }
    
            void operator()(std::string const& str) const {
                os << str;
            }
    
        private:
            std::ostream& os;
            void do_indent() const { for (int n = indent; n>0; --n) os << ' '; }
            int indent = 0;
        };
    
        boost::apply_visitor(print(os), map);
        return os;
    }
    

Смотрите на coliru

Выход:

# g++ -std=c++11 -Wall -pedantic -Wextra main.cpp  && ./a.out

{
    a: b
    c: {
        d: e
        f: {
            empty: {}
            most nested: leaf node
        }
    }
}
person sehe    schedule 09.03.2014
comment
Добавлена ​​демонстрация на основе рекурсивных вариантов Boost, показывающая, как реализовать operator<< для вашего дерева без ручного переключения типа узла :) - person sehe; 09.03.2014
comment
Это просто красиво. И вы даже не представляете (на самом деле, возможно), как много я узнал из этого ответа! Благодарю вас! - person user76568; 10.03.2014
comment
Я наткнулся на этот ответ при поиске хорошей реализации для вложенного словаря с вариантным типом значения (например, map<string, boost::variant<int, double, string, map<...> > >). Хотя показанная здесь реализация хорошо подходит для итеративного доступа к контейнеру (идеома посетителя boost::variant), произвольный доступ к контейнеру кажется невозможным. Есть ли способ заставить это работать? - person mzoll; 07.04.2021
comment
@mzoll Конечно, по цене. Например. используя vector<pair<key, value>, вы можете получить это. См., например, здесь, где я использую это для хранения значений JSON: github.com/sehe/spirit-v2-json/blob/master/json.hpp#L51 В качестве альтернативы вы можете использовать Boost MultiIndex, чтобы иметь оба, например, stackoverflow.com/questions/49371896/, но заменяет индекс sequenced на random_access: coliru.stacked-crooked.com/a/4a11d98bc5e2461f - person sehe; 07.04.2021