如何为基于文本的命令控制台存储参数化、强类型的函数

问题描述 投票:0回答:1

用例

我正在尝试为基于文本的命令控制台构建一个小型解释器。 例如,假设某处有一个函数:

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
的类型,但这听起来像是一个指数复杂度解决方案。

有没有好的方法来克服这些限制? 我希望我能够很好地描述问题。

c++ c++17 interpreter std-variant
1个回答
0
投票

您可以从以下内容开始:

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";
    }
}

演示

© www.soinside.com 2019 - 2024. All rights reserved.