Reputation: 481
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:
b: 2, 4
c: 1
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__);
MUTATE_LIVE_LINKS
: methods like clear()
, where something has to apply to the linked vector, but the filter/transform function doesn't need to be applied.MUTATE_LIVE_LINKS_VERIFY_AND_ADD
: methods like push_back(...)
where the element has to be checked against the filter/transform method, and then be mutated into the vector.MUTATE_LIVE_LINKS_VERIFY
: methods like pop_back()
, where no argument is called, but the element has to conform to the filter to be popped.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
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