c++c++20c++-coroutinec++-templates

template deduction with co_yield


I am trying do to a basic signal/slot library with coroutines. The idea is that you have signal members, connect them with slots that have the same signature, and when you co_yield a signal it calls the registered slots. Signal class looks like this:

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:
    (...)
};

The important part is SignalArgs, since when you do a co_yield my_sig(...) it's what the promise sees. the yield values is defined as such:

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

My problem is with the template deduction:

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)

Clearly the template deduction doesn't work in this case and I wonder what can I do.


Solution

  • The problem is that Signal<T...>:: is a non-deduced context for T.... What you can do is move SignalArgs out to be it's own template.

    namespace detail {
    template<typename... T>
    struct SignalArgs {
        using slot_member = std::function<Slot(T...)>;
    
        SignalArgs(std::span<slot_member> slots, T... args): args(args...), slots(slots)  {}
        std::tuple<T...> args;
        std::span<slot_member> slots;
    };
    }
    
    template<typename... T>
    class Signal {
    public:
        using slot_member = std::function<Slot(T...)>;
    
        detail::SignalArgs<T...> operator()(T... args) {
            return { slots, args... };
        }
    private:
        (...)
    };
    
    template<typename... T>
    std::suspend_never yield_value(typename detail::SignalArgs<T...> args)
    {
        for (auto& f: args.slots) {
            Slot s = std::apply(f, args.args);
            s();
        }
        return {};
    }