Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<ranges>: views::counted::_Choose() misses difference casting for contiguous_iterator case #5183

Open
hewillk opened this issue Dec 11, 2024 · 7 comments
Labels
bug Something isn't working

Comments

@hewillk
Copy link
Contributor

hewillk commented Dec 11, 2024

STL/stl/inc/ranges

Lines 4214 to 4220 in 89ca073

template <class _It>
_NODISCARD static consteval _Choice_t<_St> _Choose() noexcept {
using _Decayed = decay_t<_It>;
_STL_INTERNAL_STATIC_ASSERT(input_or_output_iterator<_Decayed>);
if constexpr (contiguous_iterator<_Decayed>) {
return {_St::_Span,
noexcept(span(_STD to_address(_STD declval<_It>()), iter_difference_t<_Decayed>{}))};

The second difference argument passed into span misses the size_t casting, which would cause a hard error if it is not implicitly-convertible to size_t.
testcase:

#include <ranges>

struct ContiguousIter {
  using iterator_category = std::contiguous_iterator_tag;
  using difference_type = std::_Signed128;
  using element_type = char;
  element_type& operator*() const;
  ContiguousIter& operator++();
  ContiguousIter operator++(int);
  ContiguousIter& operator--();
  ContiguousIter operator--(int);
  ContiguousIter& operator+=(difference_type);
  ContiguousIter& operator-=(difference_type);
  element_type* operator->() const;
  element_type& operator[](difference_type) const;
  friend ContiguousIter operator+(ContiguousIter, difference_type);
  friend ContiguousIter operator+(difference_type, ContiguousIter);
  friend ContiguousIter operator-(ContiguousIter, difference_type);
  friend difference_type operator-(ContiguousIter, ContiguousIter);
  auto operator<=>(const ContiguousIter&) const = default;
};

int main() {
  ContiguousIter it;
  std::ranges::contiguous_range auto r = std::views::counted(it, 42); // boom
}

https://godbolt.org/z/e1hv1o6h8

This can be a good first issue.

@frederick-vs-ja
Copy link
Contributor

frederick-vs-ja commented Dec 11, 2024

Looks like that we can just use span(_STD to_address(_STD declval<_It>()), size_t{}).

@CaseyCarter
Copy link
Member

to_address is always noexcept. The conversion of an iterator difference type to size_t is always noexcept. Our span constructor that takes (pointer, size_t) is always noexcept. I think we can just use true.

@CaseyCarter CaseyCarter added the bug Something isn't working label Dec 12, 2024
@hewillk
Copy link
Contributor Author

hewillk commented Dec 14, 2024

STL/stl/inc/ranges

Lines 4224 to 4227 in 89ca073

} else if constexpr (constructible_from<_Decayed, _It>) {
return {_St::_Subrange_counted,
noexcept(subrange(
counted_iterator(_STD declval<_It>(), iter_difference_t<_Decayed>{}), default_sentinel))};

In addition, counted_iterator(_STD declval<_It>(), ...) here may not be well-formed for some pathological cases such as I with an explicit copy constructor, i.e. explicit I(const I&).

@CaseyCarter
Copy link
Member

std::copy_constructible and std::move_constructible have conversion requirements as well as construction requirements to disallow types whose pertinent constructors are explicit.

@hewillk
Copy link
Contributor Author

hewillk commented Dec 16, 2024

std::copy_constructible and std::move_constructible have conversion requirements as well as construction requirements to disallow types whose pertinent constructors are explicit.

Sorry, what I meant here is that if _It has an explicit copy constructor and is an lvalue, then there will be a hard error in counted_iterator(_STD declval<_It>(), ...).

@CaseyCarter
Copy link
Member

CaseyCarter commented Dec 17, 2024

Sorry, what I meant here is that if _It has an explicit copy constructor and is an lvalue, then there will be a hard error in counted_iterator(_STD declval<_It>(), ...).

I see! Yes, we don't want the program to be ill-formed for the wrong reason (EDIT: Nope, it shouldn't be ill-formed at all in this case, Casey); we should be checking convertible_to<_It, _Decayed> in _Choose instead of constructible_from<_Decayed, _It>. Thanks for spelling it out for me.

@frederick-vs-ja
Copy link
Contributor

I thing there're weird iterator types indicating that we should use is_convertible_v (Godbolt link).

#include <ranges>

struct I {
  using difference_type = int;
  I() = default;
  I(I&&) = default;
  I& operator=(I&&) = default;

  explicit I(const I&) = delete;
  template<int = 0>
  I(const I&);

  int operator*();
  I& operator++();
  void operator++(int);
};

int main() {
  I it;
  (void) std::views::counted(it, 5);   // currently rejected
  (void) std::counted_iterator(it, 5); // currently OK
}

Moreover, it's a bit unfortrunate that counted_iterator's corresponding constructor takes the iterator argument by value, which makes views::counted (at least sometimes) need an additional move. Should we add some internal mechanisms to achieve strict expression-equivalence?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants