Написание собственного фильтра для диапазона

Пытаюсь разобраться, как работать с диапазонами в С++20 по книге Rainer Grimm "C++20 Get the details".

Хочу написать собственный фильтр, которому бы можно было передать контейнер (пусть просто вектор) и который бы отфильтрованные элементы выводил бы в него и передавал дальше. Чтоб пожно было написать что-то вроде

vect | views::filter(f1) | views::transform(f2) | copy_to(w) | views::filter(f3)

примерно так. Т.е. после фильтрации и преобразования скидываем в вектор w и фильтруем себе дальше.

Только вот совершенно запутался, как сделать такую вещь. Если я правильно понимаю, то должны быть только begin() и end() для диапазона. Так? А как получить очередной элемент и записать его?

Если где-то есть более ПРОСТОЕ описание написания фильтров (да и самих диапазонов... никак не могу их понять, как оно там внутри) - буду рад услышать.

Свой позор привожу исключительно для того, чтоб вопрос не закрыли из-за отсутствия собственных попыток. Потому что это в стиле "лишь бы компилировалось" из-за полного непонимания, как это должно работать...

using namespace std::ranges;

template<typename Container, std::ranges::input_range Range> requires std::ranges::view<Range>
class copy_to : public std::ranges::view_interface<copy_to<Container,Range>>
{
private:
    Container * c = nullptr;
    Range range_{};
    std::ranges::iterator_t<Range> begin_{ std::begin(range_) };
    std::ranges::iterator_t<Range> end_{ std::end(range_) };

public:
    //Container() = default;

    constexpr copy_to(Container& c, Range r): c(&c), range_(std::move(r)),
    begin_(std::begin(r)), end_(std::end(r))
    {
        for(auto b = begin_; b != end_; ++b) c->push_back(*b);
    }

    constexpr copy_to(Container& c): c(&c), begin_(std::begin(c)), end_(std::end(c))
    {
        for(auto b = begin_; b != end_; ++b) c->push_back(*b);
    }

    constexpr auto begin() const {
        return begin_;
    }
    constexpr auto end() const {
        return end_;
    }
};

template<typename Container, typename Range>
copy_to(Container&, Range&&) -> copy_to<Container, std::ranges::views::all_t<Range>>;

Ответы (1 шт):

Автор решения: Chorkov

Если вы пишите свою операцию для работы с диапазонами, то вы имеете дело с тремя типами: (1) - результат выполнения copy_to(w) - это может быть собственно тип copy_to. Этот тип не обязан выглядеть как диапазон или контейнер. (2) - результат выполнения ...|copy_to(w) - это уже другой тип. И он, как раз, обязан быть контейнером или диапазоном. (3) - результат выполнения ...|copy_to(w)|... - это уже забота операнда справа, если только мы не переопределим оператор | (справо), например, для оптимизации.

Шаблон std::ranges::view_interface предназначен для определения собственных диапазонов, т.е. он может подойти для типа (2), но ненужен для (1). Вы попытались соединить в одном типы (1) и (2).

Пример решения:

#include <ranges>
#include <vector>
#include <iostream>
using namespace std::ranges;


template<typename Container>
class copy_to
{
    Container& output;
public:
    copy_to(Container& output) : output(output) {}
    copy_to(const copy_to&) = default;
    copy_to(copy_to&&) = default;
    
    
    template<viewable_range Range>
    friend auto operator | ( Range range, const copy_to& output )
    {
        auto& w = output.output;
        for( auto i : range )
            w.push_back( i );
        return range;
    }
};
template<typename Container>
copy_to(Container&) -> copy_to<Container>;


int main()
{
    auto f1=[](int i){ return i%3!=0; };
    auto f2=[](int i){ return i+2; };
    auto f3=[](int i){ return i*3; };
    
    std::vector<int> vect = {1,2,3};
    std::vector<int> w;
    for( int i :  vect | views::filter(f1) | views::transform(f2) | copy_to(w) | views::transform(f3) )
        std::cout<<i<<" ";
    std::cout<<std::endl;
    for( int i : w )
        std::cout<<i<<" ";
    std::cout<<std::endl;
}
→ Ссылка