@auto_js/utility 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +2 -0
- package/functional/functional.cc +58 -26
- package/functional/move_only_function.cc +95 -0
- package/package.json +1 -1
- package/type_traits/type_traits.cc +45 -0
- package/utility/constant_wrapper.cc +1 -1
- package/utility/ranges.cc +2 -2
- package/utility/sequence.cc +22 -0
- package/utility/string.cc +20 -0
- package/utility/utility.cc +1 -16
package/CMakeLists.txt
CHANGED
|
@@ -19,6 +19,7 @@ target_sources(utility_js
|
|
|
19
19
|
functional/function_constant.cc
|
|
20
20
|
functional/function_ref.cc
|
|
21
21
|
functional/functional.cc
|
|
22
|
+
functional/move_only_function.cc
|
|
22
23
|
functional/regular_return.cc
|
|
23
24
|
memory/autorelease_pool.cc
|
|
24
25
|
memory/comparator.cc
|
|
@@ -38,6 +39,7 @@ target_sources(utility_js
|
|
|
38
39
|
utility/facade.cc
|
|
39
40
|
utility/hash.cc
|
|
40
41
|
utility/ranges.cc
|
|
42
|
+
utility/sequence.cc
|
|
41
43
|
utility/string.cc
|
|
42
44
|
utility/utility.cc
|
|
43
45
|
utility/variant.cc
|
package/functional/functional.cc
CHANGED
|
@@ -3,6 +3,7 @@ export import :functional.bind;
|
|
|
3
3
|
export import :functional.elide;
|
|
4
4
|
export import :functional.function_constant;
|
|
5
5
|
export import :functional.function_ref;
|
|
6
|
+
export import :functional.move_only_function;
|
|
6
7
|
export import :functional.regular_return;
|
|
7
8
|
import std;
|
|
8
9
|
|
|
@@ -14,32 +15,6 @@ struct overloaded : Visitors... {
|
|
|
14
15
|
using Visitors::operator()...;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
// Holder for functions in libc++ which doesn't support std::move_only_function
|
|
18
|
-
#if defined(__cpp_lib_move_only_function) && !defined(DEBUG)
|
|
19
|
-
|
|
20
|
-
export template <class Signature>
|
|
21
|
-
using maybe_move_only_function = std::move_only_function<Signature>;
|
|
22
|
-
|
|
23
|
-
export template <class Fn>
|
|
24
|
-
auto make_indirect_moveable_function(Fn fn) {
|
|
25
|
-
return fn;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
#else
|
|
29
|
-
|
|
30
|
-
export template <class Signature>
|
|
31
|
-
using maybe_move_only_function = std::function<remove_function_cvref_t<Signature>>;
|
|
32
|
-
|
|
33
|
-
export template <class Fn>
|
|
34
|
-
auto make_indirect_moveable_function(Fn&& fn) {
|
|
35
|
-
auto fn_ptr = std::make_shared<std::remove_cvref_t<Fn>>(std::forward<Fn>(fn));
|
|
36
|
-
return [ = ](auto&&... args) -> decltype(auto) {
|
|
37
|
-
return (*fn_ptr)(std::forward<decltype(args)>(args)...);
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
#endif
|
|
42
|
-
|
|
43
18
|
// Hide `-Wunused-value` warnings
|
|
44
19
|
export constexpr auto unused = [](auto&& /*nothing*/) -> void {};
|
|
45
20
|
|
|
@@ -63,6 +38,46 @@ constexpr auto invoke_this_as(auto&& func, auto&&... args)
|
|
|
63
38
|
return std::forward<decltype(func)>(func).InvokeAsType_::operator()(std::forward<decltype(args)>(args)...);
|
|
64
39
|
}
|
|
65
40
|
|
|
41
|
+
// Invoke the given function with each value in a pack. The first parameter is a value and the
|
|
42
|
+
// second parameter is a continuation thunk. If it reaches the end it will invoke the function with
|
|
43
|
+
// no parameters, returning that result.
|
|
44
|
+
export constexpr auto template_traverse(auto values_pack, const auto& invoke) -> decltype(auto) {
|
|
45
|
+
const auto fold = util::overloaded{
|
|
46
|
+
[ & ]() -> decltype(auto) { return invoke(); },
|
|
47
|
+
[ & ](this const auto& self, auto value, auto... values) -> decltype(auto) {
|
|
48
|
+
return invoke(value, [ & ]() -> decltype(auto) { return self(values...); });
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
const auto [... values ] = values_pack;
|
|
52
|
+
return fold(values...);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Fold operation for template packs. If the pack is empty then `identity` is returned. Otherwise
|
|
56
|
+
// `identity` is ignored.
|
|
57
|
+
export constexpr auto template_fold(auto values_pack, auto identity, const auto& invoke) -> decltype(auto) {
|
|
58
|
+
const auto fold = util::overloaded{
|
|
59
|
+
[ & ]() -> decltype(auto) { return identity; },
|
|
60
|
+
[ & ](auto left) -> decltype(auto) { return left; },
|
|
61
|
+
[ & ](this const auto& self, auto left, auto right, auto... values) -> decltype(auto) {
|
|
62
|
+
return self(invoke(left, right), values...);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
const auto [... values ] = values_pack;
|
|
66
|
+
return fold(values...);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Template pack reducer
|
|
70
|
+
export constexpr auto template_reduce(auto values_pack, auto accumulator, const auto& invoke) -> decltype(auto) {
|
|
71
|
+
const auto reduce = util::overloaded{
|
|
72
|
+
[ & ](auto accumulator) -> decltype(auto) { return accumulator; },
|
|
73
|
+
[ & ](this const auto& self, auto accumulator, auto value, auto... values) -> decltype(auto) {
|
|
74
|
+
return self(invoke(accumulator, value), values...);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
const auto [... values ] = values_pack;
|
|
78
|
+
return reduce(accumulator, values...);
|
|
79
|
+
};
|
|
80
|
+
|
|
66
81
|
// Invoke the given function with the constant expression matching a runtime value.
|
|
67
82
|
export constexpr auto template_switch(const auto& value, auto case_pack, auto invoke) -> decltype(auto) {
|
|
68
83
|
auto dispatch = [ & ](this const auto& self, auto case_, auto... cases) -> decltype(auto) {
|
|
@@ -82,6 +97,23 @@ export constexpr auto template_switch(const auto& value, auto case_pack, auto in
|
|
|
82
97
|
return dispatch(cases...);
|
|
83
98
|
}
|
|
84
99
|
|
|
100
|
+
// It seems like this should work but clang 22.1.6 throws this error:
|
|
101
|
+
// /workspace/packages/auto_js/js/assertions.cc:37:15: error: static assertion expression is not an integral constant expression
|
|
102
|
+
// 37 | static_assert(transfer<double>(std::variant<int, double>{1.1}) == 1.1);
|
|
103
|
+
// | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
104
|
+
// /workspace/packages/utility/functional/functional.cc:73:11: note: subobject of type 'const util::overloaded<(lambda at /workspace/packages/utility/functional/functional.cc:85:4), (lambda at /workspace/packages/utility/functional/functional.cc:86:4)> &const' is not initialized
|
|
105
|
+
// 73 | return invoke(value, [ & ]() -> decltype(auto) { return self(values...); });
|
|
106
|
+
//
|
|
107
|
+
// export constexpr auto template_switch(const auto& value, auto case_pack, auto invoke) -> decltype(auto) {
|
|
108
|
+
// return template_traverse(
|
|
109
|
+
// case_pack,
|
|
110
|
+
// util::overloaded{
|
|
111
|
+
// [ & ](auto case_, auto next) -> decltype(auto) { return value == case_ ? invoke(case_) : next(); },
|
|
112
|
+
// [ & ]() -> decltype(auto) { return invoke(); },
|
|
113
|
+
// }
|
|
114
|
+
// );
|
|
115
|
+
// }
|
|
116
|
+
|
|
85
117
|
// libc++'s `std::reference_wrapper` causes a template instantiation while looking for
|
|
86
118
|
// `result_type`. That breaks our transfer machinery.
|
|
87
119
|
export template <class Type>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export module util:functional.move_only_function;
|
|
2
|
+
import :type_traits;
|
|
3
|
+
import std;
|
|
4
|
+
|
|
5
|
+
namespace util {
|
|
6
|
+
|
|
7
|
+
#if defined(__cpp_lib_move_only_function)
|
|
8
|
+
|
|
9
|
+
export template <class Signature>
|
|
10
|
+
using move_only_function = std::move_only_function<Signature>;
|
|
11
|
+
|
|
12
|
+
#else
|
|
13
|
+
|
|
14
|
+
// Virtual base class. There must be two, one for each const/non-const signature because virtual
|
|
15
|
+
// function cannot have explicit this or requirement clauses.
|
|
16
|
+
template <class CvRef, class Signature>
|
|
17
|
+
struct move_only_function_virtual;
|
|
18
|
+
|
|
19
|
+
template <class... Args, bool Nx, class Result>
|
|
20
|
+
struct move_only_function_virtual<int, auto(Args...) noexcept(Nx)->Result> {
|
|
21
|
+
virtual constexpr ~move_only_function_virtual() = default;
|
|
22
|
+
virtual constexpr auto operator()(Args... args) noexcept(Nx) -> Result = 0;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
template <class... Args, bool Nx, class Result>
|
|
26
|
+
struct move_only_function_virtual<const int, auto(Args...) noexcept(Nx)->Result> {
|
|
27
|
+
virtual constexpr ~move_only_function_virtual() = default;
|
|
28
|
+
virtual constexpr auto operator()(Args... args) const noexcept(Nx) -> Result = 0;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Instance per underlying function type
|
|
32
|
+
template <class CvRef, class Signature, class Function>
|
|
33
|
+
class move_only_function_instance;
|
|
34
|
+
|
|
35
|
+
template <class... Args, bool Nx, class Result, class Function>
|
|
36
|
+
class move_only_function_instance<int, auto(Args...) noexcept(Nx)->Result, Function>
|
|
37
|
+
: public move_only_function_virtual<int, auto(Args...) noexcept(Nx)->Result> {
|
|
38
|
+
public:
|
|
39
|
+
explicit constexpr move_only_function_instance(Function function) : function_{std::move(function)} {}
|
|
40
|
+
virtual constexpr auto operator()(Args... args) noexcept(Nx) -> Result { return function_(std::forward<Args>(args)...); }
|
|
41
|
+
|
|
42
|
+
private:
|
|
43
|
+
Function function_;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
template <class... Args, bool Nx, class Result, class Function>
|
|
47
|
+
class move_only_function_instance<const int, auto(Args...) noexcept(Nx)->Result, Function>
|
|
48
|
+
: public move_only_function_virtual<const int, auto(Args...) noexcept(Nx)->Result> {
|
|
49
|
+
public:
|
|
50
|
+
explicit constexpr move_only_function_instance(Function function) : function_{std::move(function)} {}
|
|
51
|
+
virtual constexpr auto operator()(Args... args) const noexcept(Nx) -> Result { return function_(std::forward<Args>(args)...); }
|
|
52
|
+
|
|
53
|
+
private:
|
|
54
|
+
Function function_;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// std::move_only_function polyfill implementation (separated to split out cvref from signature)
|
|
58
|
+
template <class CvRef, class Signature>
|
|
59
|
+
class move_only_function_handle;
|
|
60
|
+
|
|
61
|
+
template <class Signature>
|
|
62
|
+
using move_only_function_handle_for =
|
|
63
|
+
move_only_function_handle<util::extract_function_cvref_t<Signature>, remove_function_cvref_t<Signature>>;
|
|
64
|
+
|
|
65
|
+
template <class CvRef, class... Args, bool Nx, class Result>
|
|
66
|
+
class move_only_function_handle<CvRef, auto(Args...) noexcept(Nx)->Result> {
|
|
67
|
+
public:
|
|
68
|
+
move_only_function_handle() = default;
|
|
69
|
+
|
|
70
|
+
template <class Function>
|
|
71
|
+
// NOLINTNEXTLINE(google-explicit-constructor)
|
|
72
|
+
constexpr move_only_function_handle(Function function) :
|
|
73
|
+
instance_{std::make_unique<move_only_function_instance<CvRef, auto(Args...) noexcept(Nx)->Result, Function>>(std::move(function))} {}
|
|
74
|
+
constexpr auto operator()(this util::apply_cvref_t<CvRef, move_only_function_handle>& self, Args... args) noexcept(Nx) -> Result {
|
|
75
|
+
return (*self.instance_)(std::forward<Args>(args)...);
|
|
76
|
+
}
|
|
77
|
+
constexpr explicit operator bool() const { return bool{instance_}; }
|
|
78
|
+
|
|
79
|
+
private:
|
|
80
|
+
std::unique_ptr<move_only_function_virtual<CvRef, auto(Args...) noexcept(Nx)->Result>> instance_;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// std::move_only_function polyfill
|
|
84
|
+
export template <class Signature>
|
|
85
|
+
struct move_only_function : move_only_function_handle_for<Signature> {
|
|
86
|
+
move_only_function() = default;
|
|
87
|
+
using move_only_function_handle_for<Signature>::move_only_function_handle_for;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
template <class Function>
|
|
91
|
+
move_only_function(Function) -> move_only_function<util::function_signature_t<Function>>;
|
|
92
|
+
|
|
93
|
+
#endif
|
|
94
|
+
|
|
95
|
+
} // namespace util
|
package/package.json
CHANGED
|
@@ -3,3 +3,48 @@ export import :type_traits.function_traits;
|
|
|
3
3
|
export import :type_traits.transform;
|
|
4
4
|
export import :type_traits.type_of;
|
|
5
5
|
export import :type_traits.type_pack;
|
|
6
|
+
import std;
|
|
7
|
+
|
|
8
|
+
namespace util {
|
|
9
|
+
|
|
10
|
+
// Check if casting from one numeric to another can be done without loss. It is probably missing a
|
|
11
|
+
// plethora of exotic cases but I think this covers the fundamentals.
|
|
12
|
+
export template <class From, class To>
|
|
13
|
+
struct safe_numeric_conversion {
|
|
14
|
+
using limits_from = std::numeric_limits<From>;
|
|
15
|
+
using limits_to = std::numeric_limits<To>;
|
|
16
|
+
constexpr static auto value =
|
|
17
|
+
// Check signed / floating point compatibility
|
|
18
|
+
(limits_to::is_signed || !limits_from::is_signed) &&
|
|
19
|
+
(limits_from::is_integer || !limits_to::is_integer) &&
|
|
20
|
+
// Check precision
|
|
21
|
+
(limits_to::digits >= limits_from::digits) &&
|
|
22
|
+
// Check range
|
|
23
|
+
(limits_from::lowest() >= limits_to::lowest()) &&
|
|
24
|
+
(limits_from::max() <= limits_to::max());
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export template <class From, class To>
|
|
28
|
+
constexpr auto safe_numeric_conversion_v = safe_numeric_conversion<From, To>::value;
|
|
29
|
+
|
|
30
|
+
static_assert(safe_numeric_conversion<std::int32_t, std::int64_t>::value);
|
|
31
|
+
static_assert(!safe_numeric_conversion<std::int32_t, std::uint64_t>::value);
|
|
32
|
+
static_assert(!safe_numeric_conversion<std::int64_t, double>::value);
|
|
33
|
+
static_assert(safe_numeric_conversion<std::int32_t, double>::value);
|
|
34
|
+
static_assert(safe_numeric_conversion<float, double>::value);
|
|
35
|
+
static_assert(!safe_numeric_conversion<double, float>::value);
|
|
36
|
+
|
|
37
|
+
// Resolves to the type of every type in the pack. If any type differs then it is ill-formed.
|
|
38
|
+
template <class... Type>
|
|
39
|
+
struct identical_type;
|
|
40
|
+
|
|
41
|
+
template <class... Type>
|
|
42
|
+
using identical_type_t = identical_type<Type...>::type;
|
|
43
|
+
|
|
44
|
+
template <class Type>
|
|
45
|
+
struct identical_type<Type> : std::type_identity<Type> {};
|
|
46
|
+
|
|
47
|
+
template <class Type, class... Rest>
|
|
48
|
+
struct identical_type<Type, Type, Rest...> : identical_type<Type, Rest...> {};
|
|
49
|
+
|
|
50
|
+
} // namespace util
|
|
@@ -7,7 +7,7 @@ import std;
|
|
|
7
7
|
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-array-to-pointer-decay, modernize-avoid-c-arrays)
|
|
8
8
|
namespace util {
|
|
9
9
|
|
|
10
|
-
template <class Type>
|
|
10
|
+
export template <class Type>
|
|
11
11
|
struct fixed_value {
|
|
12
12
|
using type = Type;
|
|
13
13
|
// NOLINTNEXTLINE(google-explicit-constructor)
|
package/utility/ranges.cc
CHANGED
|
@@ -55,11 +55,11 @@ export constexpr auto into_range(meta_range auto&& range) {
|
|
|
55
55
|
// Returns either a `std::tuple<size_type>` with the range's size, or `std::tuple<>` if it can't be
|
|
56
56
|
// determined in constant time.
|
|
57
57
|
export template <class Type>
|
|
58
|
-
constexpr auto maybe_range_size(const Type& range) {
|
|
58
|
+
constexpr auto maybe_range_size(const Type& range, auto... if_not) {
|
|
59
59
|
if constexpr (std::ranges::sized_range<Type>) {
|
|
60
60
|
return std::tuple{std::ranges::size(range)};
|
|
61
61
|
} else {
|
|
62
|
-
return std::tuple{};
|
|
62
|
+
return std::tuple{if_not...};
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export module util:utility.sequence;
|
|
2
|
+
import std;
|
|
3
|
+
|
|
4
|
+
namespace util {
|
|
5
|
+
|
|
6
|
+
// Return a sequence of index constants
|
|
7
|
+
export template <std::size_t Size>
|
|
8
|
+
constexpr auto sequence = []() consteval -> auto {
|
|
9
|
+
// With C++26 P2686 we can do constexpr decomposition. So instead of the `tuple` of
|
|
10
|
+
// `integral_constants` it can be an array of `std::size_t`.
|
|
11
|
+
// https://clang.llvm.org/cxx_status.html
|
|
12
|
+
|
|
13
|
+
// std::array<std::size_t, Size> result{};
|
|
14
|
+
// std::ranges::copy(std::ranges::views::iota(std::size_t{0}, Size), result.begin());
|
|
15
|
+
// return result;
|
|
16
|
+
|
|
17
|
+
return []<std::size_t... Index>(std::index_sequence<Index...> /*sequence*/) consteval {
|
|
18
|
+
return std::tuple{std::integral_constant<std::size_t, Index>{}...};
|
|
19
|
+
}(std::make_index_sequence<Size>());
|
|
20
|
+
}();
|
|
21
|
+
|
|
22
|
+
} // namespace util
|
package/utility/string.cc
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export module util:utility.string;
|
|
2
|
+
import :type_traits;
|
|
2
3
|
import :utility.constant_wrapper;
|
|
4
|
+
import :utility.sequence;
|
|
3
5
|
import std;
|
|
4
6
|
|
|
5
7
|
namespace util {
|
|
@@ -16,6 +18,24 @@ consteval auto make_consteval_string_view(constant_wrapper<Value> /*cw*/) -> std
|
|
|
16
18
|
return make_consteval_string_view(constant_wrapper<Value>::value);
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
// Concatenate a pack of util::cw<"xyz">
|
|
22
|
+
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-array-to-pointer-decay, cppcoreguidelines-pro-bounds-pointer-arithmetic, modernize-avoid-c-arrays)
|
|
23
|
+
export template <auto... Strings>
|
|
24
|
+
consteval auto consteval_strcat(constant_wrapper<Strings>... /*strings*/) {
|
|
25
|
+
using char_type = identical_type_t<std::remove_extent_t<typename constant_wrapper<Strings>::value_type>...>;
|
|
26
|
+
constexpr auto chars = [ & ]() -> auto {
|
|
27
|
+
constexpr auto size = (... + (std::extent_v<typename constant_wrapper<Strings>::value_type> - 1));
|
|
28
|
+
std::array<char_type, size + 1> chars{};
|
|
29
|
+
std::size_t offset = 0;
|
|
30
|
+
(..., (std::ranges::copy(Strings.value, chars.data() + offset), offset += std::extent_v<typename constant_wrapper<Strings>::value_type> - 1));
|
|
31
|
+
return chars;
|
|
32
|
+
}();
|
|
33
|
+
auto [... indices ] = sequence<chars.size()>;
|
|
34
|
+
constexpr char_type string[ chars.size() ] = {chars[ indices ]...};
|
|
35
|
+
return cw<string>;
|
|
36
|
+
}
|
|
37
|
+
// NOLINTEND(cppcoreguidelines-pro-bounds-array-to-pointer-decay, cppcoreguidelines-pro-bounds-pointer-arithmetic, modernize-avoid-c-arrays)
|
|
38
|
+
|
|
19
39
|
// Helper for `codepoint_char_sequence` which stores an array of the most characters it will take to
|
|
20
40
|
// represent a codepoint in a given character type.
|
|
21
41
|
template <class Char, std::size_t Extent>
|
package/utility/utility.cc
CHANGED
|
@@ -4,6 +4,7 @@ export import :utility.covariant_value;
|
|
|
4
4
|
export import :utility.facade;
|
|
5
5
|
export import :utility.hash;
|
|
6
6
|
export import :utility.ranges;
|
|
7
|
+
export import :utility.sequence;
|
|
7
8
|
export import :utility.string;
|
|
8
9
|
// export import :utility.variant;
|
|
9
10
|
import std;
|
|
@@ -31,22 +32,6 @@ export class non_moveable {
|
|
|
31
32
|
auto operator=(const non_moveable&) = delete;
|
|
32
33
|
};
|
|
33
34
|
|
|
34
|
-
// Return a sequence of index constants
|
|
35
|
-
export template <std::size_t Size>
|
|
36
|
-
constexpr auto sequence = []() consteval -> auto {
|
|
37
|
-
// With C++26 P2686 we can do constexpr decomposition. So instead of the `tuple` of
|
|
38
|
-
// `integral_constants` it can be an array of `std::size_t`.
|
|
39
|
-
// https://clang.llvm.org/cxx_status.html
|
|
40
|
-
|
|
41
|
-
// std::array<std::size_t, Size> result{};
|
|
42
|
-
// std::ranges::copy(std::ranges::views::iota(std::size_t{0}, Size), result.begin());
|
|
43
|
-
// return result;
|
|
44
|
-
|
|
45
|
-
return []<std::size_t... Index>(std::index_sequence<Index...> /*sequence*/) consteval {
|
|
46
|
-
return std::tuple{std::integral_constant<std::size_t, Index>{}...};
|
|
47
|
-
}(std::make_index_sequence<Size>());
|
|
48
|
-
}();
|
|
49
|
-
|
|
50
35
|
// Checked container / range access for types which don't have `.at()`
|
|
51
36
|
export auto at(auto&& range, std::size_t index) -> decltype(auto) {
|
|
52
37
|
if (range.size() <= index) {
|