【发布时间】:2015-01-18 15:36:06
【问题描述】:
我有一个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 的子菜单。这个额外的功能会非常方便,如果说主菜单有 @ 987654325@ 作为其选项的类型,然后其中一个选项是“取出武器”,其子菜单理想情况下希望将 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 的建议,我使用以下简化代码(真正的 Menu 类将具有链接的选项列表,用户将通过输入从中选择)。但也有缺点。
#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<T>的基类,例如BaseMenu,然后子菜单可以是指向此基类的指针... -
你推荐使用 boost::any 作为 choose() 函数的返回类型吗?
-
为什么你希望它可以有不同类型的子菜单,而主菜单项必须都是相同的类型?这似乎是一组奇怪的限制,真的。
-
子菜单中的选项也都是相同的类型,但该类型可能与主菜单的选项类型不同。第一个动机来自我已经为
Menu<T>提供的“返回上一个菜单”选项。就像现在一样,每当我想通过更改T类型(当前必须是新菜单而不是子菜单)前进到不同的菜单时,我无法“返回”到以前的菜单不同种类。将所有菜单作为子菜单放在根菜单中可以解决这个问题。
标签: c++ templates c++11 variadic