Using Boost.Range in C++ interfaces - c++

I have a polymorphic interface
struct Interface {
Interface(SomeType& other)
: range([=](){ return other.my_range(); }), /*...*/ {}
Interface(SomeOtherType& other)
: range([=](){ return other.some_range(); }), /*...*/ {}
const std::function<Range(void)> range;
/// ...
};
The elements in both ranges are of the same type (e.g. int), but the types returned by my_range() and by some_range() are different, e.g. one can be a filtered counting range and the other a transformed filtered counting range. For the interface I need a single Range type.
I've tried using boost::any_range but the performance is significantly worse. I would like to avoid having to copy the range elements into a vector and returning the vector instead.
Are there any alternatives to any_range and copying?

Kind of, but not really.
You want to access data sequentially when you don't know how it's stored. You have three options:
Copy the data into a container with known format (the "return vector" option).
Use compile-time polymorphism to choose the correct access method (the way std algorithms do it, not possible due to you using an interface).
Use runtime polymorphism to choose the correct access method.
So the second is not possible due to the constraint that you want to use an interface. The first and the third both come with overhead.
The obvious way of doing the third thing is any_range. But it's not the only way, depending on what you want to do. The problem with any_range is that in a simple for-each loop, there are three virtual calls for every element: the increment, the comparison, and the dereference.
As long as all you want to do is simple for-each iteration, you could reduce the overhead to one virtual call by implementing the loop on the interface level:
struct Interface {
Interface(SomeType& other)
: traverse([=](std::function<void(int)> body) {
for (int i : other.my_range()) body(i);
}) {}
const std::function<void (std::function<void(int)>)> traverse;
};
Of course that only works as long as the ways you use the range are very limited.

If there are only known 2 known types (or fixed number of types), then alternative could be Boost.Variant. Here is sample usage:
#include <boost/variant.hpp>
#include <functional>
struct SomeType
{
typedef int range_t;
range_t my_range() const { return 1; }
};
struct SomeOtherType
{
typedef double range_t;
range_t some_range() const { return 3.14; }
};
typedef std::function<SomeType::range_t (void)> SomeTypeRange;
typedef std::function<SomeOtherType::range_t (void)> SomeOtherTypeRange;
typedef boost::variant<SomeTypeRange, SomeOtherTypeRange> Range;
struct Interface
{
Interface(const SomeType& other)
: range( SomeTypeRange([=](){ return other.my_range(); }) ) {}
Interface(const SomeOtherType& other)
: range( SomeOtherTypeRange([=](){ return other.some_range(); }) ) {}
Range range;
};
struct print_me_visitor : public boost::static_visitor<void>
{
public:
void operator()( const SomeTypeRange& i_st ) const
{
std::cout << "SomeTypeRange: " << i_st() << std::endl;
}
void operator()( const SomeOtherTypeRange& i_sot ) const
{
std::cout << "SomeOtherTypeRange: " << i_sot() << std::endl;
}
};
int main()
{
SomeType st;
SomeOtherType sot;
Interface i1( st );
Interface i2( sot );
boost::apply_visitor( print_me_visitor(), i1.range );
boost::apply_visitor( print_me_visitor(), i2.range );
return 0;
}

Related

How to do unordered containers of inherited objects in C++

The problem in question is related to the question and discussion found in this SO thread.
The problem is essentially as follows, I have an abstract class called players. Then I have two classes attackers and defenders. Now, I would like to have an unordered container (map, set, etc.) containing all players. For that I need a hash function (which is not the issue as both attackers and defenders have names) and an equality function. The latter is the problem. As discussed in the linked SO thread, it seems to be bad practice to inherit operator== and I can see why.
I now wonder what the idiomatic solution to my problem is. Is it to just have two containers? One for players and one for attackers? Are there other solutions?
Edit:
Yes, I am talking about unordered_* containers. I am aware that I would need to store pointers in the containers rather than objects themselves. For example, I'd have a container std::unordered_set<std::shared_ptr<players>> all_players.
When you instantiate your map, you can specify a comparator:
template< class InputIt >
map( InputIt first, InputIt last,
const Compare& comp = Compare(), /// << this here
const Allocator& alloc = Allocator() );
Reference: https://en.cppreference.com/w/cpp/container/map/map
Then, you don't need to define the equality operator. In any case, in order to store polymorphic objects in a map, you'll need to store pointers.
So, you would need a comparator anyway, in order to compare the objects, not the pointer values.
In order to store multiple different types in the same container, you have to take advantage of polymorphism.
Polymorphism requires you to use pointers to objects instead of actual objects. That's because C/C++ doesn't allow you to make arrays of multiple different types. The best way to do that in modern C++ is to use std::unique_ptr or std::shared_ptr from #include <memory>
To make a polymorphic container you'll need the following:
A common base struct/class that all of your types inherit from.
Some way to interface with subtype-specific methods/members.
This can be a virtual method in the base object, a member of the base object, or you can also use typeid to figure out what type something is at runtime.
#include <iostream>
#include <vector>
#include <memory>
#include <typeinfo>
struct base {
virtual ~base() = default;
virtual int GetValue() const = 0;
};
struct A : base {
int a;
A(const int v) : a{ v } {}
int GetValue() const override { return a; }
void doSomething() const { std::cout << "Hello World!"; }
};
struct B : base {
int b;
int mult;
B(const int v, const int mult) : b{ v }, mult{ mult } {}
int GetValue() const override { return b * mult; }
void doSomethingElse() const { std::cout << "!dlroW olleH"; }
};
int main()
{
std::vector<std::unique_ptr<base>> vec;
vec.emplace_back(std::make_unique<A>(5)); //< insert an object of type A
vec.emplace_back(std::make_unique<B>(8, 2)); //< insert an object of type B
for (const auto& it : vec) {
std::cout << it->GetValue() << '\t';
if (typeid(*it.get()) == typeid(A)) {
((A*)it.get())->doSomething();
}
else if (typeid(*it.get()) == typeid(B)) {
((B*)it.get())->doSomethingElse();
}
std::cout << std::endl;
}
}
Outputs:
5 Hello World!
16 !dlroW olleH
You need a hash function-object that looks through your pointers. The base class can have non-virtual implementations, or equivalently you can have the hasher inspect the public members.
class players {
public:
size_t hash() const;
friend bool operator==(const player & lhs, const player & rhs);
};
using std::unique_ptr<players> players_ptr;
struct players_hash {
size_t operator()(const players_ptr & ptr) { return ptr->hash(); }
};
struct players_equal {
bool operator()(const players_ptr & lhs, const players_ptr & rhs) { return *lhs == *rhs; }
};
std::unordered_set<players_ptr, players_hash, players_equal> all_players;

unordered_set of shared_ptr does not find equivalent objects it has stored

I have a class that stores a std::vector of stuff. In my program, I create a std::unordered_set of std::shared_ptr to objects of this class (see code below). I defined custom functions to compute hashes and equality so that the unordered_set "works" with the objects instead of the pointers. This means: Two different pointers to different objects that have the same content should be treated as equal, let's call it "equivalent".
So far everything worked as expected but now I stumbled across a strange behaviour: I add a pointer to an object to the unordered_set and create a different pointer to a different object with the same content. As said I would expect that my_set.find(different_object) would return a valid iterator to the equivalent pointer stored in the set. But it doesn't.
Here is a minimal working code example.
#include <boost/functional/hash.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <unordered_set>
#include <vector>
class Foo {
public:
Foo() {}
bool operator==(Foo const & rhs) const {
return bar == rhs.bar;
}
std::vector<int> bar;
};
struct FooHash {
size_t operator()(std::shared_ptr<Foo> const & foo) const {
size_t seed = 0;
for (size_t i = 0; i < foo->bar.size(); ++i) {
boost::hash_combine(seed, foo->bar[i]);
}
return seed;
}
};
struct FooEq {
bool operator()(std::shared_ptr<Foo> const & rhs,
std::shared_ptr<Foo> const & lhs) const {
return *lhs == *rhs;
}
};
int main() {
std::unordered_set<std::shared_ptr<Foo>, FooHash, FooEq> fooSet;
auto empl = fooSet.emplace(std::make_shared<Foo>());
(*(empl.first))->bar.emplace_back(0);
auto baz = std::make_shared<Foo>();
baz->bar.emplace_back(0);
auto eqFun = fooSet.key_eq();
auto hashFun = fooSet.hash_function();
if (**fooSet.begin() == *baz) {
std::cout << "Objects equal" << std::endl;
}
if (eqFun(*fooSet.begin(), baz)) {
std::cout << "Keys equal" << std::endl;
}
if (hashFun(*fooSet.begin()) == hashFun(baz)) {
std::cout << "Hashes equal" << std::endl;
}
if (fooSet.find(baz) != fooSet.end()) {
std::cout << "Baz in fooSet" << std::endl;
} else {
std::cout << "Baz not in fooSet" << std::endl;
}
return 0;
}
Output
Objects equal
Keys equal
Hashes equal
And here is the problem:
Baz not in fooSet
What am I missing here? Why does the set not find the equivalent object?
Possibly of interest: I played around with this and found that if my class stores a plain int instead of a std::vector, it works. If I stick to the std::vector but change my constructor to
Foo(int i) : bar{i} {}
and initialize my objects with
std::make_shared<Foo>(0);
it also works. If I remove the whole pointer stuff, It breaks as std::unordered_set::find returns constant iterators and thus modification of objects in the set cannot be done (this way). However, none of these changes is applicable in my real program, anyway.
I compile with g++ version 7.3.0 using -std=c++17
You can't modify an element of a set (and expect the set to work). Because you have provided FooHash and FooEq which inspect the referent's value, that makes the referent part of the value from the point of view of the set!
If we change the initialisation of fooSet to set up the element before inserting it, we get the result you want/expect:
std::unordered_set<std::shared_ptr<Foo>, FooHash, FooEq> fooSet;
auto e = std::make_shared<Foo>();
e->bar.emplace_back(0); // modification is _before_
fooSet.insert(e); // insertion
Looking up the object in the set depends on the hash value not changing. If we really need to modify a member after it has been added, we need to remove it, make the changes, then add the modified object - see Yakk's answer.
To avoid running into issues like this, it may be safer to use std::shared_ptr<const Foo> as elements, which will prevent modification of the pointed-at Foo through the set (although you're still responsible for the use of any non-const pointers you may also have).
Any operation such that the hash or == result of an element in an unordered_set violates the rules of unordered_set is bad; the result is undefined behavior.
You changed the result of a hash of an element in an unordered_set, because your elements are shared pointers, but their hash and == is based off of the value pointed to. And your code changes the value pointed to.
Make all std::shared_ptr<Foo> in your code std::shared_ptr<Foo const>.
This includes the equals and hash code and unordered set code.
auto empl = fooSet.emplace(std::make_shared<Foo>());
(*(empl.first))->bar.emplace_back(0);
this code is right out, and it will (afterwards) fail to compile, as is safe.
If you want to mutate an element in a fooSet,
template<class C, class It, class F>
void mutate(C& c, It it, F&& f) {
auto e = *it->first;
f(e); // do this before erasing, more exception-safe
auto new_elem = std::make_shared<decltype(e)>(std::move(e));
c.erase(it);
c.insert( new_elem ); // could throw, but hard to avoid.
}
now the code reads:
auto empl = fooSet.emplace(std::make_shared<Foo>());
mutate(fooSet, empl.first, [&](auto&& elem) {
elem.emplace_back(0);
});
mutate copies an element out, removes the pointer from the set, calls the function on it, then reinserts it back into the fooSet.
Of course in this case it is dumb; we just put it in and now we take it out mutate it and put it back.
But in a more general case it will be less dumb.
Here you add an object and it's stored using its current hash value.
auto empl = fooSet.emplace(std::make_shared<Foo>());
Here you change the hash value:
(*(empl.first))->bar.emplace_back(0);
The set now has an object stored using the old/wrong hash value. If you need to change anything in an object that affects its hash value, you need to extract the object, change it and re-insert it. If all mutable members of the class are used to calculate the hash value, make it a set of <const Foo> instead.
To make future declarations of sets of shared_ptr<const Foo> easier, you may also extend the std namespace with your specializations.
class Foo {
public:
Foo() {}
size_t hash() const {
size_t seed = 0;
for (auto& b : bar) {
boost::hash_combine(seed, b);
}
return seed;
}
bool operator==(Foo const & rhs) const {
return bar == rhs.bar;
}
std::vector<int> bar;
};
namespace std {
template<>
struct hash<Foo> {
size_t operator()(const Foo& foo) const {
return foo.hash();
}
};
template<>
struct hash<std::shared_ptr<const Foo>> {
size_t operator()(const std::shared_ptr<const Foo>& foo) const {
/* A version using std::hash<Foo>:
std::hash<Foo> hasher;
return hasher(*foo);
*/
return foo->hash();
}
};
template<>
struct equal_to<std::shared_ptr<const Foo>> {
bool operator()(std::shared_ptr<const Foo> const & rhs,
std::shared_ptr<const Foo> const & lhs) const {
return *lhs == *rhs;
}
};
}
With that in place, you can simply declare your unordered_set like this:
std::unordered_set<std::shared_ptr<const Foo>> fooSet;
which now is the same as declaring it like this:
std::unordered_set<
std::shared_ptr<const Foo>,
std::hash<std::shared_ptr<const Foo>>,
std::equal_to<std::shared_ptr<const Foo>>
> fooSet;

Turning vector of shared_ptr into vector of shared_ptr to const

Let
class A
{
std::vector<std::shared_ptr<int>> v_;
};
Now I'd like to add access to v_ using two public member functions
std::vector<std::shared_ptr<int>> const & v() { return v_; }
and
std::vector<std::shared_ptr<int const> const & v() const { TODO }
I cannot replace TODO with return v_; though.
One option would be to not return a reference but a copy. Apart from the obvious performance penalty, this would also make the interface somewhat less desirable.
Another option is to make TODO equal to return reinterpret_cast<std::vector<std::shared_ptr<int const>> const &>(v_);
My question is, is this undefined behavior? Or, alternatively, is there a better option, preferably without using reinterpret_cast?
A way to avoid copying the container is to provide transform iterators that transform the element on dereference:
#include <vector>
#include <memory>
#include <boost/iterator/transform_iterator.hpp>
class A
{
std::vector<std::shared_ptr<int> > v_;
struct Transform
{
template<class T>
std::shared_ptr<T const> operator()(std::shared_ptr<T> const& p) const {
return p;
}
};
public:
A() : v_{std::make_shared<int>(1), std::make_shared<int>(2)} {}
using Iterator = boost::transform_iterator<Transform, std::vector<std::shared_ptr<int> >::const_iterator>;
Iterator begin() const { return Iterator{v_.begin()}; }
Iterator end() const { return Iterator{v_.end()}; }
};
int main() {
A a;
// Range access.
for(auto const& x : a)
std::cout << *x << '\n';
// Indexed access.
auto iterator_to_second_element = a.begin() + 1;
std::cout << **iterator_to_second_element << '\n';
}
Putting aside the discussion of whether or not you should return a reference to a member...
std::vector already propagates its own const qualifier to the references, pointee's and iterators it returns. The only hurdle is making it propagate further to the pointee type of the std::shared_ptr. You can use a class like std::experimental::propagate_const (that will hopefully be standardized) to facilitate that. It will do as its name implies, for any pointer or pointer-like object it wraps.
class A
{
using ptr_type = std::experimental::propagate_const<std::shared_ptr<int>>;
std::vector<ptr_type> v_;
};
Thus TODO can become return v_;, and any access to the pointees (like in the range-based for you wish to support) will preserve const-ness.
Only caveat is that it's a moveable only type, so copying out an element of the vector will require a bit more work (for instance, by calling std::experimental::get_underlying) with the element type of the vector itself.

Virtually turn vector of struct into vector of struct members

I have a function that takes a vector-like input. To simplify things, let's use this print_in_order function:
#include <iostream>
#include <vector>
template <typename vectorlike>
void print_in_order(std::vector<int> const & order,
vectorlike const & printme) {
for (int i : order)
std::cout << printme[i] << std::endl;
}
int main() {
std::vector<int> printme = {100, 200, 300};
std::vector<int> order = {2,0,1};
print_in_order(order, printme);
}
Now I have a vector<Elem> and want to print a single integer member, Elem.a, for each Elem in the vector. I could do this by creating a new vector<int> (copying a for all Elems) and pass this to the print function - however, I feel like there must be a way to pass a "virtual" vector that, when operator[] is used on it, returns this only the member a. Note that I don't want to change the print_in_order function to access the member, it should remain general.
Is this possible, maybe with a lambda expression?
Full code below.
#include <iostream>
#include <vector>
struct Elem {
int a,b;
Elem(int a, int b) : a(a),b(b) {}
};
template <typename vectorlike>
void print_in_order(std::vector<int> const & order,
vectorlike const & printme) {
for (int i : order)
std::cout << printme[i] << std::endl;
}
int main() {
std::vector<Elem> printme = {Elem(1,100), Elem(2,200), Elem(3,300)};
std::vector<int> order = {2,0,1};
// how to do this?
virtual_vector X(printme) // behaves like a std::vector<Elem.a>
print_in_order(order, X);
}
It's not really possible to directly do what you want. Instead you might want to take a hint from the standard algorithm library, for example std::for_each where you take an extra argument that is a function-like object that you call for each element. Then you could easily pass a lambda function that prints only the wanted element.
Perhaps something like
template<typename vectorlike, typename functionlike>
void print_in_order(std::vector<int> const & order,
vectorlike const & printme,
functionlike func) {
for (int i : order)
func(printme[i]);
}
Then call it like
print_in_order(order, printme, [](Elem const& elem) {
std::cout << elem.a;
});
Since C++ have function overloading you can still keep the old print_in_order function for plain vectors.
Using member pointers you can implement a proxy type that will allow you view a container of objects by substituting each object by one of it's members (see pointer to data member) or by one of it's getters (see pointer to member function). The first solution addresses only data members, the second accounts for both.
The container will necessarily need to know which container to use and which member to map, which will be provided at construction. The type of a pointer to member depends on the type of that member so it will have to be considered as an additional template argument.
template<class Container, class MemberPtr>
class virtual_vector
{
public:
virtual_vector(const Container & p_container, MemberPtr p_member_ptr) :
m_container(&p_container),
m_member(p_member_ptr)
{}
private:
const Container * m_container;
MemberPtr m_member;
};
Next, implement the operator[] operator, since you mentioned that it's how you wanted to access your elements. The syntax for dereferencing a member pointer can be surprising at first.
template<class Container, class MemberPtr>
class virtual_vector
{
public:
virtual_vector(const Container & p_container, MemberPtr p_member_ptr) :
m_container(&p_container),
m_member(p_member_ptr)
{}
// Dispatch to the right get method
auto operator[](const size_t p_index) const
{
return (*m_container)[p_index].*m_member;
}
private:
const Container * m_container;
MemberPtr m_member;
};
To use this implementation, you would write something like this :
int main() {
std::vector<Elem> printme = { Elem(1,100), Elem(2,200), Elem(3,300) };
std::vector<int> order = { 2,0,1 };
virtual_vector<decltype(printme), decltype(&Elem::a)> X(printme, &Elem::a);
print_in_order(order, X);
}
This is a bit cumbersome since there is no template argument deduction happening. So lets add a free function to deduce the template arguments.
template<class Container, class MemberPtr>
virtual_vector<Container, MemberPtr>
make_virtual_vector(const Container & p_container, MemberPtr p_member_ptr)
{
return{ p_container, p_member_ptr };
}
The usage becomes :
int main() {
std::vector<Elem> printme = { Elem(1,100), Elem(2,200), Elem(3,300) };
std::vector<int> order = { 2,0,1 };
auto X = make_virtual_vector(printme, &Elem::a);
print_in_order(order, X);
}
If you want to support member functions, it's a little bit more complicated. First, the syntax to dereference a data member pointer is slightly different from calling a function member pointer. You have to implement two versions of the operator[] and enable the correct one based on the member pointer type. Luckily the standard provides std::enable_if and std::is_member_function_pointer (both in the <type_trait> header) which allow us to do just that. The member function pointer requires you to specify the arguments to pass to the function (non in this case) and an extra set of parentheses around the expression that would evaluate to the function to call (everything before the list of arguments).
template<class Container, class MemberPtr>
class virtual_vector
{
public:
virtual_vector(const Container & p_container, MemberPtr p_member_ptr) :
m_container(&p_container),
m_member(p_member_ptr)
{}
// For mapping to a method
template<class T = MemberPtr>
auto operator[](std::enable_if_t<std::is_member_function_pointer<T>::value == true, const size_t> p_index) const
{
return ((*m_container)[p_index].*m_member)();
}
// For mapping to a member
template<class T = MemberPtr>
auto operator[](std::enable_if_t<std::is_member_function_pointer<T>::value == false, const size_t> p_index) const
{
return (*m_container)[p_index].*m_member;
}
private:
const Container * m_container;
MemberPtr m_member;
};
To test this, I've added a getter to the Elem class, for illustrative purposes.
struct Elem {
int a, b;
int foo() const { return a; }
Elem(int a, int b) : a(a), b(b) {}
};
And here is how it would be used :
int main() {
std::vector<Elem> printme = { Elem(1,100), Elem(2,200), Elem(3,300) };
std::vector<int> order = { 2,0,1 };
{ // print member
auto X = make_virtual_vector(printme, &Elem::a);
print_in_order(order, X);
}
{ // print method
auto X = make_virtual_vector(printme, &Elem::foo);
print_in_order(order, X);
}
}
You've got a choice of two data structures
struct Employee
{
std::string name;
double salary;
long payrollid;
};
std::vector<Employee> employees;
Or alternatively
struct Employees
{
std::vector<std::string> names;
std::vector<double> salaries;
std::vector<long> payrollids;
};
C++ is designed with the first option as the default. Other languages such as Javascript tend to encourage the second option.
If you want to find mean salary, option 2 is more convenient. If you want to sort the employees by salary, option 1 is easier to work with.
However you can use lamdas to partially interconvert between the two. The lambda is a trivial little function which takes an Employee and returns a salary for him - so effectively providing a flat vector of doubles we can take the mean of - or takes an index and an Employees and returns an employee, doing a little bit of trivial data reformatting.
template<class F>
struct index_fake_t{
F f;
decltype(auto) operator[](std::size_t i)const{
return f(i);
}
};
template<class F>
index_fake_t<F> index_fake( F f ){
return{std::move(f)};
}
template<class F>
auto reindexer(F f){
return [f=std::move(f)](auto&& v)mutable{
return index_fake([f=std::move(f),&v](auto i)->decltype(auto){
return v[f(i)];
});
};
}
template<class F>
auto indexer_mapper(F f){
return [f=std::move(f)](auto&& v)mutable{
return index_fake([f=std::move(f),&v](auto i)->decltype(auto){
return f(v[i]);
});
};
}
Now, print in order can be rewritten as:
template <typename vectorlike>
void print(vectorlike const & printme) {
for (auto&& x:printme)
std::cout << x << std::endl;
}
template <typename vectorlike>
void print_in_order(std::vector<int> const& reorder, vectorlike const & printme) {
print(reindexer([&](auto i){return reorder[i];})(printme));
}
and printing .a as:
print_in_order( reorder, indexer_mapper([](auto&&x){return x.a;})(printme) );
there may be some typos.

Extend lifetime of a range for boost::filtered

I have classes that behave like a lazy container, generating values on the fly. Then in some cases, I would like to filter the values. Boost::range::adaptors::filtered seems to be well-suited. However it does not keep any reference to the "range", it just store the begin/end iterators.
The following code mimics my use case. But it does not work: the container is destroyed before r is used.
#include <iostream>
#include <vector>
#include <boost/range/adaptor/filtered.hpp>
#define PING() std::cerr << __PRETTY_FUNCTION__ << '\n'
using ints = std::vector<int>;
struct container
{
container() { PING(); }
~container() { PING(); }
using value_type = typename ints::value_type;
using iterator = typename ints::iterator;
using const_iterator = typename ints::const_iterator;
iterator begin() { PING(); return std::begin(c_); }
iterator end() { PING(); return std::end(c_); }
const_iterator begin() const { PING(); return std::cbegin(c_); }
const_iterator end() const { PING(); return std::cend(c_); }
ints c_ = { 1, 2, 3, 4, 5 };
};
int main()
{
auto r = container{} | boost::adaptors::filtered([](auto&& v) { return v % 2; });
std::cerr << "Loop\n";
for (auto i: r)
std::cout << i << '\n';
}
It results in (live code):
container::container()
const_iterator container::begin() const
const_iterator container::end() const
const_iterator container::end() const
const_iterator container::end() const
container::~container()
Loop
1
3
5
Is there a simple way to ensure that everybody lives as long as I need it? Of course in main I could declare a variable to store the container{}, but that's inadequate for my real world use where this container is actually obtained by querying some objects ; I don't want the client side to have to deal with this.
It seems that the simplest would be to rewrite some version of filtered that would keep a copy of the range, but I'd like to look for a solution that would avoid writing too much code. And I'm really looking for a Range-v2 solution: it's probably too soon for me to depend upon Range-v3.
So this is nasty.
The trick is you have to store the container first, then apply the pipe to the stored container, then pretend to be that range-like.
template<class X>struct store{ X data; };
template<class Src, class Range>
struct save_src_range:
private store<Src>,
Range
{
// boilerplate for copy/move goes here (TODO)
template<class S, class RangeFactory>
save_src_range( S&& s, RangeFactory&& f ):
store<Src>{std::forward<S>(s)},
Range( std::forward<RangeFactory>(f)(this->data) )
{}
};
now that needs to be gussed up with a deducing creation function and the like.
Next, we need a syntactically pretty way to insert that capability into the existing syntax.
One approach is like:
keep_a_copy( source ) | boost::adapters::filter( ... blah ... )
where we do some judo and make it work magically, maybe even after chaining.
Or
source | keep_source_copy( boost::adapters::filter( ... blah ... ) )
which is a bit easier I think.
I took a stab at it, and it is modestly painful, but I don't see anything fundamentally impossible. It definitely involves writing too much code.