SFINAE,如果实现了运算符则调用函子

SFINAE, Invoking a functor if the operator is implemented

我有一个 std::tuple,它有一堆仿函数,用不同的参数实现回调。我想在编译时遍历元组并执行与参数兼容的仿函数:

假设我有一组仿函数:

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

给定一个包含它们的元组:

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

我想做类似的事情:

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

为了做到这一点(或者使用 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

我在使用 constexpr 条件时遇到一些问题,该条件始终被评估为 true,并且代码无法编译,因为它没有找到具有给定参数的运算符:

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

我发现使用普通的 SFINAE 比 constexpr if 更容易,在 -std=c++17 模式下使用 gcc 9.2.1 测试:

#include <iostream>
#include <tuple>
#include <functional>

struct class_x {};
struct class_y {};
struct class_z {};

struct functor_a {
    void operator()(const class_x&)
    {
        std::cout << "functor_a: class_x" << std::endl;
    }
};

struct functor_b {
    void operator()(const class_x&)
    {
        std::cout << "functor_b: class_x" << std::endl;
    }

    void operator()(const class_y&)
    {
        std::cout << "functor_b: class_y" << std::endl;
    }
};

struct functor_c {
    void operator()(const class_x&)
    {
        std::cout << "functor_c: class_x" << std::endl;
    }

    void operator()(const class_y&)
    {
        std::cout << "functor_c: class_y" << std::endl;
    }

    void operator()(const class_z&)
    {
        std::cout << "functor_c: class_z" << std::endl;
    }
};

struct executor {

    template<typename message_type,
         typename tuple_type>
    static inline void exec(tuple_type &tuple,
                const message_type &message)
    {
        exec_impl(tuple, message,
              std::make_index_sequence<std::tuple_size_v<tuple_type>>{});
    }

    template<typename message_type,
         typename tuple_type,
         std::size_t ...i>
    static void exec_impl(tuple_type &tuple,
                  const message_type &message,
                  std::index_sequence<i...>)
    {
        (exec_invoke_impl(std::get<i>(tuple), message), ...);
    }

    template<typename invocable, typename message_type,
         typename=void>
    struct invoke_impl {
        static inline void invoke(const invocable &i,
                      const message_type &m)
        {
        }
    };

    template<typename message_type, typename functor_type>
    static void exec_invoke_impl(functor_type &f,
                     const message_type &message)
    {
        invoke_impl<functor_type, message_type>::invoke(f, message);
    }
};

template<typename invocable, typename message_type>
struct executor::invoke_impl<
    invocable, message_type,
    std::void_t<decltype(std::declval<invocable &&>()
                 (std::declval<const message_type &>()))>>
{
    static inline void invoke(invocable &i,
                  const message_type &m)
    {
        i(m);
    }
};

int main()
{
    std::tuple<functor_a, functor_b, functor_c> t;

    executor::exec(t, class_x{});
    executor::exec(t, class_y{});
    executor::exec(t, class_z{});

    return 0;
}

结果:

functor_a: class_x
functor_b: class_x
functor_c: class_x
functor_b: class_y
functor_c: class_y
functor_c: class_z

从单个函数对象开始,有条件地调用:

#define FWD(x) static_cast<decltype(x)&&>(x)

template <typename F, typename T>
void maybe_invoke(F&& f, T&& t) {
    if constexpr (std::is_invocable_v<F, T>) {
        std::invoke(FWD(f), FWD(t));
    }
}

现在,我们需要一种方法来为元组中的每种类型做一些事情。我们可以使用 std::apply(获取 tuple 的所有内部结构)然后折叠逗号运算符(在每个内部结构上调用我们的函数):

template <typename Tuple, typename F>
void for_each_tuple(Tuple&& tuple, F&& f) {
    std::apply([&](auto&&... args){
        (f(FWD(args)), ...);
    }, FWD(tuple));
}

然后,将这两个放在一起:

struct executor {
    template<typename message_type, typename... Type>
    static constexpr void exec(std::tuple<Types...>& tup, const message_type& msg) {
        for_each_tuple(tup, [&](auto&& f){
            maybe_invoke(FWD(f), msg);
        });
    }
};

你甚至真的 不需要 maybe_invoke(但无论如何将它作为一个单独的部分可能会很好)。这也很好用:

struct executor {
    template<typename message_type, typename... Type>
    static constexpr void exec(std::tuple<Types...>& tup, const message_type& msg) {
        for_each_tuple(tup, [&](auto& f){
            if constexpr (std::is_invocable_v<decltype(f), decltype(msg)>) {
                std::invoke(f, msg);
            }
        });
    }
};

您可以结合使用 std::apply、一些通用 lambda 和 fold expressions 来创建一个函数

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 是一个完整的例子。