Skip to content

Commit

Permalink
[libc++] [ranges] Implement std::ranges::distance
Browse files Browse the repository at this point in the history
This includes an experimental workaround for
LWG3664 "LWG3392 broke std::ranges::distance(a, a+3)",
but the workaround may be incomplete, I'm not sure.
This should be re-audited when LWG3664 is actually adopted,
to see if we need to change anything about our implementation.

See also microsoft/STL#2500

Differential Revision: https://reviews.llvm.org/D117940
  • Loading branch information
Quuxplusone authored and bsdjhb committed Jan 4, 2023
1 parent 41a8cd5 commit 225a82a
Show file tree
Hide file tree
Showing 7 changed files with 478 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/Status/Cxx2bIssues.csv
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
`3152 <https://wg21.link/LWG3152>`__,"``common_type`` and ``common_reference`` have flaws in common","October 2021","",""
`3293 <https://wg21.link/LWG3293>`__,"``move_iterator operator+()`` has incorrect constraints","October 2021","","","|ranges|"
`3361 <https://wg21.link/LWG3361>`__,"``safe_range<SomeRange&>`` case","October 2021","|Nothing To Do|","","|ranges|"
`3392 <https://wg21.link/LWG3392>`__,"``ranges::distance()`` cannot be used on a move-only iterator with a sized sentinel","October 2021","","","|ranges|"
`3392 <https://wg21.link/LWG3392>`__,"``ranges::distance()`` cannot be used on a move-only iterator with a sized sentinel","October 2021","|Complete|","14.0","|ranges|"
`3407 <https://wg21.link/LWG3407>`__,"Some problems with the wording changes of P1739R4","October 2021","","","|ranges|"
`3422 <https://wg21.link/LWG3422>`__,"Issues of ``seed_seq``'s constructors","October 2021","|Complete|","14.0"
`3470 <https://wg21.link/LWG3470>`__,"``convertible-to-non-slicing`` seems to reject valid case","October 2021","|Complete|","14.0","|ranges|"
Expand Down
2 changes: 1 addition & 1 deletion docs/Status/RangesPaper.csv
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Section,Description,Dependencies,Assignee,Complete
`[range.iter.ops] <https://wg21.link/range.iter.ops>`_,"| `ranges::advance <https://llvm.org/D101922>`_
| `ranges::distance <https://llvm.org/D102789>`_
| `ranges::next <https://llvm.org/D102563>`_
| `ranges::prev <https://llvm.org/D102564>`_",[iterator.concepts],Christopher Di Bella,In progress
| `ranges::prev <https://llvm.org/D102564>`_",[iterator.concepts],Christopher Di Bella and Arthur O'Dwyer,✅
`[predef.iterators] <https://wg21.link/predef.iterators>`_,Updates to predefined iterators.,"| [iterator.concepts]
| [iterator.cust.swap]
| [iterator.cust.move]",Unassigned,Not started
Expand Down
56 changes: 56 additions & 0 deletions include/__iterator/distance.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@
#define _LIBCPP___ITERATOR_DISTANCE_H

#include <__config>
#include <__iterator/concepts.h>
#include <__iterator/incrementable_traits.h>
#include <__iterator/iterator_traits.h>
#include <__ranges/access.h>
#include <__ranges/concepts.h>
#include <__ranges/size.h>
#include <type_traits>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
Expand Down Expand Up @@ -46,6 +52,56 @@ distance(_InputIter __first, _InputIter __last)
return _VSTD::__distance(__first, __last, typename iterator_traits<_InputIter>::iterator_category());
}

#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_CONCEPTS)

// [range.iter.op.distance]

namespace ranges {
namespace __distance {

struct __fn {
template<class _Ip, sentinel_for<_Ip> _Sp>
requires (!sized_sentinel_for<_Sp, _Ip>)
_LIBCPP_HIDE_FROM_ABI
constexpr iter_difference_t<_Ip> operator()(_Ip __first, _Sp __last) const {
iter_difference_t<_Ip> __n = 0;
while (__first != __last) {
++__first;
++__n;
}
return __n;
}

template<class _Ip, sized_sentinel_for<decay_t<_Ip>> _Sp>
_LIBCPP_HIDE_FROM_ABI
constexpr iter_difference_t<_Ip> operator()(_Ip&& __first, _Sp __last) const {
if constexpr (sized_sentinel_for<_Sp, __uncvref_t<_Ip>>) {
return __last - __first;
} else {
return __last - decay_t<_Ip>(__first);
}
}

template<range _Rp>
_LIBCPP_HIDE_FROM_ABI
constexpr range_difference_t<_Rp> operator()(_Rp&& __r) const {
if constexpr (sized_range<_Rp>) {
return static_cast<range_difference_t<_Rp>>(ranges::size(__r));
} else {
return operator()(ranges::begin(__r), ranges::end(__r));
}
}
};

} // namespace __distance

inline namespace __cpo {
inline constexpr auto distance = __distance::__fn{};
} // namespace __cpo
} // namespace ranges

#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_CONCEPTS)

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___ITERATOR_DISTANCE_H
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-no-concepts

// template<class I, sentinel_for<I> S>
// requires (!sized_sentinel_for<S, I>)
// constexpr iter_difference_t<I> ranges::distance(I first, S last);
//
// template<class I, sized_sentinel_for<decay_t<I>> S>
// constexpr iter_difference_t<I> ranges::distance(I&& first, S last); // TODO: update when LWG3664 is resolved

#include <iterator>
#include <cassert>

#include "test_iterators.h"
#include "test_macros.h"

template<class It, class Sent>
constexpr void test_unsized() {
static_assert(std::sentinel_for<Sent, It> && !std::sized_sentinel_for<Sent, It>);
int a[3] = {1,2,3};
{
It first = It(a);
auto last = Sent(It(a));
assert(std::ranges::distance(first, last) == 0);
assert(std::ranges::distance(It(a), last) == 0);
assert(std::ranges::distance(first, Sent(It(a))) == 0);
assert(std::ranges::distance(It(a), Sent(It(a))) == 0);
ASSERT_SAME_TYPE(decltype(std::ranges::distance(It(a), Sent(It(a)))), std::iter_difference_t<It>);
}
{
It first = It(a);
auto last = Sent(It(a + 3));
assert(std::ranges::distance(first, last) == 3);

// Test all const/ref-qualifications of both operands.
assert(std::ranges::distance(static_cast<It&>(first), static_cast<Sent&>(last)) == 3);
assert(std::ranges::distance(static_cast<It&>(first), static_cast<Sent&&>(last)) == 3);
assert(std::ranges::distance(static_cast<It&>(first), static_cast<const Sent&>(last)) == 3);
assert(std::ranges::distance(static_cast<It&>(first), static_cast<const Sent&&>(last)) == 3);
assert(std::ranges::distance(static_cast<It&&>(first), static_cast<Sent&>(last)) == 3);
assert(std::ranges::distance(static_cast<It&&>(first), static_cast<Sent&&>(last)) == 3);
assert(std::ranges::distance(static_cast<It&&>(first), static_cast<const Sent&>(last)) == 3);
assert(std::ranges::distance(static_cast<It&&>(first), static_cast<const Sent&&>(last)) == 3);
assert(std::ranges::distance(static_cast<const It&>(first), static_cast<Sent&>(last)) == 3);
assert(std::ranges::distance(static_cast<const It&>(first), static_cast<Sent&&>(last)) == 3);
assert(std::ranges::distance(static_cast<const It&>(first), static_cast<const Sent&>(last)) == 3);
assert(std::ranges::distance(static_cast<const It&>(first), static_cast<const Sent&&>(last)) == 3);
assert(std::ranges::distance(static_cast<const It&&>(first), static_cast<Sent&>(last)) == 3);
assert(std::ranges::distance(static_cast<const It&&>(first), static_cast<Sent&&>(last)) == 3);
assert(std::ranges::distance(static_cast<const It&&>(first), static_cast<const Sent&>(last)) == 3);
assert(std::ranges::distance(static_cast<const It&&>(first), static_cast<const Sent&&>(last)) == 3);
}
}

template<class It, class Sent>
constexpr void test_sized() {
static_assert(std::sized_sentinel_for<Sent, It>);
int a[] = {1,2,3};
{
It first = It(a + 3);
auto last = Sent(It(a));
assert(std::ranges::distance(first, last) == -3);

// Test all const/ref-qualifications of both operands.
assert(std::ranges::distance(static_cast<It&>(first), static_cast<Sent&>(last)) == -3);
assert(std::ranges::distance(static_cast<It&>(first), static_cast<Sent&&>(last)) == -3);
assert(std::ranges::distance(static_cast<It&>(first), static_cast<const Sent&>(last)) == -3);
assert(std::ranges::distance(static_cast<It&>(first), static_cast<const Sent&&>(last)) == -3);
assert(std::ranges::distance(static_cast<It&&>(first), static_cast<Sent&>(last)) == -3);
assert(std::ranges::distance(static_cast<It&&>(first), static_cast<Sent&&>(last)) == -3);
assert(std::ranges::distance(static_cast<It&&>(first), static_cast<const Sent&>(last)) == -3);
assert(std::ranges::distance(static_cast<It&&>(first), static_cast<const Sent&&>(last)) == -3);
assert(std::ranges::distance(static_cast<const It&>(first), static_cast<Sent&>(last)) == -3);
assert(std::ranges::distance(static_cast<const It&>(first), static_cast<Sent&&>(last)) == -3);
assert(std::ranges::distance(static_cast<const It&>(first), static_cast<const Sent&>(last)) == -3);
assert(std::ranges::distance(static_cast<const It&>(first), static_cast<const Sent&&>(last)) == -3);
assert(std::ranges::distance(static_cast<const It&&>(first), static_cast<Sent&>(last)) == -3);
assert(std::ranges::distance(static_cast<const It&&>(first), static_cast<Sent&&>(last)) == -3);
assert(std::ranges::distance(static_cast<const It&&>(first), static_cast<const Sent&>(last)) == -3);
assert(std::ranges::distance(static_cast<const It&&>(first), static_cast<const Sent&&>(last)) == -3);
}
{
It first = It(a);
auto last = Sent(It(a));
assert(std::ranges::distance(first, last) == 0);
assert(std::ranges::distance(It(a), last) == 0);
assert(std::ranges::distance(first, Sent(It(a))) == 0);
assert(std::ranges::distance(It(a), Sent(It(a))) == 0);
ASSERT_SAME_TYPE(decltype(std::ranges::distance(It(a), Sent(It(a)))), std::iter_difference_t<It>);
}
{
It first = It(a);
auto last = Sent(It(a + 3));
assert(std::ranges::distance(first, last) == 3);
assert(std::ranges::distance(It(a), last) == 3);
assert(std::ranges::distance(first, Sent(It(a + 3))) == 3);
assert(std::ranges::distance(It(a), Sent(It(a + 3))) == 3);
}
}

struct StrideCounter {
int *it_;
int *inc_;
using value_type = int;
using difference_type = int;
explicit StrideCounter();
constexpr explicit StrideCounter(int *it, int *inc) : it_(it), inc_(inc) {}
constexpr auto& operator++() { ++it_; *inc_ += 1; return *this; }
StrideCounter operator++(int);
int& operator*() const;
bool operator==(StrideCounter) const;
};
static_assert(std::forward_iterator<StrideCounter>);
static_assert(!std::sized_sentinel_for<StrideCounter, StrideCounter>);

struct SizedStrideCounter {
int *it_;
int *minus_;
using value_type = int;
explicit SizedStrideCounter();
constexpr explicit SizedStrideCounter(int *it, int *minus) : it_(it), minus_(minus) {}
SizedStrideCounter& operator++();
SizedStrideCounter operator++(int);
int& operator*() const;
bool operator==(SizedStrideCounter) const;
constexpr int operator-(SizedStrideCounter rhs) const { *minus_ += 1; return it_ - rhs.it_; }
};
static_assert(std::forward_iterator<SizedStrideCounter>);
static_assert(std::sized_sentinel_for<SizedStrideCounter, SizedStrideCounter>);

constexpr void test_stride_counting() {
{
int a[] = {1, 2, 3};
int inc = 0;
StrideCounter first(a, &inc);
StrideCounter last(a+3, nullptr);
std::same_as<int> auto result = std::ranges::distance(first, last);
assert(result == 3);
assert(inc == 3);
}
{
int a[] = {1, 2, 3};
int minus = 0;
SizedStrideCounter first(a, &minus);
SizedStrideCounter last(a+3, nullptr);
std::same_as<int> auto result = std::ranges::distance(first, last);
assert(result == 3);
assert(minus == 1);
}
}

constexpr bool test() {
{
int a[] = {1, 2, 3};
assert(std::ranges::distance(a, a + 3) == 3);
assert(std::ranges::distance(a, a) == 0);
assert(std::ranges::distance(a + 3, a) == -3);
}

test_unsized<cpp17_input_iterator<int*>, sentinel_wrapper<cpp17_input_iterator<int*>>>();
test_unsized<output_iterator<int*>, sentinel_wrapper<output_iterator<int*>>>();
test_unsized<forward_iterator<int*>, sentinel_wrapper<forward_iterator<int*>>>();
test_unsized<bidirectional_iterator<int*>, sentinel_wrapper<bidirectional_iterator<int*>>>();
test_unsized<random_access_iterator<int*>, sentinel_wrapper<random_access_iterator<int*>>>();
test_unsized<contiguous_iterator<int*>, sentinel_wrapper<contiguous_iterator<int*>>>();
test_unsized<int*, sentinel_wrapper<int*>>();
test_unsized<const int*, sentinel_wrapper<const int*>>();
test_unsized<forward_iterator<int*>, forward_iterator<int*>>();
test_unsized<bidirectional_iterator<int*>, bidirectional_iterator<int*>>();

test_sized<cpp17_input_iterator<int*>, sized_sentinel<cpp17_input_iterator<int*>>>();
test_sized<cpp20_input_iterator<int*>, sized_sentinel<cpp20_input_iterator<int*>>>();
test_sized<output_iterator<int*>, sized_sentinel<output_iterator<int*>>>();
test_sized<forward_iterator<int*>, sized_sentinel<forward_iterator<int*>>>();
test_sized<bidirectional_iterator<int*>, sized_sentinel<bidirectional_iterator<int*>>>();
test_sized<random_access_iterator<int*>, sized_sentinel<random_access_iterator<int*>>>();
test_sized<contiguous_iterator<int*>, sized_sentinel<contiguous_iterator<int*>>>();
test_sized<int*, sized_sentinel<int*>>();
test_sized<const int*, sized_sentinel<const int*>>();
test_sized<int*, int*>();
test_sized<int*, const int*>();
test_sized<random_access_iterator<int*>, random_access_iterator<int*>>();
test_sized<contiguous_iterator<int*>, contiguous_iterator<int*>>();

{
using It = cpp20_input_iterator<int*>; // non-copyable, thus not a sentinel for itself
static_assert(!std::is_copy_constructible_v<It>);
static_assert(!std::sentinel_for<It, It>);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), It&, It&>);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), It&, It&&>);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), It&&, It&>);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), It&&, It&&>);
}
{
using It = cpp20_input_iterator<int*>; // non-copyable
using Sent = sentinel_wrapper<It>; // not a sized sentinel
static_assert(std::sentinel_for<Sent, It> && !std::sized_sentinel_for<Sent, It>);
int a[] = {1,2,3};
Sent last = Sent(It(a + 3));
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), It&, Sent&>);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), It&, Sent&&>);
assert(std::ranges::distance(It(a), last) == 3);
assert(std::ranges::distance(It(a), Sent(It(a + 3))) == 3);
}
{
using It = cpp17_input_iterator<int*>; // not a sentinel for itself
static_assert(!std::sentinel_for<It, It>);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), It&, It&>);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), It&, It&&>);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), It&&, It&>);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), It&&, It&&>);
}

// Calling it on a non-iterator or non-sentinel isn't allowed.
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), int, int>);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), int*, int>);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), int, int*>);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), int*, char*>);

return true;
}

int main(int, char**) {
test();
static_assert(test());

return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-no-concepts

// template<class I, sentinel_for<I> S>
// requires (!sized_sentinel_for<S, I>)
// constexpr iter_difference_t<I> ranges::distance(I first, S last);
//
// template<class I, sized_sentinel_for<decay_t<I>> S>
// constexpr iter_difference_t<I> ranges::distance(const I& first, S last);

#include <iterator>
#include <cassert>

#include "test_iterators.h"

template<class It>
struct EvilSentinel {
It p_;
friend constexpr bool operator==(EvilSentinel s, It p) { return s.p_ == p; }
friend constexpr auto operator-(EvilSentinel s, It p) { return s.p_ - p; }
friend constexpr auto operator-(It p, EvilSentinel s) { return p - s.p_; }
friend constexpr void operator-(EvilSentinel s, int(&)[3]) = delete;
friend constexpr void operator-(EvilSentinel s, int(&&)[3]) = delete;
friend constexpr void operator-(EvilSentinel s, const int(&)[3]) = delete;
friend constexpr void operator-(EvilSentinel s, const int(&&)[3]) = delete;
};
static_assert( std::sized_sentinel_for<EvilSentinel<int*>, int*>);
static_assert(!std::sized_sentinel_for<EvilSentinel<int*>, const int*>);
static_assert( std::sized_sentinel_for<EvilSentinel<const int*>, int*>);
static_assert( std::sized_sentinel_for<EvilSentinel<const int*>, const int*>);

constexpr bool test() {
{
int a[] = {1, 2, 3};
assert(std::ranges::distance(a, a + 3) == 3);
assert(std::ranges::distance(a, a) == 0);
assert(std::ranges::distance(a + 3, a) == -3);
}
{
int a[] = {1, 2, 3};
assert(std::ranges::distance(a, EvilSentinel<int*>{a+3}) == 3);
assert(std::ranges::distance(a, EvilSentinel<int*>{a}) == 0);
assert(std::ranges::distance(a+3, EvilSentinel<int*>{a}) == -3);
assert(std::ranges::distance(std::move(a), EvilSentinel<int*>{a+3}) == 3);
}
{
const int a[] = {1, 2, 3};
assert(std::ranges::distance(a, EvilSentinel<const int*>{a+3}) == 3);
assert(std::ranges::distance(a, EvilSentinel<const int*>{a}) == 0);
assert(std::ranges::distance(a+3, EvilSentinel<const int*>{a}) == -3);
assert(std::ranges::distance(std::move(a), EvilSentinel<const int*>{a+3}) == 3);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), const int(&)[3], EvilSentinel<int*>>);
static_assert(!std::is_invocable_v<decltype(std::ranges::distance), const int(&&)[3], EvilSentinel<int*>>);
}

return true;
}

int main(int, char**) {
test();
static_assert(test());

return 0;
}
Loading

0 comments on commit 225a82a

Please sign in to comment.