c++ - 具有可变参数模板的数据结构

标签 c++ templates c++11 variadic

我有一个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;我试图创建一个简单的 int 选项菜单和字符串选项子菜单,甚至无法编译,因为子菜单的类型为 Menu<std::string>这与类模板不匹配。这也没有意义,因为 Menu<int, std::string>是从 choose() 返回 int函数,但进入其子菜单将返回一个字符串。这里需要 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 检查以获得所需的返回项。但所选择的元素需要储存,而不仅仅是短期使用。我欢迎提出改进意见。

最佳答案

一种解决方案是创建 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;

  ...
};

为了简洁起见,这是类的简化版本,但如果您添加嵌套的 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);
  ...
}

更新:choose() 功能的可能实现可以使用访问者模式。您需要为菜单中包含的每种类型提供一个重载 operator() 的类型(在本例中为 intstd::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() 函数的想法有点困难,但您应该能够调整上述内容以适应大多数场景。

关于c++ - 具有可变参数模板的数据结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28011468/

相关文章:

c++ - g++ 编译器为表达式提供 << 类型错误,但在 Visual Studio 中有效

c++ - 为什么这个菱形图案有歧义?

c++ - 类POD成员变量初始化

c++ - SDL2 透明背景

c++ - 与 c 中的链接相比,c++ 链接中 namespace 的影响是什么?

c++ - 为什么 std::bitset 只取 constexpr 值?

c++ - 将模板化参数类型转换为字符串

c++ - 我可以将 "token pasting operator"与 'const' 模板参数一起使用吗?

c++ - Variadic Templates - 参数包扩展理解

c++ - 现代 C++ 的实验特性对于长期项目是否可靠?