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?
CodePudding user response:
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;
};