3

Why does Eigen::VectorXd not satisfy the concept std::ranges::contiguous_range? That is, static_assert(std::ranges::contiguous_range<Eigen::VectorXd>); does not compile.

Also, is there the possibility to specialize a template to make Eigen vectors satisfy the contiguous range concept? For instance, we can specialize std::ranges::enable_borrowed_range to make any range satisfy the std::range::borrowed_range concept. In other words, is there a way to make the above static assertion compile?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
fdev
  • 127
  • 12

1 Answers1

7

Contiguous ranges have to be opted into. There is no way to determine just by looking at an iterator whether or not it is contiguous or just random access. Consider the difference between deque<int>::iterator and vector<int>::iterator - they provide all the same operations that return all the same things, how would you know unless the vector<int>::iterator explicitly told you?

Eigen's iterators do not do this yet. Indeed, before C++20 there was no notion of a contiguous iterator to begin with. That's new with C++20 Ranges.

You can see this if you try to just verify that it is contiguous:

using I = Eigen::VectorXd::iterator;
static_assert(std::contiguous_iterator<I>);

On gcc, the diagnostic indicates:

/opt/compiler-explorer/gcc-trunk-20211221/include/c++/12.0.0/concepts:67:13:   required for the satisfaction of 'derived_from<typename std::__detail::__iter_concept_impl<_Iter>::type, std::contiguous_iterator_tag>' [with _Iter = Eigen::internal::pointer_based_stl_iterator<Eigen::Matrix<double, -1, 1, 0, -1, 1> >]
/opt/compiler-explorer/gcc-trunk-20211221/include/c++/12.0.0/concepts:67:28: note:   'std::contiguous_iterator_tag' is not a base of 'std::random_access_iterator_tag'
   67 |     concept derived_from = __is_base_of(_Base, _Derived)
      |                            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Basically, our category is random-access, not contiguous.

The way for Eigen to do this correctly is to add:

 template<typename XprType>
 class pointer_based_stl_iterator
 {
   enum { is_lvalue  = internal::is_lvalue<XprType>::value };
   typedef pointer_based_stl_iterator<typename internal::remove_const<XprType>::type> non_const_iterator;
   typedef pointer_based_stl_iterator<typename internal::add_const<XprType>::type> const_iterator;
   typedef typename internal::conditional<internal::is_const<XprType>::value,non_const_iterator,const_iterator>::type other_iterator;
   // NOTE: in C++03 we cannot declare friend classes through typedefs because we need to write friend class:
   friend class pointer_based_stl_iterator<typename internal::add_const<XprType>::type>;
   friend class pointer_based_stl_iterator<typename internal::remove_const<XprType>::type>;
 public:
   typedef Index difference_type;
   typedef typename XprType::Scalar value_type;
   typedef std::random_access_iterator_tag iterator_category;
+  typedef std::contiguous_iterator_tag iterator_concept;
   typedef typename internal::conditional<bool(is_lvalue), value_type*, const value_type*>::type pointer;
   typedef typename internal::conditional<bool(is_lvalue), value_type&, const value_type&>::type reference;
};

That would make it a C++20 contiguous iterator.

Alternatively, you could do this yourself externally, although it's not a great idea (really, it's Eigen that should do this correctly), and has to be done earlier than any ranges uses:

template <>
struct std::iterator_traits<I> {
    using iterator_concept = std::contiguous_iterator_tag;
    using iterator_category = std::random_access_iterator_tag;
    using value_type = typename I::value_type;
    using difference_type = typename I::difference_type;
};
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thank you very much for your great answer! I agree that it would be better if *Eigen* fixed this issue, but why do you specifically advise against specializing `std::iterator_traits`? If I know that *Eigen* vectors do model the contiguous range concept, then what is wrong with "notifying" the iterator traits about this fact? – fdev Dec 21 '21 at 23:05
  • 1
    @fdev Have to do it as early as possible, and every part of your program has to agree on this. It's very easy to end up with odr violations as a result of different parts of a program having different understandings of what `iterator_traits` would be. – Barry Dec 21 '21 at 23:14
  • I see! So the only problem would be if I forget to include the header file where I specialize `std::iterator_traits`, but as long as this does not happen I should be safe. In my case, I would do this in a header file that is strictly required by all other files in the repository (it defines the most basic data types). Thus, I should never incur ODR violations! – fdev Dec 21 '21 at 23:29
  • Unfortunately, specializing `std::iterator_traits` does not work well if I want to satisfy the contiguous range concept with all *Eigen* vector types, that is, both dynamic and fixed-size vectors, segments, and `Eigen::Ref` objects. Do you know if there is a way to achieve this without listing all possible types? – fdev Dec 22 '21 at 08:27
  • @fdev You'd just need to do it for all of the iterator types. – Barry Dec 22 '21 at 14:31
  • But it looks like there is one for each *Eigen* type due to CRTP, am I wrong? – fdev Dec 22 '21 at 18:29
  • @fdev Not really familiar with Eigen. But... so? – Barry Dec 22 '21 at 20:36
  • 2
    @fdev How about making this a feature request: https://gitlab.com/libeigen/eigen/-/issues ? – dtell Dec 26 '21 at 01:29