Skip to content

Commit c7350d3

Browse files
A small amount of std::ranges from the future
1 parent 3868e35 commit c7350d3

File tree

5 files changed

+301
-2
lines changed

5 files changed

+301
-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: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
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+
// 1: Object is an array
68+
template <typename El, std::size_t N>
69+
static constexpr auto impl(El (&arr)[N], rank<5>) bsoncxx_returns(arr + 0);
70+
71+
// 2: Object has member .begin() returning an iterator
72+
template <typename R>
73+
static constexpr auto impl(R& rng, rank<4>) bsoncxx_returns(_decay_iterator(rng.begin()));
74+
75+
// 3: Object has an ADL-visible begin(x) returning an iterator
76+
template <typename R>
77+
static constexpr auto impl(R& rng, rank<3>) bsoncxx_returns(_decay_iterator(begin(rng)));
78+
79+
template <typename R>
80+
constexpr auto operator()(R&& rng) const bsoncxx_returns((impl)(rng, rank<10>{}));
81+
} begin;
82+
83+
/**
84+
* @brief Yields the iterator result from calling begin(R&), if that expression
85+
* is valid
86+
*/
87+
template <typename R>
88+
using iterator_t = decltype(begin(std::declval<R&>()));
89+
90+
/**
91+
* @brief Access the sentinel value for a range-like object.
92+
*
93+
* Requires that a valid begin() is also available, and that the end() returns
94+
* a type which is_sentinel_for the type iterator_t<R>
95+
*
96+
* Based on std::ranges::end().
97+
*/
98+
static constexpr struct _end_fn {
99+
// 1: Range is an array
100+
template <typename Iter, typename El, std::size_t N>
101+
static constexpr auto impl(El (&arr)[N], rank<5>) bsoncxx_returns(arr + N);
102+
103+
// 2: Range has member .end() returning a valid sentinel
104+
template <typename Iter, typename R>
105+
static constexpr auto impl(R& rng, rank<4>) bsoncxx_returns(_decay_sentinel<Iter>(rng.end()));
106+
107+
// 3: Range has ADL-found end(x) returning a valid sentinel
108+
template <typename Iter, typename R>
109+
static constexpr auto impl(R& r, rank<3>) bsoncxx_returns(_decay_sentinel<Iter>(end(r)));
110+
111+
template <typename R>
112+
constexpr auto operator()(R&& rng) const bsoncxx_returns((impl<iterator_t<R>>)(rng, rank<5>{}));
113+
} end;
114+
115+
/**
116+
* @brief Yield the type resulting from end(R&), if that expression is valid
117+
*/
118+
template <typename R>
119+
using sentinel_t = decltype(end(std::declval<R&>()));
120+
121+
/**
122+
* @brief Obtain the size of the given range `rng`.
123+
*
124+
* Returns the first valid of:
125+
* - The bounds of the array, if `rng` is an array
126+
* - The result of rng.size() on R, if that returns an integral type
127+
* - The result of size(rng), if such a name is visible via ADL and returns
128+
* an integral type.
129+
* - The value of (end(rng) - begin(rng)) as an unsigned integer if `rng` is
130+
* forward-iterable and the sentinel is a sized sentinel type.
131+
*/
132+
static constexpr struct _size_fn {
133+
// 1: Array of known bound
134+
template <typename Element, std::size_t N>
135+
static constexpr auto impl(Element (&)[N], rank<5>) bsoncxx_returns(N);
136+
137+
// 2: Range with member .size()
138+
template <typename R>
139+
static constexpr auto impl(R& rng, rank<4>) bsoncxx_returns(_decay_integral(rng.size()));
140+
141+
// 3: Range with ADL-found size(x)
142+
template <typename R>
143+
static constexpr auto impl(R& rng, rank<3>) bsoncxx_returns(_decay_integral(size(rng)));
144+
145+
// 4: Range is a forward-range and has a sized sentinel type
146+
template <
147+
typename R,
148+
typename Iter = iterator_t<R>,
149+
typename Sentinel = sentinel_t<R>,
150+
// Require a forward iterator:
151+
requires_t<int, std::is_base_of<std::forward_iterator_tag, iterator_concept_t<Iter>>> = 0,
152+
// Require a sized sentinel:
153+
requires_t<int, is_sized_sentinel_for<Sentinel, Iter>> = 0,
154+
// We cast to an unsigned type from the difference type:
155+
typename Sz = make_unsigned_t<difference_t<Sentinel, Iter>>>
156+
static constexpr auto impl(R& rng, rank<3>)
157+
bsoncxx_returns(static_cast<Sz>(end(rng) - begin(rng)));
158+
159+
template <typename R>
160+
constexpr auto operator()(R&& rng) const bsoncxx_returns((impl)(rng, rank<10>{}));
161+
} size;
162+
163+
/**
164+
* @brief Obtain the size type of the given range
165+
*/
166+
template <typename R>
167+
using range_size_t = decltype(size(std::declval<R&>()));
168+
169+
/**
170+
* @brief Obtain the size of the given range as a signed integer type
171+
*/
172+
static constexpr struct _ssize_fn {
173+
template <typename R,
174+
typename Unsigned = range_size_t<R>,
175+
typename Signed = make_signed_t<Unsigned>,
176+
typename RetDiff =
177+
conditional_t<(sizeof(Signed) > sizeof(std::ptrdiff_t)), Signed, std::ptrdiff_t>>
178+
constexpr auto operator()(R&& rng) const bsoncxx_returns(static_cast<RetDiff>(size(rng)));
179+
} ssize;
180+
181+
/**
182+
* @brief Obtain the difference type of the given range
183+
*/
184+
template <typename R>
185+
using range_difference_t = iter_difference_t<iterator_t<R>>;
186+
187+
/**
188+
* @brief Obtain a pointer-to-data for the given `rng`.
189+
*
190+
* Returns the first valid of:
191+
* - `rng.data()` if such expression yields a pointer type
192+
* - `data(rng)` if such expression is visible via ADL and returns a pointer type
193+
* - `to_address(begin(rng))` if such expression is valid and iterator_t<R> is
194+
* a contiguous_iterator.
195+
*/
196+
static constexpr struct _data_fn {
197+
template <typename R>
198+
static constexpr auto impl(R&& rng, rank<2>) bsoncxx_returns(_decay_copy_pointer(rng.data()));
199+
200+
template <typename R, requires_t<int, is_contiguous_iterator<iterator_t<R>>> = 0>
201+
static constexpr auto impl(R&& rng, rank<1>) bsoncxx_returns(to_address(begin(rng)));
202+
203+
template <typename R>
204+
constexpr auto operator()(R&& rng) const bsoncxx_returns((impl)(rng, rank<10>{}));
205+
} data;
206+
207+
/**
208+
* @brief Get the type returned by data(R&), if valid
209+
*/
210+
template <typename R>
211+
using range_data_t = decltype(data(std::declval<R&>()));
212+
213+
/**
214+
* @brief Get the value type of the range
215+
*
216+
* Equivalent: iter_value_t<iterator_t<R>>
217+
*/
218+
template <typename R>
219+
using range_value_t = iter_value_t<iterator_t<R>>;
220+
221+
template <typename R>
222+
using range_concept_t = iterator_concept_t<iterator_t<R>>;
223+
224+
/**
225+
* @brief Trait detects if the given type is a range
226+
*/
227+
template <typename R>
228+
struct is_range : conjunction<is_detected<iterator_t, R>, is_detected<sentinel_t, R>> {};
229+
230+
/**
231+
* @brief Detect if the given range is contiguous-ish.
232+
*
233+
* This is not quite like ranges::contiguous_range, as it does not require that
234+
* the iterator be configuous. It only looks for a valid data(R) and size(R).
235+
* Without stdlib support, we cannot fully detect contiguous_iterators, but we
236+
* want to be able to support, basic_string<C>, vector<T>, array<T>, etc, which
237+
* have .data() and .size()
238+
*/
239+
template <typename R>
240+
struct is_contiguous_range
241+
: conjunction<is_range<R>, is_detected<range_data_t, R>, is_detected<range_size_t, R>> {};
242+
243+
} // namespace detail
244+
} // namespace v_noabi
245+
} // namespace bsoncxx
246+
247+
#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)