2015-01-18 2 views
2

У меня есть класс Menu<T>, варианты которого являются элементами типа T, и он может иметь подменю типа Menu<T> (без ограничения глубины вложенных подменю).Структура данных с вариационными шаблонами

template <typename T> 
class Menu { 
    private: 
     class Option { 
      const std::string name; 
      const T item; 
      Menu<T>* submenu; 
      Option* next = nullptr; 
      friend class Menu<T>; 
      Option (const std::string& itemName, const T& t, Menu<T>* menu = nullptr) : name(itemName), item(t), submenu(menu) {} 
      ~Option() {if (submenu) delete submenu;} 
      inline T choose() const; 
      inline void print (int n) const; 
     }; 
     Option* first = nullptr; // first option in the menu 
     Menu<T>* parent = nullptr; 
     Option* parentOption = nullptr; 
     enum ChoosingType {Normal, Remove}; 
    public: 
     Menu() = default; 
     Menu (const Menu<T>&); 
     Menu& operator = (const Menu<T>&); 
     ~Menu(); 
     inline void insert (const std::string& itemName, const T& t, Menu<T>* submenu = nullptr, int pos = END_OF_MENU); 
     T choose() const {return chosenItem().first;} 
     inline int print() const; 
    private: 
     inline std::pair<T, int> chosenItem (ChoosingType = Normal) const; 
     inline Option* deepCopy (const Option*); 
}; 

И я проверил, что он работает правильно, но мой Menu<T> класс выше не поддерживает вложенные меню, элементы которого имеют другой тип, чем T. Эта дополнительная функция будет очень удобно, если, скажем, в главном меню имел Action как тип для своих опций, а затем один из вариантов - «Извлечь оружие», чье подменю в идеале должно было бы иметь Weapon в качестве его параметров, но, как сейчас, подменю снова должно иметь Action, поскольку его варианты.

Моя попытка обобщить с

template <typename T, typename U, typename... Rest> 
class Menu { // Menu with T as its option types. 
    private: 
     class Option { 
      const std::string name; 
      const T item; 
      Menu<U, Rest...>* submenu; // Submenu with U as its option types. 
      Option* next = nullptr; 
      friend class Menu<T, U, Rest...>; 
      Option (const std::string& itemName, const T& t, Menu<U, Rest...>* menu = nullptr) : name(itemName), item(t), submenu(menu) {} 
      ~Option() {if (submenu) delete submenu;} 
      inline T choose() const; 
      inline void print (int n) const; 
     }; 
     Option* first = nullptr; 
// .... 
} 

int main() { 
    Menu<int, std::string> menu; // Will not compile. 
} 

не является правильным, потому что Menu<int, std::string> menu; чего я пытаюсь создать простое меню ИНТ опций с подменю опций строки, даже не компилируется, поскольку тогда подменю типа Menu<std::string>, который не соответствует шаблону класса. Это также не имеет смысла, потому что Menu<int, std::string> должен возвращать int из своей функции choose(), но в свое подменю будет возвращена строка. boost :: variant нужен здесь?

Мне просто нужно указать, с чего начать. Я знаю, что это может показаться частью CodeReview, но там они хотят изучить мой код только в том случае, если он уже работает, но здесь моя попытка обобщить сейчас не работает (еще не началась), поэтому мне нужно обратиться к экспертов здесь, только с чего начать.

Обновление: По предложению gmbeard, я получил это работает со следующим упрощенным кодом (классы реального меню будут связаны список опций, которые пользователь выберет из через вход). Но есть недостатки.

#include <iostream> 
#include <string> 

struct Visitor { 
    virtual void visit (int&) = 0; 
    virtual void visit (std::string&) = 0; 
    virtual void visit (char& c) = 0; 
}; 

struct ChooseVisitor : Visitor { 
    std::pair<int, bool> chosenInt; 
    std::pair<std::string, bool> chosenString; 
    std::pair<char, bool> chosenCharacter; 
    virtual void visit (int& num) override { 
     chosenInt.first = num; 
     chosenInt.second = true; 
    } 
    virtual void visit (std::string& str) override { 
     chosenString.first = str; 
     chosenString.second = true; 
    } 
    virtual void visit (char& c) override { 
     chosenCharacter.first = c; 
     chosenCharacter.second = true; 
    } 
}; 

template <typename...> struct Menu; 

template <typename T> 
struct Menu<T> { 
    struct Option { 
     T item; 
     void accept (ChooseVisitor& visitor) {visitor.visit(item);} 
    }; 
    Option* option; // Assume only one option for simplicity here. 
    ChooseVisitor choose() const { 
     ChooseVisitor visitor; 
     option->accept(visitor); 
     return visitor; 
    } 
    void insert (const T& t) {option = new Option{t};} 
}; 

// A specialization for the Menu instances that will have submenus. 
template <typename T, typename... Rest> 
struct Menu<T, Rest...> { // Menu with T as its options type. 
    struct Option { 
     T item; 
     Menu<Rest...>* submenu; // Submenu with the first type in Rest... as its options type. 
     void accept (ChooseVisitor& visitor) {visitor.visit(item);} 
    }; 
    Option* option; 
    ChooseVisitor choose() const { 
    // In reality there will be user input, of course. The user might not choose to enter a submenu, 
    // but instead choose from among the options in the current menu. 
     ChooseVisitor visitor; 
     if (option->submenu) 
      return option->submenu->choose(); 
     else 
      option->accept(visitor); 
     return visitor; 
    } 
    void insert (const T& t, Menu<Rest...>* submenu = nullptr) {option = new Option{t, submenu};} 
}; 

int main() { 
    Menu<int, std::string, char> menu; 
    Menu<std::string, char> submenu; 
    Menu<char> subsubmenu; 
    subsubmenu.insert('t'); 
    submenu.insert ("", &subsubmenu); 
    menu.insert (0, &submenu); 
    const ChooseVisitor visitor = menu.choose(); 
    if (visitor.chosenInt.second) 
     std::cout << "You chose " << visitor.chosenInt.first << ".\n"; // Do whatever with it. 
    else if (visitor.chosenString.second) 
     std::cout << "You chose " << visitor.chosenString.first << ".\n"; // Do whatever with it. 
    else if (visitor.chosenCharacter.second) 
     std::cout << "You chose " << visitor.chosenCharacter.first << ".\n"; // Do whatever with it. 
} 

Выход:

You chose t. 

Самая большая проблема заключается в том, что ChooseVisitor необходимо постоянно обновляется для всех типов параметров возможно меню (это может закончиться буквально сотни элементов данных и перегрузками), а не упоминайте ужасные if-checks, чтобы получить желаемый возвращенный предмет. Но выбранный элемент нужно хранить, а не просто использовать на короткий срок. Я приветствую идеи для улучшения.

+2

вы должны рассмотреть возможность использования наследования и имеющим базовый класс для '' меню , например, 'BaseMenu', того submenues может быть указатели на этот базовый класс ... – Nim

+0

ли вы рекомендуете использовать подталкивание :: любого для тип возвращаемого значения для функции select()? – prestokeys

+0

Зачем вам нужно иметь подменю разного типа, в то время как элементы главного меню должны быть одного типа? На самом деле это странный набор ограничений. – Yakk

ответ

1

Одним из решений является создание некоторых частных специализаций Menu, чтобы развернуть пакет переменных параметров.

Во-первых, создать шаблон класса ...

// Can be incomplete; only specialized versions will be instantiated... 
template<typename... Args> class Menu; 

Теперь создать специализацию в конце цепочки меню (без подменю) ...

template<typename T> 
class Menu<T> 
{ 
public: 
    // No submenu in this specialization 
    using item_type = T; 
    std::vector<item_type> items; 
    ... 
}; 

Наконец, создать специализация для Menu экземпляров, которые будут иметь подменю ...

template<typename Head, typename... Tail> 
class Menu<Head, Tail...> 
{ 
public: 
    Menu<Tail...> submenu; 
    using item_type = Head; 
    std::vector<item_type> items; 

    ... 
}; 

Это simplifi ed версии вашего класса для краткости, но тот же принцип по-прежнему применяется, если вы добавляете вложенный класс Option.

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

template<typename T> 
void 
print_menu(Menu<T> const& menu) 
{ 
    for(auto i : menu.items) { 
    std::cout << i << std::endl; 
    } 
} 

template<typename... T> 
void 
print_menu(Menu<T...> const& menu) 
{ 
    for(auto i : menu.items) { 
    std::cout << i << std::endl; 
    } 
    print_menu(menu.submenu); 
} 

int 
main(int, char*[]) 
{ 
    Menu<int, std::string> menu{}; 
    menu.items.emplace_back(1); 
    menu.submenu.items.emplace_back("42"); 
    print_menu(menu); 
    ... 
} 

Update: Возможное внедрение choose() функциональности может использовать шаблон посетителя. Вы должны были бы обеспечить тип, который перегружает operator() для каждого типа, содержащегося в меню (в данном случае, int и std::string) ...

struct ChooseVisitor 
{ 
    void operator()(std::string const& string_val) const 
    { /* Do something when a string value is chosen */ } 

    void operator()(int int_val) const 
    { /* Do something when an int value is chosen */ } 
}; 

Аналогично функции print_menu, можно определить пару choose_menu функции перегрузки ...

template<typename... Types, typename Visitor> 
void 
choose_menu(Menu<Types...> const& menu, Visitor const& visitor) 
{ 
    for(auto i : menu.items) { 
    if(/* is menu item chosen? */) { 
     visitor(i); 
     return; 
    } 
    } 
    choose_menu(menu.Submenu, visitor); 
} 

template<typename Type, typename Visitor> 
void 
choose_menu(Menu<Type> const& menu, Visitor const& visitor) 
{ 
    for(auto i : menu.items) { 
    if(/* is menu item chosen? */) { 
     visitor(i); 
     return; 
    } 
    } 
} 

Это будет использоваться как так ...

Menu<int, std::string> menu{}; 
... 
choose_menu(menu, ChooseVisitor{}); 

Немного сложно получить то, что вы имели в виду для своей функции choose(), но вы должны уметь адаптировать вышеуказанное в соответствии с большинством сценариев.

+0

Отличное предложение! Я следил за вашей идеей, и все кажется прекрасным, в том числе print(), за исключением важной функции меню T choose() const, которая возвращает элемент, выбранный из меню (возможно, из его подменю)? Тип возврата может быть не T, потому что все зависит от того, какие подменю были введены. Нужно ли использовать boost: любой, если да, то как именно? Обновление в моем исходном вопросе показывает новый код, чтобы проиллюстрировать проблему, о которой я говорю. – prestokeys

+0

Возможно, вы сможете использовать вариант типа, учитывая, что вы будете хранить в меню/подменю. Другой вариант - использовать посетителя. Я не могу понять, как работает функция 'select()'. Он выглядит детерминированным, потому что он не принимает аргументов. – gmbeard

+0

Я обновил ответ, чтобы уточнить мое предложение посетителя – gmbeard

1

У меня есть небольшое чувство, что здесь все кончено.

Вот версия, использующая Boost Variant, как вы предполагали.

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

Menu menu = MenuT<int> { 
    { "one", 1 }, 
    { "two", 2 }, 
    { "three", 3 }, 

    { "forty-two", 42, 
      Menu(MenuT<std::string> { 
       {"Life, The Universe And Everything", "LtUae"}, 
       {"Dent", "Arthur", 
        Menu(MenuT<bool> { 
         {"yes", true}, 
         {"no", false}, 
        }) 
       }, 
      }) 
    }, 
}; 

Как вы можете видеть, он смешивает MenuT<int>, MenuT<std::string> и MenuT<bool> на различных уровнях. Вы можете посетить это без лишнего шума:

struct simple : boost::static_visitor<> 
{ 
    void operator()(Menu& m) const { boost::apply_visitor(*this, m); } 

    template <typename T> void operator()(MenuT<T>& m) const { 
     std::cout << "-----------\n"; 

     for (auto& o : m.options) { 
      std::cout << "option '" << o.name << "':\t" << o.item << "\n"; 
      if (o.submenu) 
       (*this)(*o.submenu); 
     } 
    } 
}; 

который печатает

----------- 
option 'one': 1 
option 'two': 2 
option 'three': 3 
option 'forty-two': 42 
----------- 
option 'Life, The Universe And Everything': LtUae 
option 'Dent': Arthur 
----------- 
option 'yes': true 
option 'no': false 

Live On Coliru

#include <string> 
#include <vector> 
#include <boost/optional.hpp> 
#include <boost/variant.hpp> 
#include <iostream> 

template <typename> struct MenuT; 

using Menu = boost::make_recursive_variant < 
     boost::recursive_wrapper<MenuT<int>>, 
     boost::recursive_wrapper<MenuT<std::string>>, 
     boost::recursive_wrapper<MenuT<bool>> 
    >::type; 

template <typename T> struct MenuT { 
    struct Option { 
     std::string   name; 
     T      item; 
     boost::optional<Menu> submenu; 

     Option(std::string name, T t, boost::optional<Menu> submenu = boost::none) 
      : name(name), item(t), submenu(submenu) 
     { } 

     T choose() const; 
     void print(int n) const; 
    }; 

    private: 
    template <typename U> friend struct MenuT; 
    friend struct visitors; 

    std::vector<Option> options; 
    //boost::optional<Menu&> parent; // TODO e.g. link_parents_visitor 
    enum ChoosingType { Normal, Remove }; 

    public: 
    enum SpecialPosition : size_t { END_OF_MENU = size_t(-1) }; 

    MenuT() = default; 
    MenuT(std::initializer_list<Option> options) : options(options) {} 

    void insert(const std::string &itemName, const T &t, boost::optional<Menu> submenu = boost::none, size_t pos = END_OF_MENU) { 
     auto it = (pos == END_OF_MENU 
       ? options.end() 
       : std::next(options.begin(), std::min(pos, options.size()))); 

     options.emplace(it, itemName, t, submenu); 
    } 

    T choose() const { return chosenItem().first; } 
    int print() const; 

    private: 
    std::pair<T, int> chosenItem(ChoosingType = Normal) const; 
}; 

struct visitors { 
    struct simple : boost::static_visitor<> 
    { 
     void operator()(Menu& m) const { boost::apply_visitor(*this, m); } 

     template <typename T> void operator()(MenuT<T>& m) const { 
      std::cout << "-----------\n"; 

      for (auto& o : m.options) { 
       std::cout << "option '" << o.name << "':\t" << o.item << "\n"; 
       if (o.submenu) 
        (*this)(*o.submenu); 
      } 
     } 
    }; 
}; 

static const visitors::simple demo { }; 

int main() 
{ 
    Menu menu = MenuT<int> { 
     { "one", 1 }, 
     { "two", 2 }, 
     { "three", 3 }, 

     { "forty-two", 42, 
       Menu(MenuT<std::string> { 
        {"Life, The Universe And Everything", "LtUae"}, 
        {"Dent", "Arthur", 
         Menu(MenuT<bool> { 
          {"yes", true}, 
          {"no", false}, 
         }) 
        }, 
       }) 
     }, 
    }; 

    std::cout << std::boolalpha; 
    demo(menu); 
} 

СЛОВО

  • Примечательным упущение я не инициализировать

    boost::optional<Menu&> parent; // TODO e.g. link_parents_visitor 
    
  • Я думаю, что вы все еще хотите упростить это (я думаю, что вы, возможно, не хотят разделения между Menut и Option в первую очередь. Вы просто представили это, пытаясь вложить различные варианты меню?