ericniebler / range-v3

Range library for C++14/17/20, basis for C++20's std::ranges
Other
4.05k stars 437 forks source link

ranges::to eigen #1803

Open hwelzel opened 7 months ago

hwelzel commented 7 months ago

Is there a way to collect to an Eigen::VectorXd? If I do...

const auto xs = std::vector{1.2, 3.4, 5.6};
const auto ys = xs | ranges::to<Eigen::VectorXd>;

...I get among other things...

...
/opt/compiler-explorer/libs/rangesv3/0.12.0/include/range/v3/range/conversion.hpp:319:24: note: in instantiation of function template specialization 'Eigen::Matrix<double, -1, 1, 0>::Matrix<__gnu_cxx::__normal_iterator<const double *, std::vector<double>>, __gnu_cxx::__normal_iterator<const double *, std::vector<double>>>' requested here
                return Cont(I{ranges::begin(rng)}, I{ranges::end(rng)});
...

...which is understandable because eigen doesn't have that ctor. Nor does it have an assign(begin, end) for the other overload. I am not even sure Eigen::VectorXd complies to the range concept.

Still, I was wondering if there is a way to hook in a custom container to struct/function overload which would be called to initialise custom containers like eigen. I would need to do something more or less like:

Eigen::VectorXd to(std::distance(std::begin(from), std::end(from)));
std::copy(std::begin(from), std::end(from), std::begin(to));
return to;
brevzin commented 7 months ago

I'm not familiar with Eigen.

to() was standardized from C++23, you can find the mechanism for how it works here. In short, if C is the destination type and r is the source range, it'll try to do (in order):

  1. C(r)
  2. C(from_range, r)
  3. C(ranges::begin(r), ranges::end(r))
  4. C c; /* try to reserve */; ranges::copy(r, /* inserter into c */);
  5. A more complicated thing for recursion

It looks like option (4) there might work for Eigen::VectorXd, does it? If so we could try to extend the implementation here to do something similar.

range-v3 right now tries to do

  1. C c; r.reserve(ranges::size(r)); c.assign(ranges::begin(r), ranges::end(r));
  2. C(ranges::begin(r), ranges::end(r))
hwelzel commented 7 months ago

Thanks for all the info! Indeed option 4 should work with Eigen. Its pretty much what I do in my custom function in the original comment as you noted.

I tried with c++23 std::ranges::to (should've done before) but unfortunately it doesn't compile. https://godbolt.org/z/rsd8bdEKc So I am not sure that in the end it would work with ranges-v3 either. Maybe Eigen would need to do something as well.