I started writing some of the standard algorithms for tuples, here are the first few non-modifying ones
#include <functional>
#include <iostream>
#include <optional>
#include <tuple>
#include <type_traits>
template <typename Predicate, typename Tuple>
constexpr bool all_of(Predicate&& pred, Tuple&& t) noexcept {
return std::apply(
[&](auto&&... xs) constexpr noexcept {
return (... && pred(std::forward<decltype(xs)>(xs)));
},
std::forward<decltype(t)>(t));
}
template <typename Predicate, typename Tuple>
constexpr bool any_of(Predicate&& pred, Tuple&& t) noexcept {
return std::apply(
[&](auto&&... xs) constexpr noexcept {
return (... || pred(std::forward<decltype(xs)>(xs)));
},
std::forward<decltype(t)>(t));
}
template <typename Predicate, typename Tuple>
constexpr bool none_of(Predicate&& pred, Tuple&& t) noexcept {
return std::apply(
[&](auto&&... xs) constexpr noexcept {
return !(... || pred(std::forward<decltype(xs)>(xs)));
},
std::forward<decltype(t)>(t));
}
template <typename Predicate, typename Tuple>
constexpr void for_each(Predicate&& f, Tuple&& t) noexcept {
return std::apply(
[&](auto&&... xs) constexpr noexcept {
(..., f(std::forward<decltype(xs)>(xs)));
},
std::forward<decltype(t)>(t));
}
template <typename Predicate, typename Tuple, std::size_t... Is>
constexpr void for_each_n_impl(Predicate&& f, Tuple&& t,
std::index_sequence<Is...>) noexcept {
return (..., f(std::get<Is>(t)));
}
template <std::size_t N, typename Predicate, typename Tuple>
constexpr void for_each_n(Predicate&& f, Tuple&& t) noexcept {
static_assert(N <= std::tuple_size_v<std::decay_t<decltype(t)>>);
return for_each_n_impl(std::forward<decltype(f)>(f),
std::forward<decltype(t)>(t),
std::make_index_sequence<N>());
}
template <typename Tuple, typename T>
constexpr std::size_t count(Tuple&& t, const T& value) noexcept {
return std::apply(
[&](auto&&... xs) constexpr noexcept {
return (0u + ... + static_cast<std::size_t>(value == xs));
},
std::forward<decltype(t)>(t));
}
template <typename Predicate, typename Tuple>
constexpr std::size_t count_if(Predicate&& pred, Tuple&& t) noexcept {
return std::apply(
[&](auto&&... xs) constexpr noexcept {
return (0u + ... +
static_cast<std::size_t>(pred(std::forward<decltype(xs)>(xs))));
},
std::forward<decltype(t)>(t));
}
template <std::size_t N, typename TupleZero, typename TupleOne>
constexpr std::optional<std::size_t> mismatch_impl(TupleZero&& t0,
TupleOne&& t1) noexcept {
constexpr std::size_t I = std::tuple_size_v<std::decay_t<decltype(t0)>> - N;
if constexpr (N == 0u) {
return std::nullopt;
} else {
return std::get<I>(t0) == std::get<I>(t1)
? mismatch_impl<N - 1u>(std::forward<decltype(t0)>(t0),
std::forward<decltype(t1)>(t1))
: std::make_optional(I);
}
}
template <std::size_t N, typename Predicate, typename TupleZero,
typename TupleOne>
constexpr std::optional<std::size_t> mismatch_impl(Predicate&& pred,
TupleZero&& t0,
TupleOne&& t1) noexcept {
constexpr std::size_t I = std::tuple_size_v<std::decay_t<decltype(t0)>> - N;
if constexpr (N == 0u) {
return std::nullopt;
} else {
return pred(std::get<I>(t0), std::get<I>(t1))
? mismatch_impl<N - 1u>(std::forward<decltype(pred)>(pred),
std::forward<decltype(t0)>(t0),
std::forward<decltype(t1)>(t1))
: std::make_optional(I);
}
}
template <typename TupleZero, typename TupleOne>
constexpr std::optional<std::size_t> mismatch(TupleZero&& t0,
TupleOne&& t1) noexcept {
static_assert(std::tuple_size_v<std::decay_t<decltype(t0)>> <=
std::tuple_size_v<std::decay_t<decltype(t1)>>);
return mismatch_impl<std::tuple_size_v<std::decay_t<decltype(t0)>>>(
std::forward<decltype(t0)>(t0), std::forward<decltype(t1)>(t1));
}
template <typename Predicate, typename TupleZero, typename TupleOne>
constexpr std::optional<std::size_t> mismatch(Predicate&& pred, TupleZero&& t0,
TupleOne&& t1) noexcept {
static_assert(std::tuple_size_v<std::decay_t<decltype(t0)>> <=
std::tuple_size_v<std::decay_t<decltype(t1)>>);
return mismatch_impl<std::tuple_size_v<std::decay_t<decltype(t0)>>>(
std::forward<decltype(pred)>(pred), std::forward<decltype(t0)>(t0),
std::forward<decltype(t1)>(t1));
}
template <std::size_t N, typename Tuple, typename T>
constexpr std::optional<std::size_t> find_impl(Tuple&& t,
const T& value) noexcept {
constexpr std::size_t I = std::tuple_size_v<std::decay_t<decltype(t)>> - N;
if constexpr (N == 0u) {
return std::nullopt;
} else {
return std::get<I>(t) == value
? std::make_optional(I)
: find_impl<N - 1u>(std::forward<decltype(t)>(t), value);
}
}
template <typename Tuple, typename T>
constexpr std::optional<std::size_t> find(Tuple&& t, const T& value) noexcept {
return find_impl<std::tuple_size_v<std::decay_t<decltype(t)>>>(
std::forward<decltype(t)>(t), value);
}
template <std::size_t N, typename Predicate, typename Tuple>
constexpr std::optional<std::size_t> find_if_impl(Predicate&& pred,
Tuple&& t) noexcept {
constexpr std::size_t I = std::tuple_size_v<std::decay_t<decltype(t)>> - N;
if constexpr (N == 0u) {
return std::nullopt;
} else {
return pred(std::get<I>(t))
? std::make_optional(I)
: find_if_impl<N - 1u>(std::forward<decltype(pred)>(pred),
std::forward<decltype(t)>(t));
}
}
template <typename Predicate, typename Tuple>
constexpr std::optional<std::size_t> find_if(Predicate&& pred,
Tuple&& t) noexcept {
return find_if_impl<std::tuple_size_v<std::decay_t<decltype(t)>>>(
std::forward<decltype(pred)>(pred), std::forward<decltype(t)>(t));
}
template <std::size_t N, typename Predicate, typename Tuple>
constexpr std::optional<std::size_t> find_if_not_impl(Predicate&& pred,
Tuple&& t) noexcept {
constexpr std::size_t I = std::tuple_size_v<std::decay_t<decltype(t)>> - N;
if constexpr (N == 0u) {
return std::nullopt;
} else {
return pred(std::get<I>(t))
? find_if_not_impl<N - 1u>(std::forward<decltype(pred)>(pred),
std::forward<decltype(t)>(t))
: std::make_optional(I);
}
}
template <typename Predicate, typename Tuple>
constexpr std::optional<std::size_t> find_if_not(Predicate&& pred,
Tuple&& t) noexcept {
return find_if_not_impl<std::tuple_size_v<std::decay_t<decltype(t)>>>(
std::forward<decltype(pred)>(pred), std::forward<decltype(t)>(t));
}
Some tests:
auto print = [](auto x) { std::cout << x << '\n'; };
constexpr auto id = [](auto x) constexpr noexcept { return x; };
int main() {
static_assert(all_of(id, std::make_tuple(true, true, true)), "assert 0");
static_assert(any_of(id, std::make_tuple(false, false, true)), "assert 1");
static_assert(none_of(id, std::make_tuple(false, false, false)), "assert 2");
for_each(print, std::make_tuple(1, 2, 3));
for_each_n<2u>(print, std::make_tuple(1, 2, 3));
static_assert(count(std::make_tuple(true, true, true), true) == 3u,
"assert 3");
static_assert(count_if(id, std::make_tuple(false, false, false)) == 0u,
"assert 4");
static_assert(
mismatch(std::make_tuple(1, 2, 3), std::make_tuple(1, 3, 3)).value() ==
1u,
"assert 5");
static_assert(mismatch(std::equal_to<int>{}, std::make_tuple(1, 2, 3),
std::make_tuple(1, 2, 4))
.value() == 2u,
"assert 6");
static_assert(find(std::make_tuple(1, 2, 3), 3).value() == 2u, "assert 7");
static_assert(find_if([](auto x) constexpr noexcept { return x == 2; },
std::make_tuple(1, 2, 3))
.value() == 1u,
"assert 8");
static_assert(find_if_not([](auto x) constexpr noexcept { return x != 1; },
std::make_tuple(1, 2, 3))
.value() == 0u,
"assert 9");
}
Instead of iterators to the elements in a sequence, optional index values are returned based on whether or not the tuple elements satisfied the criteria. I'd like some feedback on the implementation, in particular I was wondering if there is a way around that ugly "impl"-pattern (namespaces are one option). I am also not checking whether or not tuple element types posses different operators, e.g. equality, which can lead to some nasty errors.