Skip to content

Commit 989eb1e

Browse files
A small amount of std::ranges from the future
1 parent d1ea5c1 commit 989eb1e

File tree

5 files changed

+311
-2
lines changed

5 files changed

+311
-2
lines changed

src/bsoncxx/include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ set_dist_list(src_bsoncxx_include_DIST
6161
bsoncxx/v_noabi/bsoncxx/stdx/make_unique.hpp
6262
bsoncxx/v_noabi/bsoncxx/stdx/optional.hpp
6363
bsoncxx/v_noabi/bsoncxx/stdx/operators.hpp
64+
bsoncxx/v_noabi/bsoncxx/stdx/ranges.hpp
6465
bsoncxx/v_noabi/bsoncxx/stdx/string_view.hpp
6566
bsoncxx/v_noabi/bsoncxx/stdx/type_traits.hpp
6667
bsoncxx/v_noabi/bsoncxx/string/to_string.hpp
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
/**
2+
* @file ranges.hpp
3+
* @brief A backport of a small amount of std::ranges from C++20
4+
* @date 2023-11-14
5+
*
6+
* @copyright Copyright (c) 2023
7+
*/
8+
#pragma once
9+
10+
#include <cstddef>
11+
#include <initializer_list>
12+
#include <iterator>
13+
#include <memory>
14+
#include <type_traits>
15+
#include <utility>
16+
17+
#include <bsoncxx/stdx/iterator.hpp>
18+
#include <bsoncxx/stdx/type_traits.hpp>
19+
20+
#include <bsoncxx/config/prelude.hpp>
21+
22+
namespace bsoncxx {
23+
inline namespace v_noabi {
24+
namespace detail {
25+
26+
/**
27+
* XXX: These _decay_xyz functions are required for MSVC 14.0 (VS2015) compat.
28+
*
29+
* The requirements on each of the below invocable objects is that the decay-copied
30+
* return type meet certain requirements. It would be easier to include these
31+
* requirements inline directly via enable_if/requires_t, but it is difficult to
32+
* position them in a way that doesn't confuse MSVC 14.0. The most reliable
33+
* configuration is to use a SFINAE-disappearing return type, so these functions
34+
* serve that purpose while simultaneously performing the required decay-copy.
35+
*
36+
* It's possible that these are actually easier on the eyes than the inline
37+
* requirements…
38+
*/
39+
template <typename I>
40+
constexpr requires_t<I, is_iterator<I>> _decay_iterator(I i) noexcept {
41+
return i;
42+
}
43+
44+
template <typename I, typename S>
45+
constexpr requires_t<S, is_sentinel_for<I, S>> _decay_sentinel(S s) noexcept {
46+
return s;
47+
}
48+
49+
template <typename Sz>
50+
constexpr requires_t<Sz, std::is_integral<Sz>> _decay_integral(Sz s) noexcept {
51+
return s;
52+
}
53+
54+
template <typename P>
55+
constexpr requires_t<P, std::is_pointer<P>> _decay_copy_pointer(P p) noexcept {
56+
return p;
57+
}
58+
59+
/**
60+
* @brief Access the beginning iterator of a range-like object.
61+
*
62+
* Requires that the result is a valid iterator type
63+
*
64+
* Based on std::ranges::begin()
65+
*/
66+
static constexpr struct _begin_fn {
67+
struct impl {
68+
// 1: Object is an array
69+
template <typename El, std::size_t N>
70+
static constexpr auto x(El (&arr)[N], rank<5>) bsoncxx_returns(arr + 0);
71+
72+
// 2: Object has member .begin() returning an iterator
73+
template <typename R>
74+
static constexpr auto x(R& rng, rank<4>) bsoncxx_returns(_decay_iterator(rng.begin()));
75+
76+
// 3: Object has an ADL-visible begin(x) returning an iterator
77+
template <typename R>
78+
static constexpr auto x(R& rng, rank<3>) bsoncxx_returns(_decay_iterator(begin(rng)));
79+
};
80+
81+
template <typename R>
82+
constexpr auto operator()(R&& rng) const bsoncxx_returns(impl::x(rng, rank<10>{}));
83+
} begin;
84+
85+
/**
86+
* @brief Yields the iterator result from calling begin(R&), if that expression
87+
* is valid
88+
*/
89+
template <typename R>
90+
using iterator_t = decltype(begin(std::declval<R&>()));
91+
92+
/**
93+
* @brief Access the sentinel value for a range-like object.
94+
*
95+
* Requires that a valid begin() is also available, and that the end() returns
96+
* a type which is_sentinel_for the type iterator_t<R>
97+
*
98+
* Based on std::ranges::end().
99+
*/
100+
static constexpr struct _end_fn {
101+
struct impl {
102+
// 1: Range is an array
103+
template <typename Iter, typename El, std::size_t N>
104+
static constexpr auto x(El (&arr)[N], rank<5>) bsoncxx_returns(arr + N);
105+
106+
// 2: Range has member .end() returning a valid sentinel
107+
template <typename Iter, typename R>
108+
static constexpr auto x(R& rng, rank<4>) bsoncxx_returns(_decay_sentinel<Iter>(rng.end()));
109+
110+
// 3: Range has ADL-found end(x) returning a valid sentinel
111+
template <typename Iter, typename R>
112+
static constexpr auto x(R& r, rank<3>) bsoncxx_returns(_decay_sentinel<Iter>(end(r)));
113+
};
114+
115+
template <typename R>
116+
constexpr auto operator()(R&& rng) const
117+
bsoncxx_returns(impl::x<iterator_t<R>>(rng, rank<5>{}));
118+
} end;
119+
120+
/**
121+
* @brief Yield the type resulting from end(R&), if that expression is valid
122+
*/
123+
template <typename R>
124+
using sentinel_t = decltype(end(std::declval<R&>()));
125+
126+
/**
127+
* @brief Obtain the size of the given range `rng`.
128+
*
129+
* Returns the first valid of:
130+
* - The bounds of the array, if `rng` is an array
131+
* - The result of rng.size() on R, if that returns an integral type
132+
* - The result of size(rng), if such a name is visible via ADL and returns
133+
* an integral type.
134+
* - The value of (end(rng) - begin(rng)) as an unsigned integer if `rng` is
135+
* forward-iterable and the sentinel is a sized sentinel type.
136+
*/
137+
static constexpr struct _size_fn {
138+
struct impl {
139+
// 1: Array of known bound
140+
template <typename Element, std::size_t N>
141+
static constexpr auto x(Element (&)[N], rank<5>) bsoncxx_returns(N);
142+
143+
// 2: Range with member .size()
144+
template <typename R>
145+
static constexpr auto x(R& rng, rank<4>) bsoncxx_returns(_decay_integral(rng.size()));
146+
147+
// 3: Range with ADL-found size(x)
148+
template <typename R>
149+
static constexpr auto x(R& rng, rank<3>) bsoncxx_returns(_decay_integral(size(rng)));
150+
151+
// 4: Range is a forward-range and has a sized sentinel type
152+
template <
153+
typename R,
154+
typename Iter = iterator_t<R>,
155+
typename Sentinel = sentinel_t<R>,
156+
// Require a forward iterator:
157+
requires_t<int, std::is_base_of<std::forward_iterator_tag, iterator_concept_t<Iter>>> =
158+
0,
159+
// Require a sized sentinel:
160+
requires_t<int, is_sized_sentinel_for<Sentinel, Iter>> = 0,
161+
// We cast to an unsigned type from the difference type:
162+
typename Sz = make_unsigned_t<difference_t<Sentinel, Iter>>>
163+
static constexpr auto x(R& rng, rank<3>)
164+
bsoncxx_returns(static_cast<Sz>(end(rng) - begin(rng)));
165+
};
166+
167+
template <typename R>
168+
constexpr auto operator()(R&& rng) const bsoncxx_returns(impl::x(rng, rank<10>{}));
169+
} size;
170+
171+
/**
172+
* @brief Obtain the size type of the given range
173+
*/
174+
template <typename R>
175+
using range_size_t = decltype(size(std::declval<R&>()));
176+
177+
/**
178+
* @brief Obtain the size of the given range as a signed integer type
179+
*/
180+
static constexpr struct _ssize_fn {
181+
template <typename R,
182+
typename Unsigned = range_size_t<R>,
183+
typename Signed = make_signed_t<Unsigned>,
184+
typename RetDiff =
185+
conditional_t<(sizeof(Signed) > sizeof(std::ptrdiff_t)), Signed, std::ptrdiff_t>>
186+
constexpr auto operator()(R&& rng) const bsoncxx_returns(static_cast<RetDiff>(size(rng)));
187+
} ssize;
188+
189+
/**
190+
* @brief Obtain the difference type of the given range
191+
*/
192+
template <typename R>
193+
using range_difference_t = iter_difference_t<iterator_t<R>>;
194+
195+
/**
196+
* @brief Obtain a pointer-to-data for the given `rng`.
197+
*
198+
* Returns the first valid of:
199+
* - `rng.data()` if such expression yields a pointer type
200+
* - `data(rng)` if such expression is visible via ADL and returns a pointer type
201+
* - `to_address(begin(rng))` if such expression is valid and iterator_t<R> is
202+
* a contiguous_iterator.
203+
*/
204+
static constexpr struct _data_fn {
205+
struct impl {
206+
template <typename R>
207+
static constexpr auto x(R&& rng, rank<2>) bsoncxx_returns(_decay_copy_pointer(rng.data()));
208+
209+
template <typename R, requires_t<int, is_contiguous_iterator<iterator_t<R>>> = 0>
210+
static constexpr auto x(R&& rng, rank<1>) bsoncxx_returns(to_address(begin(rng)));
211+
};
212+
213+
template <typename R>
214+
constexpr auto operator()(R&& rng) const bsoncxx_returns(impl::x(rng, rank<10>{}));
215+
} data;
216+
217+
/**
218+
* @brief Get the type returned by data(R&), if valid
219+
*/
220+
template <typename R>
221+
using range_data_t = decltype(data(std::declval<R&>()));
222+
223+
/**
224+
* @brief Get the value type of the range
225+
*
226+
* Equivalent: iter_value_t<iterator_t<R>>
227+
*/
228+
template <typename R>
229+
using range_value_t = iter_value_t<iterator_t<R>>;
230+
231+
template <typename R>
232+
using range_concept_t = iterator_concept_t<iterator_t<R>>;
233+
234+
/**
235+
* @brief Trait detects if the given type is a range
236+
*/
237+
template <typename R>
238+
struct is_range : conjunction<is_detected<iterator_t, R>, is_detected<sentinel_t, R>> {};
239+
240+
/**
241+
* @brief Detect if the given range is contiguous-ish.
242+
*
243+
* This is not quite like ranges::contiguous_range, as it does not require that
244+
* the iterator be configuous. It only looks for a valid data(R) and size(R).
245+
* Without stdlib support, we cannot fully detect contiguous_iterators, but we
246+
* want to be able to support, basic_string<C>, vector<T>, array<T>, etc, which
247+
* have .data() and .size()
248+
*/
249+
template <typename R>
250+
struct is_contiguous_range
251+
: conjunction<is_range<R>, is_detected<range_data_t, R>, is_detected<range_size_t, R>> {};
252+
253+
} // namespace detail
254+
} // namespace v_noabi
255+
} // namespace bsoncxx
256+
257+
#include <bsoncxx/config/postlude.hpp>

src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/stdx/type_traits.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,7 @@ struct rank<0> {};
459459
struct _decay_copy_fn {
460460
template <typename T>
461461
constexpr auto operator()(T&& arg) const
462-
noexcept(std::is_nothrow_constructible<decay_t<T>, T&&>{})
463-
-> requires_t<decay_t<T>, std::is_constructible<decay_t<T>, T&&>> {
462+
noexcept(noexcept(static_cast<decay_t<T>>(static_cast<T&&>(arg)))) -> decay_t<T> {
464463
return static_cast<decay_t<T>>(static_cast<T&&>(arg));
465464
}
466465
};

src/bsoncxx/test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ add_executable(test_bson
3838
oid.cpp
3939
view_or_value.cpp
4040
make_unique.test.cpp
41+
ranges.test.cpp
4142
type_traits.test.cpp
4243
iterator.test.cpp
4344
)
@@ -144,4 +145,5 @@ set_dist_list(src_bsoncxx_test_DIST
144145
type_traits.test.cpp
145146
make_unique.test.cpp
146147
iterator.test.cpp
148+
ranges.test.cpp
147149
)

src/bsoncxx/test/ranges.test.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#include <cstddef>
2+
#include <deque>
3+
#include <vector>
4+
5+
#include <bsoncxx/stdx/iterator.hpp>
6+
#include <bsoncxx/stdx/ranges.hpp>
7+
#include <third_party/catch/include/catch.hpp>
8+
9+
namespace ranges = bsoncxx::detail;
10+
11+
static_assert(ranges::is_range<std::vector<int>>{}, "fail");
12+
static_assert(ranges::is_contiguous_range<std::vector<int>>{}, "fail");
13+
static_assert(ranges::is_contiguous_range<std::vector<int>&>{}, "fail");
14+
static_assert(ranges::is_contiguous_range<std::vector<int> const&>{}, "fail");
15+
16+
static_assert(
17+
std::is_same<ranges::range_difference_t<std::vector<int>>, std::vector<int>::difference_type>{},
18+
"fail");
19+
20+
static_assert(ranges::is_range<int (&)[2]>{}, "fail");
21+
static_assert(ranges::is_contiguous_range<int (&)[2]>{}, "fail");
22+
static_assert(std::is_same<ranges::range_difference_t<int[21]>, std::ptrdiff_t>{}, "fail");
23+
24+
static_assert(ranges::is_range<std::deque<int>>{}, "fail");
25+
static_assert(!ranges::is_contiguous_range<std::deque<int>>{}, "fail");
26+
27+
TEST_CASE("Range from vector") {
28+
std::vector<int> nums = {1, 2, 3, 4};
29+
auto it = ranges::begin(nums);
30+
REQUIRE(it != ranges::end(nums));
31+
static_assert(std::is_same<decltype(it), std::vector<int>::iterator>::value, "fail");
32+
CHECK(*it == 1);
33+
++it;
34+
CHECK(it == std::next(ranges::begin(nums)));
35+
CHECK(*it == 2);
36+
CHECK(ranges::size(nums) == 4);
37+
CHECK(ranges::ssize(nums) == 4);
38+
CHECK(ranges::data(nums) == nums.data());
39+
CHECK(ranges::to_address(ranges::begin(nums)) == nums.data());
40+
}
41+
42+
TEST_CASE("Range from array") {
43+
int array[] = {1, 2, 3, 4};
44+
auto it = ranges::begin(array);
45+
CHECK(it == array + 0);
46+
CHECK(ranges::end(array) == array + 4);
47+
CHECK(ranges::size(array) == 4);
48+
CHECK(ranges::ssize(array) == 4);
49+
CHECK(ranges::data(array) == ranges::begin(array));
50+
}

0 commit comments

Comments
 (0)