Info
-
Did you know about different ways of iterating over objects?
Example
int main() {
std::variant<int, std::string_view, double> v = 3.14;
std::visit([](const auto& e) { std::cout << e; }, v); // prints 3.14
std::tuple t = {42, "text", 3.14};
std::apply([](const auto&... args) { ((std::cout << args << ' '), ...); }, t); // prints 42 text 3.14
std::array a{1, 2, 3};
std::for_each(std::cbegin(a), std::cend(a), [](const auto& e) { std::cout << e << ' '; }); // prints 1 2 3
}
Puzzle
-
Can you implement a generic
iterate
function which can iterate over any object?- Double points for applying concepts for overloading
auto iterate(auto&& t, auto&& expr) -> void {
// TODO
}
int main() {
using namespace boost::ut;
using std::literals::string_view_literals::operator""sv;
"iterate"_test = [] {
std::stringstream str{};
const auto out = [&str](const auto& value) { str << value << ' '; };
"tuple"_test = [&] {
std::tuple t = {42, "text", 3.14};
iterate(t, out);
expect("42 text 3.14 "sv == str.str());
};
"array"_test = [&] {
std::array a{1, 2, 3};
iterate(a, out);
expect("1 2 3 "sv == str.str());
};
"vector"_test = [&] {
std::vector v{1, 2, 3};
iterate(v, out);
expect("1 2 3 "sv == str.str());
};
"variant"_test = [&] {
std::variant<int, std::string_view, double> v = 42;
iterate(v, out);
expect("42 "sv == str.str());
};
"array of tuples"_test = [&] {
using tuple_t = std::tuple<int, std::string_view, double>;
std::array at{tuple_t{1, "1", 1.}, tuple_t{2, "2", 2.}, tuple_t{3, "3", 3.}};
iterate(at, out);
expect("1 1 1 2 2 2 3 3 3 "sv == str.str());
};
"vector of variants"_test = [&] {
using variant_t = std::variant<int, std::string_view, double>;
std::vector<variant_t> vv = {42, "text", 3.14};
iterate(vv, out);
expect("42 text 3.14 "sv == str.str());
};
};
}
Solutions
namespace detail {
template <class T>
concept gettable = requires (T t) {
std::get<0>(t);
};
template <class T>
concept container = requires (T t) {
t.begin();
t.end();
t.capacity();
};
template <class T>
concept visitable = gettable<T> and requires (T t) {
t.index();
};
template <class T, class TExpr>
auto iterate(T&& t, TExpr&& expr) -> void {
std::invoke(std::forward<TExpr>(expr), std::forward<T>(t));
}
template <container T>
auto iterate(T&& t, auto&& expr) -> void;
template <visitable T, class TExpr>
auto iterate(T&& t, TExpr&& expr) -> void;
template <gettable T>
auto iterate(T&& t, auto&& expr) -> void {
std::apply([&] <class... TElems> (TElems&&... elems) {
(detail::iterate(std::forward<TElems>(elems), expr), ...);
}, std::forward<T>(t));
}
template <container T>
auto iterate(T&& t, auto&& expr) -> void {
for (auto&& elem : std::forward<T>(t)) {
detail::iterate(std::forward<decltype(elem)>(elem), expr);
}
}
template <visitable T, class TExpr>
auto iterate(T&& t, TExpr&& expr) -> void {
std::visit([&] <class TElem> (TElem&& elem) {
detail::iterate(std::forward<TElem>(elem), std::forward<TExpr>(expr));
}, std::forward<T>(t));
}
}
template <class T, class TExpr>
auto iterate(T&& t, TExpr&& expr) -> void {
return detail::iterate(std::forward<T>(t), std::forward<TExpr>(expr));
}
template<typename Vis, typename Var>
concept CanIterateWithVisit = requires(Vis vis, Var var) {
var.index();
var.valueless_by_exception();
std::get<0>(var);
};
template<typename UnaryF, typename C>
concept CanIterateWithForEach = requires(C c, UnaryF unaryF, C::const_iterator citer) {
c.begin();
c.end();
std::for_each(citer, citer, unaryF);
};
template<typename Func, typename T>
concept CanIterateWithApply = requires(T t, Func func) {
std::tuple_size<T>{};
std::get<0>(t);
};
auto iterate(auto&& t, auto&& expr) -> void {
using tType = std::remove_reference_t<decltype(t)>;
using exprType = std::remove_reference_t<decltype(expr)>;
if constexpr( CanIterateWithVisit<decltype(expr), tType>)
std::visit(expr, t);
else if constexpr( CanIterateWithForEach<decltype(expr), tType>)
std::for_each( std::cbegin(t), std::cend(t), [&](const auto& arg) { iterate(arg, expr); } );
else if constexpr( CanIterateWithApply< exprType, tType>)
std::apply([&](const auto& ...args) { (expr(args),...); }, t);
else
expr(t);
}
template <typename T>
concept For_Eachable = requires (const T& t) {
std::for_each(t.cbegin(), t.cend(), [](const auto&) {});
};
template <template <typename...> typename, typename>
constexpr auto IsContainer = false;
template <template <typename...> typename C, typename... Ts>
constexpr auto IsContainer<C, C<Ts...>> = true;
void iterate(const auto& t, const auto& expr) {
using T = decltype(t);
if constexpr ( IsContainer<std::variant, std::decay_t<T>> ) {
std::visit(expr, t);
}
else if constexpr ( IsContainer<std::tuple, std::decay_t<T>> ) {
const auto f_apply = [&](const auto&... es) { (..., expr(es)); };
std::apply(f_apply, t);
}
else if constexpr ( For_Eachable<T> ) {
const auto f_for_each = [&](const auto& e) { iterate(e, expr); };
std::for_each(t.cbegin(), t.cend(), f_for_each);
}
else {
expr(t);
}
}
namespace detail {
template<typename T>
concept gettable = requires(T t) {
std::get<0>(t);
};
template<typename T>
concept indexable = requires(T t) {
t.index();
};
template<typename T>
concept container = requires(T t) {
t.begin();
t.end();
t.size();
t.capacity();
};
template<typename T>
concept apply_iterable = gettable<T>;
template<typename T>
concept for_iterable = container<T>;
template<typename T>
concept visit_iterable = gettable<T> && indexable<T>;
}
template<detail::apply_iterable TIterable, typename TExpr>
auto iterate(TIterable&& t, TExpr&& expr) -> void {
std::apply(
[&](const auto&... args ){
(iterate(args, expr), ...);
},
t);
}
template<detail::for_iterable TIterable, typename TExpr>
auto iterate(TIterable&& t, TExpr&& expr) -> void {
for(const auto& val : t) {
iterate(val, expr);
}
}
template<detail::visit_iterable TIterable, typename TExpr>
auto iterate(TIterable&& t, TExpr&& expr) -> void {
std::visit(
[&](const auto& var){
expr(var);
},
t);
}
template<typename TValue, typename TExpr>
auto iterate(TValue&& t, TExpr&& expr) -> void {
expr(t);
}
auto iterate(auto&& t, auto&& expr) -> void {
expr(t);
}
template<class... Ts>
auto iterate(std::tuple<Ts...> t, auto&& expr) -> void {
std::apply([expr](const auto&... args) { (expr(args), ...); }, t);
}
template<class... Ts>
auto iterate(std::variant<Ts...> v, auto&& expr) -> void {
std::visit(expr, v);
}
template<class T, auto N>
auto iterate(std::array<T, N> a, auto&& expr) -> void {
std::for_each(std::cbegin(a), std::cend(a), [expr](const auto& e){
iterate(e, expr);
});
}
template<class T>
auto iterate(std::vector<T> v, auto&& expr) -> void {
std::for_each(std::cbegin(v), std::cend(v), [expr](const auto& e){
iterate(e, expr);
});
}