如何将不同模板参数的std::function存入数组?
首先直接给结论:可以,但不能直接用普通数组存不同模板参数的std::function——因为std::function<int(int)>和std::function<int()>是完全独立的实例化类型,C++数组要求所有元素类型必须一致,所以直接定义Sometype funArray[2]是行不通的,除非你用类型擦除的手段。
先聊聊你尝试的void*转换方式为什么不靠谱:
你把std::function对象的地址转成void*再强制转回来,虽然编译能过,但这属于未定义行为。不同签名的std::function内部布局可能完全不同,强制类型转换会破坏类型安全,你那次运行成功只是巧合,换个编译器、换个环境大概率会崩溃或者输出错误结果。更别说你例子里调用(*fun)(a, b)——std::function<int(int)>只接受一个int参数,传两个本身就是签名不匹配,不管转不转void*都是错误的。
接下来给你两种靠谱的解决方案:
方案一:C++17及以上推荐用std::variant
std::variant是C++17引入的类型安全的联合类型,可以存多种不同类型的对象,完美适配你的场景:
#include <functional> #include <iostream> #include <variant> #include <array> int main() { // 定义两个不同签名的std::function std::function<int(int)> funA = [](int x) { return x + 5; }; std::function<int()> funB = []() { return 100; }; // 声明variant类型,包含我们需要的两种std::function using FuncVariant = std::variant<std::function<int(int)>, std::function<int()>>; // 用array存储,当然也可以用vector std::array<FuncVariant, 2> funArray = {funA, funB}; // 使用std::visit来调用每个元素,需要处理每种类型的情况 std::visit([](auto&& func) { using FuncType = std::decay_t<decltype(func)>; // 用constexpr分支判断当前func的类型 if constexpr (std::is_same_v<FuncType, std::function<int(int)>>) { std::cout << "调用funA:" << func(3) << std::endl; // 输出8 } else if constexpr (std::is_same_v<FuncType, std::function<int()>>) { std::cout << "调用funB:" << func() << std::endl; // 输出100 } }, funArray[0]); std::visit([](auto&& func) { using FuncType = std::decay_t<decltype(func)>; if constexpr (std::is_same_v<FuncType, std::function<int(int)>>) { std::cout << func(3) << std::endl; } else if constexpr (std::is_same_v<FuncType, std::function<int()>>) { std::cout << "调用funB:" << func() << std::endl; // 输出100 } }, funArray[1]); return 0; }
这种方式完全类型安全,不需要手动管理内存,是现在最推荐的做法。
方案二:用多态基类(兼容C++11及以上)
如果你还在使用C++11/14,可以用基类+子类模板的方式做类型擦除:
#include <functional> #include <iostream> #include <vector> #include <memory> // 定义抽象基类 class FunctionBase { public: virtual ~FunctionBase() = default; // 必须有虚析构,否则子类析构会出问题 }; // 模板子类,包装具体的std::function template<typename FuncType> class FunctionWrapper : public FunctionBase { public: explicit FunctionWrapper(FuncType func) : func_(std::move(func)) {} // 获取内部的std::function FuncType& get() { return func_; } const FuncType& get() const { return func_; } private: FuncType func_; }; int main() { std::function<int(int)> funA = [](int x) { return x * 2; }; std::function<int()> funB = []() { return 42; }; // 用智能指针数组/vector存储基类指针 std::vector<std::unique_ptr<FunctionBase>> funArray; funArray.emplace_back(std::make_unique<FunctionWrapper<decltype(funA)>>(funA)); funArray.emplace_back(std::make_unique<FunctionWrapper<decltype(funB)>>(funB)); // 使用时需要dynamic_cast转成具体的子类类型 if (auto wrapper = dynamic_cast<FunctionWrapper<decltype(funA)>*>(funArray[0].get())) { std::cout << "调用funA:" << wrapper->get()(3) << std::endl; // 输出6 } if (auto wrapper = dynamic_cast<FunctionWrapper<decltype(funB)>*>(funArray[1].get())) { std::cout << "调用funB:" << wrapper->get()() << std::endl; // 输出42 } return 0; }
这种方式需要你在调用时知道每个元素的具体签名,否则dynamic_cast会返回nullptr,需要做空指针检查。
最后再强调一遍:绝对不要用void*强制转换的方式,这种做法完全依赖未定义行为,没有任何可靠性可言,遇到问题只是时间问题。
内容的提问来源于stack exchange,提问作者0811张庆昊

