C++协程Signal/Slot库co_yield模板推导失败问题求助
我尝试基于C++协程实现一个基础的Signal/Slot库,核心逻辑是:Signal成员可连接同签名的Slot,当co_yield某个Signal时触发已注册的Slot。
现有代码实现
Signal类定义
template<typename... T> class Signal { public: using slot_member = std::function<Slot(T...)>; struct SignalArgs { SignalArgs(std::span<slot_member> slots, T... args): args(args...), slots(slots) {} std::tuple<T...> args; std::span<slot_member> slots; }; SignalArgs operator()(T... args) { return SignalArgs(slots, args...); } private: (...) };
yield_value函数实现
执行co_yield my_sig(...)时,Promise会接收SignalArgs对象,对应的yield_value定义如下:
template<typename... T> std::suspend_never yield_value(typename Signal<T...>::SignalArgs args) { for (auto f: args.slots) { Slot s = std::apply(f(args.args)); s(); } return {}; }
报错信息
error: no matching member function for call to 'yield_value'
co_yield s(3, "lil");
^~~~~~~~
note: candidate function [with T = <>] not viable: no known conversion from 'Signal<int, const char *>::SignalArgs' to 'typename Signal<>::SignalArgs' for 1st argument
std::suspend_never yield_value(typename Signal<T...>::SignalArgs args)
问题原因
模板参数推导无法从Signal<T...>::SignalArgs这种嵌套类型反向推导出T...——这属于C++中的非推导上下文,编译器没办法从传入的Signal<int, const char*>::SignalArgs实例,反推外层Signal类的模板参数int, const char*。
解决方法
方法一:给SignalArgs添加嵌套类型,暴露模板参数
修改Signal类,让SignalArgs携带外层Signal的类型信息,这样yield_value可以直接从参数中推导所需类型:
template<typename... T> class Signal { public: using slot_member = std::function<Slot(T...)>; struct SignalArgs { // 添加嵌套类型,暴露外层Signal的模板参数信息 using SignalType = Signal<T...>; using ArgsTuple = std::tuple<T...>; SignalArgs(std::span<slot_member> slots, T... args): args(args...), slots(slots) {} std::tuple<T...> args; std::span<slot_member> slots; }; SignalArgs operator()(T... args) { return SignalArgs(slots, args...); } private: std::vector<slot_member> slots; // 补充缺失的成员定义 };
重写yield_value函数,利用嵌套类型推导:
template<typename Args> std::suspend_never yield_value(Args args) { using SignalType = typename Args::SignalType; using SlotFunc = typename SignalType::slot_member; for (SlotFunc f : args.slots) { // 修正原代码的std::apply调用错误:第一个参数是可调用对象,第二个是元组 Slot s = std::apply(f, args.args); s(); } return {}; }
方法二:将yield_value定义为Signal的友元函数
直接把yield_value作为Signal的友元,这样可以直接获取Signal的模板参数T...,避免推导问题:
template<typename... T> class Signal { public: using slot_member = std::function<Slot(T...)>; struct SignalArgs { SignalArgs(std::span<slot_member> slots, T... args): args(args...), slots(slots) {} std::tuple<T...> args; std::span<slot_member> slots; }; SignalArgs operator()(T... args) { return SignalArgs(slots, args...); } // 友元函数直接拿到T...参数,无需推导 friend std::suspend_never yield_value(SignalArgs args) { for (auto f : args.slots) { Slot s = std::apply(f, args.args); s(); } return {}; } private: std::vector<slot_member> slots; };
额外注意
原代码中的std::apply(f(args.args))是错误调用,正确的std::apply用法是将可调用对象和元组作为两个独立参数传入,即std::apply(f, args.args)。
内容的提问来源于stack exchange,提问作者guillaume guigue

