【发布时间】:2020-12-26 21:53:51
【问题描述】:
在处理包含其他实体的某种组合的通用结构时,我经常最终使用std::tuple,需要对std::tuple 的各个元素应用操作。例如,当我实现"zip iterator" 时,底层范围和迭代器存储在std::tuple 中。虽然使用std::index_sequence<std::tuple_size<Tuple>> 破解自定义函数或类模板以获取相关元素是相当直接的,但似乎使用在std::tuples 或std::tuple 类似结构上运行的算法(例如std::array 或@ 987654330@) 可以改进代码。
有些操作很容易实现。例如,tuple_for_each() 可以调度到递归实现或基于std::index_sequence 的实现以应用元素:
template <typename Tuple, typename Fun>
void tuple_for_each(Tuple&& tuple, Fun fun)
{
auto const impl = [&tuple, fun]<std::size_t...I>(std::index_sequence<I...>){
(fun(std::get<I>(tuple)), ...);
};
impl(std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>());
}
当操作不是像for_each 或transform 中那样真正按元素进行操作时(因为后者产生返回类型更有趣),但将函数应用于元素的结果组合起来产生结果,通用算法似乎并不那么简单。该算法有点像accumulate 或inner_product。这些可以在自定义设置中很容易地编写,例如,
template <typename... T>
struct some_struct {
std::tuple<T...> tuple;
template <typename Tuple, std::size_t... I>
static bool equals(Tuple&& t0, Tuple&& t1, std::index_sequence<I...>) {
return ((std::get<I>(t0) == std::get<I>(t1)) && ...);
}
bool operator== (some_struct const& other) const {
return equals(this->tuple, other.tuple, std::make_index_sequence<sizeof...(T)>());
}
};
如果这个operator==() 可以委托给一个合适的tuple 算法就好了:
bool operator== (some_struct const& other) const {
return tuple_inner_product(this->tuple, other.tuple, true, std::equal_to<>(), std::logical_and<>());
}
除了根据最后一个参数的类型创建特殊版本之外,似乎没有办法使用折叠表达式实现tuple_inner_product。我知道如何实现递归版本,但如果组合操作是逻辑操作数之一,我认为该版本不会使评估短路(在调用函数之前总是需要确定函数参数):
template <typename T0, typename T1, typename Init, typename Transform, typename Combine>
auto tuple_inner_product(T0&& t0, T1&& t1, Init init, Transform transform, Combine combine) {
auto const recurse = [&t0, &t1, transform, combine]<std::size_t I>(
std::integral_constant<std::size_t, I>,
auto const& r, auto init) {
if constexpr (I == std::min(std::tuple_size_v<std::decay_t<T0>>,
std::tuple_size_v<std::decay_t<T1>>)) {
return init;
}
else {
return combine(transform(std::get<I>(t0), std::get<I>(t1)),
r(std::integral_constant<std::size_t, I+1>(), r, init));
}
};
return recurse(std::integral_constant<std::size_t, 0u>(), recurse, init);
}
因此,问题就变成了是否有办法使用折叠表达式来实现这个算法?
【问题讨论】:
-
你见过 std::apply 吗?您的两个示例看起来都很容易用它编写。 (现在无法实际编写和测试它。)
-
你基本上想要this。
-
@HTNW:我认为
std::apply()有点相反:它调用一个函数扩展std::tuple元素成为函数参数。我想对每个元素应用一个函数。例如,如果std::tuple包含不同序列的迭代器,例如tuple_for_each(tuple, [](auto& it){ ++it; })变为++std::get<0>(tuple), ++std::get<1>(tuple), ...。 -
HTNW 评论跟进:
std::apply([](auto&... its){(++its, ...);}, tuple);. -
@DietmarKühl 我们开始吧:
void tuple_for_each(auto &&t, auto f) { apply([&](auto&&... xs) { (f(forward<decltype(xs)>(xs)), ...); }, forward<decltype(t)>(t)); } auto tuple_transform(auto &&t, auto f) { return apply([&](auto&&... xs) { return make_tuple(f(std::forward<decltype(xs)>(xs))...); }, std::forward<decltype(t)>(t)); } bool tuple_eq(auto &&l, auto &&r) { return apply([&](auto&&... ls) { return apply([&](auto&&... rs) { return ((std::forward<decltype(ls)>(ls) == std::forward<decltype(rs)>(rs)) && ...); }, forward<decltype(r)>(r)); }, forward<decltype(l)>(l)); }