I have a mathy library. In this library, I have functions to manipulate hyperplanes in a simplex space, so that I can sort through them in various ways.
It turns out that these hyperplanes can represent different things in different contexts. While the math is the same, in each context the hyperplanes mean a different thing, and are associated with different data structures.
It is advantageous to me to be able to write the code to manipulate the hyperplanes once, but allowing them to handle different data structures.
Below is a simplified example that tries to explain what I am trying to do:
// Assume this struct represent my hyperplane, or whatever
// construct I want to be able to manipulate.
struct Math {
int x;
};
// Here is my function `foo` which expects a range of Math. It does
// some work on it, and re-arranges it as it is useful to me.
template <typename It>
void foo(It begin, It end) {
while (begin < end) {
--end;
if (begin->x < end->x)
std::iter_swap(begin, end);
++begin;
}
}
template <typename Range>
void foo(Range & r) {
foo(ranges::begin(r), ranges::end(r));
}
This is basically my underlying functionality, which is common for every additional class that uses my hyperplanes (or, in this case, the Math
class).
Now in other parts of my library I have classes that look like this:
struct Something {
int additional_metadata;
Math contextual_name_for_math;
};
struct SomethingElse {
double other_metadata;
std::vector<int> some_other_metadata;
Math another_different_contextual_name;
};
Now I need to be able to apply foo
to ranges of these classes and rearrange them based on the properties of the Math
they contain. At the same time:
foo
does not know the contextual name thatMath
has in each of these classes.foo
does not care about the additional metadata that is present.
What I would like to write is something like this:
// Create data
std::vector<Something> S{{1,{2}},{3,{4}},{5,{6}}};
// Transform data in to view of Math, so that 'foo' can work on it
auto S_as_math = S | ranges::view::transform(
// I guess I can remove the consts here, although `foo` does not
// really need to alter the values, it only shuffles things around.
[](auto const& s) -> Math const& { return s.contextual_name_for_math; }
);
// Do work inline on the view, but hopefully on the underlying S too.
foo(S_as_math);
// Print results.
for (auto s : S) {
std::cout << '(' << s.additional_metadata << ", "
<< s.contextual_name_for_math.x << ")\n";
}
std::cout << "\n";
// expected, keeps Math and associated metadata together:
//
// (5, 6)
// (3, 4)
// (1, 2)
//
// NOT WANTED, only shuffles Math with no regard for metadata:
//
// (1, 6)
// (3, 4)
// (5, 2)
Currently I am doing this by passing boost::transform_iterator
s to foo
that extract the Math
component when dereferenced, and by using a custom implementation of iter_swap
inside foo
which is able to know whether it is being passed a proxy iterator and always swaps the underlying originals. This achieves what I want.
I am curious whether this is possible to do using ranges-v3
. Currently I am able to compile this example if I remove the const
s in the lambda I use to unwrap the Something
class, but then foo
only shuffles the Math
s without keeping them together with their metadata.