Search code examples
c++c++17sfinae

SFINAE, Invoking a functor if the operator is implemented


I have a std::tuple with a bunch of functors that implements callbacks with different arguments. I want to iterate over the tuple at compilation time and execute the functors compatibles with the arguments:

Let's say I have a set of functors:

struct functor_a { void operator()(const class_x&) {} };
struct functor_b { void operator()(const class_x&) {} void operator()(const class_y&) {} };
struct functor_c { void operator()(const class_x&) {} void operator()(const class_z&) {} };

Given a tuple containing them:

using tuple_type = std::tuple<functor_a, functor_b, functor_c>;

I want to do something like:

struct executor {

    template<typename message_type, typename... Type>
    static constexpr void exec(std::tuple<Types...>& tup, const message_type& msg) {
        exec(std::index_sequence_for<Types...>(), tup. msg);
    }

    template<typename message_type, std::size_t... Is>
    static void exec(std::index_sequence<Is...>, tuple_type& tup, const message_type& msg) {
        if constexpr (std::is_nothrow_invocable_v<typename std::tuple_element<Is, tuple_type>::type..., message_type>) {
            std::invoke(std::get<Is>(tup)..., msg);
        }
    }
}
};

In order to do this (or alternatively use std::invoke):

auto tup = get_tuple();
executor::exec(tup, class_x()); // Call functor a, b, c
executor::exec(tup, class_y()); // Call functor b
executor::exec(tup, class_z()); // Call functor c

I am having some problems with the constexpr condition that is always evaluated to true, and the code does not compile because it does not find the operator with the given argument:

std::is_nothrow_invocable_v<typename std::tuple_element<Is, tuple_type>::type..., message_type>

Solution

  • You can use a combination of std::apply, some generic lambdas and fold expressions to make a single function

    template <typename Tuple, typename... Args>
    constexpr void dispatch(Tuple&& tup, Args const&... args) 
    {
        constexpr auto dispatch_helper = [] (auto&& func, auto&&...args)
        {
            if constexpr (std::is_invocable_v<decltype(func), decltype(args)...>)
                std::invoke(std::forward<decltype(func)>(func), std::forward<decltype(args)>(args)...);
        };
    
        std::apply([&](auto&&... funcs)
        {
            (dispatch_helper(std::forward<decltype(funcs)>(funcs), args...), ...);
        }, std::forward<Tuple>(tup));
    }
    

    Here is a full example.