When dealing with generic structures to hold some combination of other entities I often end up using std::tuple
with the need to apply operations to individual elements of the std::tuple
. For example, when I implemented a "zip iterator" the underlying ranges and iterators were stored in a std::tuple
. While it is reasonably straight forward to hack up custom function or class templates using an std::index_sequence<std::tuple_size<Tuple>>
to get the relevant elements, it seems using algorithms operating on std::tuple
s or std::tuple
-like structures (e.g., std::array
or std::pair
) could improve the code.
Some operations are fairly straight forward to implement. For example, tuple_for_each()
could either dispatch to a recursive implementation or an implementation based on std::index_sequence
to apply the elements:
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>>>());
}
When the operation isn't really element-wise as in for_each
or transform
(for the latter producing the return type is a bit more interesting) but the result of a function applied to the elements is combined to produce a result a generic algorithm doesn't seem to work as simple. The algorithm would be somewhat like accumulate
or inner_product
. These can be written quite easy in a custom setting, e.g.,
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)>());
}
};
It would be nice if this operator==()
could just delegate to a suitable tuple
algorithm:
bool operator== (some_struct const& other) const {
return tuple_inner_product(this->tuple, other.tuple, true, std::equal_to<>(), std::logical_and<>());
}
There doesn't seem to be a way to implement tuple_inner_product
with a fold expression other than creating special versions based on the type of the last argument. I know how to implement a recursive version but I don't think that version would short-circuit the evaluation if the combining operation is one of the logical operands (the function arguments always need to be determined before the function can be called):
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);
}
Thus, the question becomes whether there is a way to implement this algorithm using fold expressions?