为发烧友控制多个可变参数包的解包 tuple_for_each
Controlling the unpacking of multiple variadic parameter packs for a fancier tuple_for_each
Background/Motivation
我一直在研究 VC++2015,寻找一些编写实用例程来处理元组和其他可变参数的方法。
我感兴趣的第一个函数是 common-or-garden tuple_for_all 函数。对于函数 f
和元组 t
依次调用 f(get<0>(t)
、f(get<1>(t)
,依此类推。
到目前为止,非常简单。
template<typename Tuple, typename Function, std::size_t... Indices>
constexpr void tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>) {
using swallow = int[];
static_cast<void>(swallow{ 0, (std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))), void(), 0)... });
}
template<typename Function, typename Tuple>
constexpr void tuple_for_each(Function&& f, Tuple&& t) {
return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}
好的,这行得通(模数任何编译和 copy/paste 在削减它时发生的错误)。
但我的下一个想法是 Function
在某些情况下可能 return 是 useful/interesting 值,因此我们应该捕获它。天真地,我们可以这样做:
template<typename Tuple, typename Function, std::size_t... Indices>
constexpr auto tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>) {
return std::make_tuple(std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t)));
}
template<typename Function, typename Tuple>
constexpr auto tuple_for_each(Function&& f, Tuple&& t) {
return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}
当函数执行 return 一个值时这很好,但是由于 void
令人讨厌地退化并且我们无法创建 std::tuple<void>
,它不适用于 void
-returning 函数。我们不能通过 return 类型直接重载,但 C++ 为我们提供了处理此问题的工具,SFINAE:
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr void tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>) {
using swallow = int[];
static_cast<void>(swallow{ 0, (std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))), void(), 0)... });
}
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<!std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>) {
return std::make_tuple(std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t)))...);
}
template<typename Function, typename Tuple>
constexpr decltype(auto) tuple_for_each(Function&& f, Tuple&& t) {
return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}
这已经够好了。如果两者之间的求值顺序一致就好了(void
版本是从左到右,value-returning 版本取决于编译器,所以可能是从右到-剩下)。我们可以通过避免调用 std::make_tuple
来解决这个问题,而是用括号初始化 std::tuple
来代替。我不知道是否有比 decltype(std::make_tuple(...))
更好的东西来获得正确的类型来构造。可能有。
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr void tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>)
{
using swallow = int[];
static_cast<void>(swallow{ 0, (std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))), void(), 0)... });
}
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<!std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>)
{
return decltype(std::make_tuple(std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t)))...)){std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))) ...};
}
template<typename Tuple, typename Function>
constexpr decltype(auto) tuple_for_each(Function&& f, Tuple&& t)
{
return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}
(顺便说一下,VC++ 2015 现在似乎被窃听了;它 still doesn't use left-to-right evaluation even for the braced initializer because the optimizer team doesn't seem to think it's important)
我对 std::enable_if_t
检查更感兴趣。我们不检查元组中每个类型的函数 return 是否非 void
,只有第一个。但实际上,它应该是全有或全无。 all_true
技术为我们解决了这个问题:
template <bool...> struct bool_pack;
template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<all_true<std::is_void<std::result_of_t<Function(std::tuple_element_t<Indices, std::decay_t<Tuple>>)>>::value...>::value>>
constexpr void tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>)
{
using swallow = int[];
static_cast<void>(swallow{ 0, (std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))), void(), 0)... });
}
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<!std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>)
{
return decltype(std::make_tuple(std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t)))...)){std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))) ...};
}
template<typename Function, typename Tuple>
constexpr decltype(auto) tuple_for_each(Function&& f, Tuple&& t)
{
return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}
问题
但这里有一点棘手。虽然 tuple_for_each
很好用,但我想,如果我给它加点香料呢?接受函数 f
和元组 t0
、t1
等并计算 f(get<0>(t0), get<0>(t1)...)
、f(get<1>(t0), get<1>(t1)...)
等的 tuple_for_each
怎么样?
我们天真地想做这样的事情:
using swallow = int[];
static_cast<void>(swallow{ 0, ((std::forward<Function>(f)(std::get<Indices>(std::forward<Tuples>(ts))...)), void(), 0)... });
天真地,我们希望第一个 ...
扩展 Tuples
,第二个 ...
扩展 Indices
。但是参数包扩展不提供这种控制。如果 ...
之前的表达式包含多个参数包,那么 ...
会尝试并行解包所有参数(VC++;它会发出编译器错误,指出它们的长度不同) ,或者根本找不到参数包(g++;它会发出没有包的编译器错误)。
幸运的是,这种情况可以用一个额外的间接层来处理,以分离出扩展:
template <bool...> struct bool_pack;
template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;
template<size_t N, typename Function, typename... Tuples, typename = std::enable_if_t<std::is_void<std::result_of_t<Function(std::tuple_element_t<N, std::decay_t<Tuples>>...)>>::value>>
constexpr void tuple_for_each_aux(Function&& f, Tuples&&... ts)
{
return std::forward<Function>(f)(std::get<N>(std::forward<Tuples>(ts))...);
}
template<typename Function, typename... Tuples, std::size_t... Indices, typename = std::enable_if_t<all_true<std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuples>>...)>>::value>::value>>
constexpr void tuple_for_each_aux(Function&& f, std::index_sequence<Indices...>, Tuples&&... ts)
{
using swallow = int[];
static_cast<void>(swallow{ 0, (tuple_for_each_aux<Indices>(std::forward<Function>(f), std::forward<Tuples>(ts)...), void(), 0)... });
}
template<std::size_t N, typename Function, typename... Tuples, typename = std::enable_if_t<!std::is_void<std::result_of_t<Function(std::tuple_element_t<N, std::decay_t<Tuples>>...)>>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, Tuples&&... ts)
{
return std::forward<Function>(f)(std::get<N>(std::forward<Tuples>(ts))...);
}
template<typename Function, typename... Tuples, std::size_t... Indices, typename = std::enable_if_t<all_true<!std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuples>>...)>>::value>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, std::index_sequence<Indices...>, Tuples&&... ts)
{
return decltype(std::make_tuple(tuple_for_each_aux<Indices>(std::forward<Function>(f), std::forward<Tuples>(ts)...)...)) { tuple_for_each_aux<Indices>(std::forward<Function>(f), std::forward<Tuples>(ts)...)... };
}
template<typename Function, typename Tuple, typename... Tuples>
constexpr decltype(auto) tuple_for_each(Function&& f, Tuple&& t, Tuples&&... ts)
{
return tuple_for_each_aux(std::forward<Function>(f), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{}, std::forward<Tuple>(t), std::forward<Tuples>(ts)...);
}
效果很好...除了...那些讨厌的 enable_ifs。我不得不削弱它们,回到只测试元组中的第一个元素。现在,这不是一场彻底的灾难,因为最里面的扩展可以执行检查。但这不是很好。考虑以下因素:
struct functor
{
int operator()(int a, int b) { return a + b; }
double operator()(double a, double b) { return a + b; }
void operator()(char, char) { return; }
};
int main()
{
auto t1 = std::make_tuple(1, 2.0, 'a');
auto t2 = std::make_tuple(2, 4.0, 'b');
tuple_for_each(functor{}, t1, t2);
return 0;
}
functor
对象需要强制使用void
路径,因为在第三个元组元素returns void
上计算函数。但是我们的启用检查只查看第一个元素。并且因为故障发生在 SFINAE 驱动的 "overload" 解析之后,SFINAE 无法在这里拯救我们。
但同样地,我们不能双重解包 enable_if_t
表达式,原因与我们在调用函数时不能这样做的原因相同:参数包扩展变得混乱,并试图在同时。
这就是我脱颖而出的地方。我需要一个在道德上与用于调用函数的间接寻址等效的间接寻址,但我无法立即看到如何编写该间接寻址使其真正起作用。
有什么建议吗?
持有者类型:
template<class...> class typelist {};
一个别名模板,用于计算将 F
应用于 Tuples
中每个元组的第 I
个元素的结果:
template<class F, std::size_t I, class...Tuples>
using apply_result_type = decltype(std::declval<F>()(std::get<I>(std::declval<Tuples>())...));
现在计算结果类型列表:
template<class F, std::size_t...Is, class... Tuples>
typelist<apply_result_type<F, Is, Tuples...>...>
compute_result_types(typelist<F, Tuples...>, std::index_sequence<Is...>);
template<class F, std::size_t Size, class... Tuples>
using result_types = decltype(compute_result_types(typelist<F, Tuples...>(),
std::make_index_sequence<Size>()));
并检查类型列表中是否没有 void
:
template<class... Ts>
all_true<!std::is_void<Ts>::value...> do_is_none_void(typelist<Ts...>);
template<class TL>
using has_no_void_in_list = decltype(do_is_none_void(TL()));
最后是实际的 SFINAE(只显示一个):
template<typename Function, typename... Tuples, std::size_t... Indices,
typename = std::enable_if_t<!has_no_void_in_list<result_types<Function,
sizeof...(Indices),
Tuples...>>{}>>
constexpr void tuple_for_each_aux(Function&& f, std::index_sequence<Indices...>,
Tuples&&... ts)
{
using swallow = int[];
static_cast<void>(swallow{ 0, (tuple_for_each_aux<Indices>(std::forward<Function>(f), std::forward<Tuples>(ts)...), void(), 0)... });
}
Demo.
Background/Motivation
我一直在研究 VC++2015,寻找一些编写实用例程来处理元组和其他可变参数的方法。
我感兴趣的第一个函数是 common-or-garden tuple_for_all 函数。对于函数 f
和元组 t
依次调用 f(get<0>(t)
、f(get<1>(t)
,依此类推。
到目前为止,非常简单。
template<typename Tuple, typename Function, std::size_t... Indices>
constexpr void tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>) {
using swallow = int[];
static_cast<void>(swallow{ 0, (std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))), void(), 0)... });
}
template<typename Function, typename Tuple>
constexpr void tuple_for_each(Function&& f, Tuple&& t) {
return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}
好的,这行得通(模数任何编译和 copy/paste 在削减它时发生的错误)。
但我的下一个想法是 Function
在某些情况下可能 return 是 useful/interesting 值,因此我们应该捕获它。天真地,我们可以这样做:
template<typename Tuple, typename Function, std::size_t... Indices>
constexpr auto tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>) {
return std::make_tuple(std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t)));
}
template<typename Function, typename Tuple>
constexpr auto tuple_for_each(Function&& f, Tuple&& t) {
return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}
当函数执行 return 一个值时这很好,但是由于 void
令人讨厌地退化并且我们无法创建 std::tuple<void>
,它不适用于 void
-returning 函数。我们不能通过 return 类型直接重载,但 C++ 为我们提供了处理此问题的工具,SFINAE:
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr void tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>) {
using swallow = int[];
static_cast<void>(swallow{ 0, (std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))), void(), 0)... });
}
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<!std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>) {
return std::make_tuple(std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t)))...);
}
template<typename Function, typename Tuple>
constexpr decltype(auto) tuple_for_each(Function&& f, Tuple&& t) {
return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}
这已经够好了。如果两者之间的求值顺序一致就好了(void
版本是从左到右,value-returning 版本取决于编译器,所以可能是从右到-剩下)。我们可以通过避免调用 std::make_tuple
来解决这个问题,而是用括号初始化 std::tuple
来代替。我不知道是否有比 decltype(std::make_tuple(...))
更好的东西来获得正确的类型来构造。可能有。
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr void tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>)
{
using swallow = int[];
static_cast<void>(swallow{ 0, (std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))), void(), 0)... });
}
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<!std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>)
{
return decltype(std::make_tuple(std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t)))...)){std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))) ...};
}
template<typename Tuple, typename Function>
constexpr decltype(auto) tuple_for_each(Function&& f, Tuple&& t)
{
return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}
(顺便说一下,VC++ 2015 现在似乎被窃听了;它 still doesn't use left-to-right evaluation even for the braced initializer because the optimizer team doesn't seem to think it's important)
我对 std::enable_if_t
检查更感兴趣。我们不检查元组中每个类型的函数 return 是否非 void
,只有第一个。但实际上,它应该是全有或全无。 all_true
技术为我们解决了这个问题:
template <bool...> struct bool_pack;
template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<all_true<std::is_void<std::result_of_t<Function(std::tuple_element_t<Indices, std::decay_t<Tuple>>)>>::value...>::value>>
constexpr void tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>)
{
using swallow = int[];
static_cast<void>(swallow{ 0, (std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))), void(), 0)... });
}
template<typename Function, typename Tuple, std::size_t... Indices, typename = std::enable_if_t<!std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuple>>)>>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, Tuple&& t, std::index_sequence<Indices...>)
{
return decltype(std::make_tuple(std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t)))...)){std::forward<Function>(f)(std::get<Indices>(std::forward<Tuple>(t))) ...};
}
template<typename Function, typename Tuple>
constexpr decltype(auto) tuple_for_each(Function&& f, Tuple&& t)
{
return tuple_for_each_aux(std::forward<Function>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{});
}
问题
但这里有一点棘手。虽然 tuple_for_each
很好用,但我想,如果我给它加点香料呢?接受函数 f
和元组 t0
、t1
等并计算 f(get<0>(t0), get<0>(t1)...)
、f(get<1>(t0), get<1>(t1)...)
等的 tuple_for_each
怎么样?
我们天真地想做这样的事情:
using swallow = int[];
static_cast<void>(swallow{ 0, ((std::forward<Function>(f)(std::get<Indices>(std::forward<Tuples>(ts))...)), void(), 0)... });
天真地,我们希望第一个 ...
扩展 Tuples
,第二个 ...
扩展 Indices
。但是参数包扩展不提供这种控制。如果 ...
之前的表达式包含多个参数包,那么 ...
会尝试并行解包所有参数(VC++;它会发出编译器错误,指出它们的长度不同) ,或者根本找不到参数包(g++;它会发出没有包的编译器错误)。
幸运的是,这种情况可以用一个额外的间接层来处理,以分离出扩展:
template <bool...> struct bool_pack;
template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;
template<size_t N, typename Function, typename... Tuples, typename = std::enable_if_t<std::is_void<std::result_of_t<Function(std::tuple_element_t<N, std::decay_t<Tuples>>...)>>::value>>
constexpr void tuple_for_each_aux(Function&& f, Tuples&&... ts)
{
return std::forward<Function>(f)(std::get<N>(std::forward<Tuples>(ts))...);
}
template<typename Function, typename... Tuples, std::size_t... Indices, typename = std::enable_if_t<all_true<std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuples>>...)>>::value>::value>>
constexpr void tuple_for_each_aux(Function&& f, std::index_sequence<Indices...>, Tuples&&... ts)
{
using swallow = int[];
static_cast<void>(swallow{ 0, (tuple_for_each_aux<Indices>(std::forward<Function>(f), std::forward<Tuples>(ts)...), void(), 0)... });
}
template<std::size_t N, typename Function, typename... Tuples, typename = std::enable_if_t<!std::is_void<std::result_of_t<Function(std::tuple_element_t<N, std::decay_t<Tuples>>...)>>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, Tuples&&... ts)
{
return std::forward<Function>(f)(std::get<N>(std::forward<Tuples>(ts))...);
}
template<typename Function, typename... Tuples, std::size_t... Indices, typename = std::enable_if_t<all_true<!std::is_void<std::result_of_t<Function(std::tuple_element_t<0, std::decay_t<Tuples>>...)>>::value>::value>>
constexpr decltype(auto) tuple_for_each_aux(Function&& f, std::index_sequence<Indices...>, Tuples&&... ts)
{
return decltype(std::make_tuple(tuple_for_each_aux<Indices>(std::forward<Function>(f), std::forward<Tuples>(ts)...)...)) { tuple_for_each_aux<Indices>(std::forward<Function>(f), std::forward<Tuples>(ts)...)... };
}
template<typename Function, typename Tuple, typename... Tuples>
constexpr decltype(auto) tuple_for_each(Function&& f, Tuple&& t, Tuples&&... ts)
{
return tuple_for_each_aux(std::forward<Function>(f), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{}, std::forward<Tuple>(t), std::forward<Tuples>(ts)...);
}
效果很好...除了...那些讨厌的 enable_ifs。我不得不削弱它们,回到只测试元组中的第一个元素。现在,这不是一场彻底的灾难,因为最里面的扩展可以执行检查。但这不是很好。考虑以下因素:
struct functor
{
int operator()(int a, int b) { return a + b; }
double operator()(double a, double b) { return a + b; }
void operator()(char, char) { return; }
};
int main()
{
auto t1 = std::make_tuple(1, 2.0, 'a');
auto t2 = std::make_tuple(2, 4.0, 'b');
tuple_for_each(functor{}, t1, t2);
return 0;
}
functor
对象需要强制使用void
路径,因为在第三个元组元素returns void
上计算函数。但是我们的启用检查只查看第一个元素。并且因为故障发生在 SFINAE 驱动的 "overload" 解析之后,SFINAE 无法在这里拯救我们。
但同样地,我们不能双重解包 enable_if_t
表达式,原因与我们在调用函数时不能这样做的原因相同:参数包扩展变得混乱,并试图在同时。
这就是我脱颖而出的地方。我需要一个在道德上与用于调用函数的间接寻址等效的间接寻址,但我无法立即看到如何编写该间接寻址使其真正起作用。
有什么建议吗?
持有者类型:
template<class...> class typelist {};
一个别名模板,用于计算将 F
应用于 Tuples
中每个元组的第 I
个元素的结果:
template<class F, std::size_t I, class...Tuples>
using apply_result_type = decltype(std::declval<F>()(std::get<I>(std::declval<Tuples>())...));
现在计算结果类型列表:
template<class F, std::size_t...Is, class... Tuples>
typelist<apply_result_type<F, Is, Tuples...>...>
compute_result_types(typelist<F, Tuples...>, std::index_sequence<Is...>);
template<class F, std::size_t Size, class... Tuples>
using result_types = decltype(compute_result_types(typelist<F, Tuples...>(),
std::make_index_sequence<Size>()));
并检查类型列表中是否没有 void
:
template<class... Ts>
all_true<!std::is_void<Ts>::value...> do_is_none_void(typelist<Ts...>);
template<class TL>
using has_no_void_in_list = decltype(do_is_none_void(TL()));
最后是实际的 SFINAE(只显示一个):
template<typename Function, typename... Tuples, std::size_t... Indices,
typename = std::enable_if_t<!has_no_void_in_list<result_types<Function,
sizeof...(Indices),
Tuples...>>{}>>
constexpr void tuple_for_each_aux(Function&& f, std::index_sequence<Indices...>,
Tuples&&... ts)
{
using swallow = int[];
static_cast<void>(swallow{ 0, (tuple_for_each_aux<Indices>(std::forward<Function>(f), std::forward<Tuples>(ts)...), void(), 0)... });
}
Demo.