Search code examples
c++c++20std-rangesc++23

How to chain views for printing elements in C++20 or C++23?


I can apply std::ranges::for_each to a view and print each element line this:

#include <chrono>
#include <iostream>
#include <ranges> // views::transform, views::filter
#include <algorithm> // ranges::for_each
using namespace std; namespace c = std::chrono;
namespace v = std::views; namespace r = std::ranges;

int main() {
    auto show_name = [](const string_view name) { cout << name << ' '; };
    const auto& db = c::get_tzdb();
    
    auto names = db.zones
        | v::transform([](const c::time_zone& z) { return z.name(); })
        | v::filter([](const string_view name) { return name.starts_with("Europe/Be"); });
    r::for_each(names, show_name);
    cout << " <- Europe/Be*\n";
}

But I would rather have something like this:

    auto names = db.zones
        | v::transform([](const c::time_zone& z) { return z.name(); })
        | v::filter([](const string_view name) { return name.starts_with("Europe/Be"); });
        | v::for_each(show_name);
    cout << " <- Europe/Be*\n";

But I believe there is no std::views::for_each(), right?

https://en.cppreference.com/w/cpp/ranges

I tried with views::transform([](n) { cout << n; return 0; } but because of "lazy", that does nothing.

Also, I tried something like

   cout << (db.zones
        | v::transform([](const c::time_zone& z) { return z.name(); })
        | v::filter([](const string_view name) { return name.starts_with("Europe/Be"); });
        | v::join) << "\n";

But I believe that is not how join works.


Solution

  • There is no such utility in the standard.

    However, thanks to pipe support for user-defined range adaptors , you can easily create one through ranges::range_adaptor_closure in C++23:

    #include <ranges>
    #include <algorithm> // for ranges::for_each
    
    namespace pipe {
      namespace detail {
      template<class F>
        class for_each_closure : public std::ranges::range_adaptor_closure<for_each_closure<F>> {
          F fun_;
        public:
          for_each_closure(F fun) : fun_(std::move(fun)) { }
          template<std::ranges::input_range R>
          constexpr auto operator()(R&& r) const {
            return std::ranges::for_each(std::forward<R>(r), fun_);
          }
        };
      }
      template<class F>
      constexpr auto for_each(F fun) {
        return detail::for_each_closure<F>(std::move(fun));
      }
    };
    

    Then you can use it like this

    auto show_name = [](const string_view name) { cout << name << ' '; };
    const auto& db = c::get_tzdb();
    
    auto result = db.zones
        | v::transform([](const c::time_zone& z) { return z.name(); })
        | v::filter([](const string_view name) { return name.starts_with("Europe/Be"); })
        | pipe::for_each(show_name);
    

    Demo