SamG101
SamG101

Reputation: 481

C++ how to create a live filter / transformed vector relative to another vector

I'd like to be able to create a vector that is a live filter or transform of another vector. For example, given some function link_filter_to and link_transform_to that defines this behaviour,

std::vector<int> a;
std::vector<int> b;
std::vector<int> c;

a.link_transform_to(b, [](int x) {return x * 2;}); // doubled numbers
a.link_filter_to   (c, [](int x) {return x % 2;}); // only odd numbers

a.push_back(1);
a.push_back(2);

expected results:

So far the only way I can think of doing this is to wrap the whole std::vector object in a new class, reimplement each function as a wrapper that calls methods / macros to update the linked vectors, and then performs the standard behaviour of the class. For example

template <typename _Tx>
class my_vector
{
    using _Filter_function_callback = std::function<bool()>;
    template <typename _Vt> using _Transform_callback_t = std::function<_Vt(_Tx)>;

public:
    template <typename _Ty>
    auto link_transform_to(_Transform_callback_t<_Ty>&& _Pred) -> std::shared_pointer<my_vector<_Ty>>;
    auto link_filter_to(_Filter_function_callback&& _Pred) -> std::shared_pointer<my_vector<_Tx>>;

    template <typename ..._Valty>
    auto push_back(_Valty&&... _Val) -> my_vector&;
    // all other methods defined here ie pop_back(), reserve(...), etc...

protected:
    std::set<std::shared_pointer<my_vector<std::any>>> _Linked_transformed_containers;
    std::set<std::shared_pointer<my_vector<_Tx     >>> _Linked_filtered_containers;

private:
    template <typename _Tx1>
    struct live_transform{std::shared_pointer<my_vector<_Tx1>> transformed_vector; _Transform_callback_t<_Tx1> callback;};
    struct live_filter{std::shared_pointer<my_vector<_Tx>> filtered_vector; _Filter_function_callback callback;};

    std::vector<_Tx> m_internal;
};

template <typename _Tx>
template <typename ..._Valty>
my_vector::push_back(_Valty&&... _Val)
{
    MUTATE_LIVE_LINKS_VERIFY_AND_ADD(push_back, std::forward<_Valty>(_Val)...)
    (m_internal.push_back(std::forward<_Valty>(_Val)), ...);
    return this;
}
#define MUTATE_LIVE_LINKS(_Pred_name, ...)                                   \
    for (const auto& _Live_filter: _Linked_filtered_containers)              \
        _Live_filter->filtered_vector->_Pred_name(__VA_ARGS__);              \
    for (const auto& _Live_transform: _Linked_transformed_containers)        \
        _Live_transform->transformed_vector->_Pred_name(__VA_ARGS__);

#define MUTATE_LIVE_LINKS_VERIFY_AND_ADD(_Pred_name, ...)                    \
    for (const auto& _Live_filter: _Linked_filtered_containers)              \
    {                                                                        \
        if (_Live_filter.callback(__VA_ARGS__))                              \
            _Live_filter->filtered_vector->_Pred_name(__VA_ARGS__);          \
    }                                                                        \
    for (const auto& _Live_transform: _Linked_transformed_containers)        \
        _Live_transform->transformed_vector->_Pred_name(__VA_ARGS__);

#define MUTATE_LIVE_LINKS_VERIFY(_Pred_name, ...)                            \
    for (const auto& _Live_filter: _Linked_filtered_containers)              \
    {                                                                        \
        if (_Live_filter.callback(__VA_ARGS__))                              \
            _Live_filter->filtered_vector->_Pred_name();                     \
    }                                                                        \
    for (const auto& _Live_transform: _Linked_transformed_containers)        \
        _Live_transform->transformed_vector->_Pred_name(__VA_ARGS__);

These macros would be called by each wrapper method ie push_back(...), pop_back(...), erase(...), etc..., by the wrapper class.

This seems to be a very long and tedious way of achieving this, as every method has to be reimplemented, and it isn't flexible for if I wanted to use a deque for the same thing, because there are different method that would have to be implemented. The transforms don't work yet either, or rather they have only been conceptualized, as I don't think that this is the optimal route to take.

What I would like to achieve is a simple way to create live filter and transform vectors that are bound to another vector, without having to wrap each method of a vector in a wrapper-class.

Upvotes: 1

Views: 114

Answers (1)

SamG101
SamG101

Reputation: 481

Thanks at @Taekahn, looked into views and they're a simple solution:

#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    auto int_vector     = std::vector<int>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    auto even_numbers   = int_vector | std::views::filter([](int i) {return i % 2 == 0;});
    auto double_numbers = int_vector | std::views::transform([](int i) {return i * 10;});

    int_vector.push_back(10);

    for (auto i : even_numbers)
        std::cout << i << ", ";
    std::cout << std::endl;

    for (auto i : double_numbers)
        std::cout << i << ", ";
    std::cout << std::endl;

    return 0;
}

results in

0, 2, 4, 6, 8, 10,
0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100,

Upvotes: 1

Related Questions