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

如何用std::any存储可调用对象?解决std::any_cast失败问题

问题根源

原代码的核心问题有两个:

  1. std::any存储的是lambda闭包、std::bind返回值这类原始可调用类型,而非std::function,导致std::any_cast<std::function<void(T...)>>因类型不匹配直接失败。
  2. bar模板的参数T...是调用时传入的参数类型,和注册回调的参数类型完全脱节,既无法做编译期校验,也无法利用隐式类型转换。
修正方案

根据你需要的「参数不匹配触发编译错误」核心需求,推荐两种不同场景的方案:

方案一:Test改为模板类(编译期校验参数,推荐)

将Test类改为模板类,用模板参数固定回调的参数类型,编译期就能校验bar的传入参数是否匹配,完全符合需求,且代码简洁高效。

#include <functional>
#include <iostream>
#include <string>

template<typename... Args>
class Test
{
private:
    std::function<void(Args...)> cb_;

public:
    // 自动适配任何可调用对象(lambda、std::function、std::bind返回值)
    template <typename Callable>
    void foo(Callable&& cb)
    {
        cb_ = std::forward<Callable>(cb);
    }

    // 触发回调,参数类型必须与模板参数匹配,否则直接编译报错
    void bar(Args... args)
    {
        if (cb_) {
            std::invoke(cb_, std::forward<Args>(args)...);
        }
    }
};

void test(int a)
{
    std::cout << a << std::endl;
}

int main()
{
    Test<std::string> t1;
    t1.foo([](std::string s) { std::cout << s << std::endl; });
    t1.bar("hello"); // 编译通过,const char*隐式转换为std::string
    // t1.bar(123); // 编译错误:参数类型不匹配

    Test<int> t2;
    t2.foo(std::bind(test, std::placeholders::_1));
    t2.bar(2); // 编译通过
    // t2.bar("test"); // 编译错误:参数类型不匹配
}

方案二:保持Test非模板类(运行时校验参数)

如果必须保持Test为非模板类,可通过类型擦除实现,但只能在运行时校验参数匹配,无法触发编译错误。

#include <any>
#include <functional>
#include <iostream>
#include <string>
#include <tuple>
#include <typeinfo>
#include <memory>

// 辅助工具:推导可调用对象的参数类型
template<typename T>
struct FuncTraits;

template<typename R, typename... Args>
struct FuncTraits<std::function<R(Args...)>> {
    using ArgsTuple = std::tuple<Args...>;
    using FuncType = std::function<R(Args...)>;
};

template<typename R, typename... Args>
struct FuncTraits<R(*)(Args...)> {
    using ArgsTuple = std::tuple<Args...>;
    using FuncType = std::function<R(Args...)>;
};

template<typename T>
struct FuncTraits : FuncTraits<decltype(&T::operator())> {};

template<typename C, typename R, typename... Args>
struct FuncTraits<R(C::*)(Args...) const> {
    using ArgsTuple = std::tuple<Args...>;
    using FuncType = std::function<R(Args...)>;
};

class Test
{
private:
    struct CallbackBase {
        virtual ~CallbackBase() = default;
        virtual bool match_args(const std::type_info& ti) const = 0;
        virtual void invoke(void* args) const = 0;
    };

    template<typename... Args>
    struct CallbackImpl : CallbackBase {
        std::function<void(Args...)> cb;

        CallbackImpl(std::function<void(Args...)> func) : cb(std::move(func)) {}

        bool match_args(const std::type_info& ti) const override {
            return ti == typeid(std::tuple<Args...>);
        }

        void invoke(void* args) const override {
            auto& args_tuple = *static_cast<std::tuple<Args...>*>(args);
            std::apply(cb, args_tuple);
        }
    };

    std::unique_ptr<CallbackBase> cb_;

public:
    template <typename Callable>
    void foo(Callable&& cb)
    {
        using Traits = FuncTraits<Callable>;
        using FuncType = typename Traits::FuncType;
        using Args = typename Traits::ArgsTuple;

        cb_ = std::make_unique<CallbackImpl<std::tuple_element_t<0, Args>, std::tuple_element_t<1, Args>...>>(
            FuncType(std::forward<Callable>(cb))
        );
    }

    template <typename... Args>
    void bar(Args&&... args)
    {
        using ArgsTuple = std::tuple<std::decay_t<Args>...>;
        if (!cb_) {
            std::cerr << "未注册回调" << std::endl;
            return;
        }
        if (!cb_->match_args(typeid(ArgsTuple))) {
            std::cerr << "参数类型不匹配" << std::endl;
            return;
        }
        auto args_tuple = std::make_tuple(std::forward<Args>(args)...);
        cb_->invoke(&args_tuple);
    }
};

void test(int a)
{
    std::cout << a << std::endl;
}

int main()
{
    Test t1;
    t1.foo([](std::string s) { std::cout << s << std::endl; });
    t1.bar("hello"); // 正常输出hello
    t1.bar(123); // 运行时提示参数不匹配

    Test t2;
    t2.foo(std::bind(test, std::placeholders::_1));
    t2.bar(2); // 正常输出2
    t2.bar("test"); // 运行时提示参数不匹配
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 20:12:02