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