我正在尝试为基于文本的命令控制台构建一个小型解释器。 例如,假设某处有一个函数:
void SetBrightness(float brightness) { /* ... */ }
然后应该可以在控制台中写入
"SetBrightness 0.5"
,这应该会导致上面的函数被调用。我们可以在运行时注册一个命令:
void AddCommand(const std::string& command, CommandInterpreterCallback* callback);
此函数不仅应将回调函数绑定到字符串command
,还应将与回调期望的内容匹配的
强类型参数列表绑定。我定义了一个类来保存回调:
using ParamType = std::variant<int32_t, float, std::string, bool>;
class CommandInterpreterCallback
{
public:
void SetParameters(const std::vector<ParamType>& args)
{
mNumParameters = args.size();
if (mNumParameters >= 1) { mArg1 = args[0]; }
// ...
}
void SetFunction(std::function<void> callback)
{
mCallback = callback; // TODO: How to solve this?
}
private:
uint8_t mNumParameters = 0;
ParamType mArg1 = ParamType<bool>; // possibly an array instead here
std::function<void> mCallback; // TODO: How to solve this?
};
我真的希望参数传递是强类型的,例如函数
SetBrightness()
必须有一个浮点数。因此,如果用户编写 "SetBrightness true"
,则不应调用该函数。另外,我不想将函数参数指定为 std::variant<...>
,因为它看起来很混乱,并且使得从代码的其他部分调用该函数变得更加困难。
但我不确定如何在类中声明
mCallback
成员,因为它应该以某种方式是参数化的。我知道我可以将类型设置为例如std::function<void(float)>
。但是,如果我想绑定另一个接受布尔值的函数怎么办?
也许我也可以使用
std::variant
来指定 mCallback
的类型,但这听起来像是一个指数复杂度解决方案。
有没有好的方法来克服这些限制? 我希望我能够很好地描述问题。
您可以从以下内容开始:
static std::map<std::string, std::function<void(const std::string&)>> commands;
template <typename T>
T extract(std::stringstream& ss)
{
T t{};
ss >> t;
if (!ss) {
throw std::runtime_error("Invalid argument");
}
return t;
}
template <typename...Ts>
void AddCommand(const std::string& name, void(*f)(Ts...))
{
commands.emplace(name, [f](const std::string& s){
std::stringstream ss(s);
try {
// {..} guarantees left-to-right order.
std::tuple args = {extract<std::decay_t<Ts>>(ss)...};
std::apply([f](const auto&... args){ f(args...); }, args);
} catch (const std::exception& ex) {
std::cout << "ERROR: " << ex.what() << std::endl;
}
});
}
void parseCommand(const std::string& s)
{
std::stringstream ss(s);
std::string command;
ss >> command;
if (auto it = commands.find(command); it != commands.end()) {
std::string arg;
std::getline(ss, arg);
it->second(arg);
} else {
std::cout << "Unknown command\n";
}
}