You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为文本式命令控制台存储带强类型参数的函数?

问题描述

应用场景

我正尝试为文本式命令控制台构建一个小型解释器。例如,假设有如下函数:

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的类型,但这似乎会带来指数级复杂度的问题。

有没有好的方法规避这些限制?


解决方案

核心思路:用多态实现类型擦除

放弃将所有回调类型塞进std::variant的方案,改用抽象基类+模板派生类的方式实现类型擦除,既支持任意参数类型的函数,又能保留强类型校验能力,同时避免类型组合爆炸。

具体实现

1. 定义抽象回调基类

using ParamType = std::variant<int32_t, float, std::string, bool>;

class BaseCommandCallback
{
public:
    virtual ~BaseCommandCallback() = default;
    // 接收解析后的参数,执行类型校验并调用回调
    virtual bool Execute(const std::vector<ParamType>& args) = 0;
    // 获取期望的参数类型列表,用于提前校验输入
    virtual const std::vector<std::type_index>& GetParamTypes() const = 0;
};

2. 实现模板化的具体回调类

这个类会自动推导目标函数的参数类型,处理参数转换和校验逻辑:

template<typename... Args>
class TypedCommandCallback : public BaseCommandCallback
{
public:
    explicit TypedCommandCallback(std::function<void(Args...)> func)
        : m_func(std::move(func))
    {
        // 初始化期望的参数类型列表
        (m_paramTypes.push_back(typeid(Args)), ...);
    }

    bool Execute(const std::vector<ParamType>& args) override
    {
        // 先检查参数数量是否匹配
        if (args.size() != sizeof...(Args))
            return false;

        // 尝试将每个variant转换为对应类型,失败则返回false
        try
        {
            ExecuteImpl(args, std::index_sequence_for<Args...>{});
            return true;
        }
        catch (const std::bad_variant_access&)
        {
            return false;
        }
    }

    const std::vector<std::type_index>& GetParamTypes() const override
    {
        return m_paramTypes;
    }

private:
    template<std::size_t... Idx>
    void ExecuteImpl(const std::vector<ParamType>& args, std::index_sequence<Idx...>)
    {
        m_func(std::get<Args>(args[Idx])...);
    }

    std::function<void(Args...)> m_func;
    std::vector<std::type_index> m_paramTypes;
};

3. 改造CommandInterpreterCallback

现在它只需要持有基类指针,无需自己存储参数和回调:

class CommandInterpreterCallback
{
public:
    template<typename Func>
    void SetFunction(Func&& func)
    {
        // 推导函数参数类型,创建对应TypedCommandCallback实例
        using FuncWrapper = decltype(std::function{std::forward<Func>(func)});
        using CallbackType = TypedCommandCallback<typename FuncWrapper::argument_type...>;
        m_callback = std::make_unique<CallbackType>(std::forward<Func>(func));
    }

    bool Execute(const std::vector<ParamType>& args)
    {
        return m_callback ? m_callback->Execute(args) : false;
    }

    const std::vector<std::type_index>& GetParamTypes() const
    {
        static const std::vector<std::type_index> empty;
        return m_callback ? m_callback->GetParamTypes() : empty;
    }

private:
    std::unique_ptr<BaseCommandCallback> m_callback;
};

4. 使用示例

// 注册命令
void RegisterCommands()
{
    CommandInterpreterCallback brightnessCb;
    brightnessCb.SetFunction(&SetBrightness);
    AddCommand("SetBrightness", &brightnessCb);

    // 注册带bool参数的命令
    CommandInterpreterCallback enabledCb;
    enabledCb.SetFunction([](bool enabled) { /* 执行逻辑 */ });
    AddCommand("SetEnabled", &enabledCb);
}

// 处理用户输入
void HandleUserInput(const std::string& input)
{
    // 拆分命令名和参数,解析为std::vector<ParamType>
    std::string cmd = ParseCommandName(input);
    std::vector<ParamType> args = ParseInputArgs(input);

    if (auto* cb = FindCommand(cmd))
    {
        if (!cb->Execute(args))
        {
            std::cout << "参数类型或数量错误:" << cmd << std::endl;
        }
    }
}

方案优势

  • 强类型校验:自动检查参数数量和类型,类型不匹配时直接拒绝执行
  • 原函数无侵入:无需修改原有业务函数的参数类型,依然可以正常直接调用
  • 扩展性强:新增参数类型只需扩展ParamType的variant定义,无需修改回调框架
  • 避免类型爆炸:用模板+多态替代所有std::function组合的variant存储,代码复杂度可控

内容的提问来源于stack exchange,提问作者alexpanter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 23:50:34