sparrow 2.5.0
C++20 idiomatic APIs for the Apache Arrow Columnar Format
Loading...
Searching...
No Matches
nullable.hpp
Go to the documentation of this file.
1// Copyright 2024 Man Group Operations Limited
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#pragma once
16
17#include <algorithm>
18#include <compare>
19#include <concepts>
20#include <exception>
21#if __has_include(<format>)
22# include <format>
23# include <ostream>
24#endif
25#include <type_traits>
26#include <variant>
27
30
31
32#if defined(SPARROW_CONSTEXPR)
33# error "SPARROW_CONSTEXPR already defined"
34#endif
35
36// clang workaround: clang instantiates the constructor in SFINAE context,
37// which is incompatible with the implementation of standard libraries which
38// are not libc++. This leads to wrong compilation errors. Making the constructor
39// not constexpr prevents the compiler from instantiating it.
40#if defined(__clang__) && not defined(_LIBCPP_VERSION)
41# define SPARROW_CONSTEXPR
42#else
43# define SPARROW_CONSTEXPR constexpr
44#endif
45
46namespace sparrow
47{
48 template <class T, mpl::boolean_like B>
49 class nullable;
50
51 template <class T>
52 struct is_nullable : std::false_type
53 {
54 };
55
56 template <class T, mpl::boolean_like B>
57 struct is_nullable<nullable<T, B>> : std::true_type
58 {
59 };
60
61 template <class T>
62 inline constexpr bool is_nullable_v = is_nullable<T>::value;
63
64 template <class N, class T>
65 concept nullable_of = is_nullable_v<N> && std::same_as<typename N::stored_value_type, T>;
66
67 template <class N, class T>
69 && std::convertible_to<typename N::stored_value_type, T>;
70
71 /*
72 * Matches a range of nullables objects.
73 *
74 * A range is considered a range of nullables if it is a range and its value type is a nullable.
75 *
76 * @tparam RangeOfNullables The range to check.
77 */
78 template <class RangeOfNullables>
79 concept range_of_nullables = std::ranges::range<RangeOfNullables>
81
82 // TODO: nullable_get_fn should not be required since nullable implements all the required overloads
83 // of get(), but some compilers fail to build without it.
85 {
86 template <class N>
88 constexpr decltype(auto) operator()(N&& nullable_value) const
89 {
90 if constexpr (std::is_lvalue_reference_v<N&&>)
91 {
92 return std::forward<N>(nullable_value).get();
93 }
94 else
95 {
96 using value_type = std::remove_cvref_t<decltype(std::forward<N>(nullable_value).get())>;
97 return value_type(std::forward<N>(nullable_value).get());
98 }
99 }
100 };
101
102 inline constexpr nullable_get_fn nullable_get{};
103
104 /*
105 * Default traits for the nullable class. These traits should be specialized
106 * for proxy classes whose reference and const_reference types are not
107 * defined as usual. For instance:
108 *
109 * @code{.cpp}
110 * struct nullable_traits<string_proxy>
111 * {
112 * using value_type = std::string;
113 * using reference = string_proxy;
114 * using const_reference = std::string_view;
115 * using rvalue_reverence = std::string&&;
116 * using const_rvalue_reference = const std::string&&;
117 * };
118 * @endcode
119 */
120 template <class T>
122 {
123 using value_type = T;
124 using reference = std::add_lvalue_reference_t<value_type>;
125 using const_reference = std::add_lvalue_reference_t<std::add_const_t<value_type>>;
128 };
129
130 template <class T>
132 {
133 using value_type = T;
134 using reference = std::add_lvalue_reference_t<value_type>;
135 using const_reference = std::add_lvalue_reference_t<std::add_const_t<value_type>>;
138 };
139
146 class bad_nullable_access : public std::exception
147 {
148 public:
149
150 bad_nullable_access() noexcept = default;
151 bad_nullable_access(const bad_nullable_access&) noexcept = default;
152 bad_nullable_access& operator=(const bad_nullable_access&) noexcept = default;
153
161 [[nodiscard]] const char* what() const noexcept override
162 {
163 return message;
164 }
165
166 private:
167
168 static constexpr const char* message = "Invalid access to nullable underlying value";
169 };
170
179 {
183 constexpr explicit nullval_t(int)
184 {
185 }
186 };
187
200 inline constexpr nullval_t nullval(0);
201
202 namespace impl
203 {
207 template <class T, class TArgs, class U, class UArgs>
208 concept both_constructible_from_cref = std::constructible_from<T, mpl::add_const_lvalue_reference_t<TArgs>>
209 and std::constructible_from<U, mpl::add_const_lvalue_reference_t<UArgs>>;
210
211 template <class To1, class From1, class To2, class From2>
212 concept both_convertible_from_cref = std::convertible_to<mpl::add_const_lvalue_reference_t<From1>, To1>
213 and std::convertible_to<mpl::add_const_lvalue_reference_t<From2>, To2>;
214
215 template <class T, class... Args>
216 concept constructible_from_one = (std::constructible_from<T, Args> || ...);
217
218 template <class T, class... Args>
219 concept convertible_from_one = (std::convertible_to<Args, T> || ...);
220
221 template <class T, class... Args>
223
224 template <class T, class Arg>
226
227 // We prefer std::is_assignable_v to std::assignable_from<To, From> because
228 // std::assignable_from requires the existence of an implicit conversion
229 // from From to To
230 template <class To, class... Args>
231 concept assignable_from_one = (std::is_assignable_v<std::add_lvalue_reference<To>, Args> && ...);
232
233 template <class T, class Arg>
235
236 template <class T, class U>
237 using conditional_ref_t = std::conditional_t<std::is_reference_v<T>, const std::decay_t<U>&, std::decay_t<U>&&>;
238
239 template <class T, class Targs, class U, class UArgs>
240 concept both_constructible_from_cond_ref = std::constructible_from<T, conditional_ref_t<T, Targs>>
241 and std::constructible_from<U, conditional_ref_t<U, UArgs>>;
242
243 template <class To1, class From1, class To2, class From2>
244 concept both_convertible_from_cond_ref = std::convertible_to<conditional_ref_t<To1, From1>, To1>
245 and std::convertible_to<conditional_ref_t<To2, From2>, To2>;
246
247 template <class To1, class From1, class To2, class From2>
248 concept both_assignable_from_cref = std::is_assignable_v<
249 std::add_lvalue_reference_t<To1>,
251 and std::is_assignable_v<
252 std::add_lvalue_reference_t<To2>,
254
255 template <class To1, class From1, class To2, class From2>
256 concept both_assignable_from_cond_ref = std::is_assignable_v<
257 std::add_lvalue_reference_t<To1>,
259 and std::is_assignable_v<
260 std::add_lvalue_reference_t<To2>,
262 }
263
310 template <class T, mpl::boolean_like B = bool>
312 {
313 public:
314
329
341 template <std::default_initializable U = T, std::default_initializable BB = B>
342 constexpr nullable() noexcept
343 : m_value()
344 , m_null_flag(false)
345 {
346 }
347
359 template <std::default_initializable U = T, std::default_initializable BB = B>
360 constexpr nullable(nullval_t) noexcept
361 : m_value()
362 , m_null_flag(false)
363 {
364 }
365
378 template <class U>
379 requires(not std::same_as<self_type, std::decay_t<U>> and std::constructible_from<T, U &&>)
380 explicit(not std::convertible_to<U&&, T>) constexpr nullable(U&& value) noexcept(
381 noexcept(T(std::declval<U>()))
382 )
383 : m_value(std::forward<U>(value))
384 , m_null_flag(true)
385 {
386 }
387
398 constexpr nullable(const self_type& rhs) = default;
399
414 template <class TO, mpl::boolean_like BO>
419 )
420 : m_value(rhs.get())
421 , m_null_flag(rhs.null_flag())
422 {
423 }
424
425#ifdef __clang__
446 template <class TO, mpl::boolean_like BO>
447 requires(impl::both_constructible_from_cref<T, TO, B, BO> and std::same_as<std::decay_t<T>, bool>)
449 const nullable<TO, BO>& rhs
450 )
451 : m_value(rhs.get())
452 , m_null_flag(rhs.null_flag())
453 {
454 }
455#endif
456
467 constexpr nullable(self_type&& rhs) noexcept = default;
468
483 template <class TO, mpl::boolean_like BO>
488 )
489 : m_value(std::move(rhs).get())
490 , m_null_flag(std::move(rhs).null_flag())
491 {
492 }
493
494#ifdef __clang__
516 template <class TO, mpl::boolean_like BO>
518 and std::same_as<std::decay_t<T>, bool>)
521 )
522 : m_value(std::move(rhs).get())
523 , m_null_flag(std::move(rhs).null_flag())
524 {
525 }
526#endif
527
539 : m_value(std::move(value))
540 , m_null_flag(std::move(null_flag))
541 {
542 }
543
554 constexpr nullable(std::add_lvalue_reference_t<T> value, std::add_lvalue_reference_t<B> null_flag)
555 : m_value(value)
556 , m_null_flag(null_flag)
557 {
558 }
559
569 constexpr nullable(value_type&& value, std::add_lvalue_reference_t<B> null_flag)
570 : m_value(std::move(value))
571 , m_null_flag(null_flag)
572 {
573 }
574
584 constexpr nullable(std::add_lvalue_reference_t<T> value, flag_type&& null_flag)
585 : m_value(value)
586 , m_null_flag(std::move(null_flag))
587 {
588 }
589
599 template <class U, class V>
600 requires(std::same_as<std::remove_cvref_t<U>, T> && std::same_as<std::remove_cvref_t<V>, B>
601 && std::is_const_v<std::remove_reference_t<U>>
602 && not std::is_const_v<std::remove_reference_t<V>> && not std::is_reference_v<T>
603 && not std::is_reference_v<B> && std::is_lvalue_reference_v<U &&>
604 && std::is_lvalue_reference_v<V &&>)
605 constexpr nullable(U& value, V& null_flag)
606 : m_value(value)
607 , m_null_flag(null_flag)
608 {
609 }
610
624 template <class U, class V>
625 requires(std::same_as<std::remove_cvref_t<U>, T> && std::same_as<std::remove_cvref_t<V>, B>
626 && not std::is_reference_v<T> && not std::is_reference_v<B>
627 && (std::is_const_v<std::remove_reference_t<U>>
628 || std::is_const_v<std::remove_reference_t<V>>) )
629 constexpr nullable(U&& value, V&& null_flag)
630 : m_value(std::forward<U>(value))
631 , m_null_flag(std::forward<V>(null_flag))
632 {
633 }
634
645 constexpr self_type& operator=(nullval_t) noexcept
646 {
647 m_null_flag = false;
648 return *this;
649 }
650
664 template <class TO>
665 requires(not std::same_as<self_type, TO> and std::assignable_from<std::add_lvalue_reference_t<T>, TO>)
666 constexpr self_type& operator=(TO&& rhs) noexcept
667 {
668 m_value = std::forward<TO>(rhs);
669 m_null_flag = true;
670 return *this;
671 }
672
684 constexpr self_type& operator=(const self_type& rhs) noexcept
685 {
686 m_value = rhs.get();
687 m_null_flag = rhs.null_flag();
688 return *this;
689 }
690
706 template <class TO, mpl::boolean_like BO>
707 requires(
711 )
712 constexpr self_type& operator=(const nullable<TO, BO>& rhs) noexcept
713 {
714 m_value = rhs.get();
715 m_null_flag = rhs.null_flag();
716 return *this;
717 }
718
730 constexpr self_type& operator=(self_type&& rhs) noexcept
731 {
732 m_value = std::move(rhs).get();
733 m_null_flag = std::move(rhs).null_flag();
734 return *this;
735 }
736
752 template <class TO, mpl::boolean_like BO>
753 requires(
757 )
758 constexpr self_type& operator=(nullable<TO, BO>&& rhs) noexcept
759 {
760 m_value = std::move(rhs).get();
761 m_null_flag = std::move(rhs).null_flag();
762 return *this;
763 }
764
773 constexpr explicit operator bool() const noexcept;
774
783 [[nodiscard]] constexpr bool has_value() const noexcept;
784
793 [[nodiscard]] constexpr flag_reference null_flag() & noexcept;
794
802 [[nodiscard]] constexpr flag_const_reference null_flag() const& noexcept;
803
812 [[nodiscard]] constexpr flag_rvalue_reference null_flag() && noexcept;
813
822 [[nodiscard]] constexpr flag_const_rvalue_reference null_flag() const&& noexcept;
823
833 [[nodiscard]] constexpr reference get() & noexcept;
834
843 [[nodiscard]] constexpr const_reference get() const& noexcept;
844
853 [[nodiscard]] constexpr rvalue_reference get() && noexcept;
854
863 [[nodiscard]] constexpr const_rvalue_reference get() const&& noexcept;
864
875 [[nodiscard]] constexpr reference value() &;
876
887 [[nodiscard]] constexpr const_reference value() const&;
888
900 [[nodiscard]] constexpr rvalue_reference value() &&;
901
912 [[nodiscard]] constexpr const_rvalue_reference value() const&&;
913
925 template <class U>
926 [[nodiscard]] constexpr value_type value_or(U&& default_value) const&;
927
939 template <class U>
940 [[nodiscard]] constexpr value_type value_or(U&& default_value) &&;
941
950 void swap(self_type& other) noexcept;
951
959 void reset() noexcept;
960
961 private:
962
968 void throw_if_null() const;
969
970 T m_value;
971 B m_null_flag;
972
973 template <class TO, mpl::boolean_like BO>
974 friend class nullable;
975 };
976
988 template <class T, class B>
989 constexpr void swap(nullable<T, B>& lhs, nullable<T, B>& rhs) noexcept;
990
1002 template <class T, class B>
1003 constexpr bool operator==(const nullable<T, B>& lhs, nullval_t) noexcept;
1004
1017 template <class T, mpl::boolean_like B>
1018 constexpr std::strong_ordering operator<=>(const nullable<T, B>& lhs, nullval_t dummy) noexcept;
1019
1033 template <class T, class B, class U>
1034 requires(!is_nullable_v<U> && mpl::weakly_equality_comparable_with<T, U>)
1035 constexpr bool operator==(const nullable<T, B>& lhs, const U& rhs) noexcept;
1036
1051 template <class T, class B, class U>
1052 requires(!is_nullable_v<U> && std::three_way_comparable_with<U, T>)
1053 constexpr std::compare_three_way_result_t<T, U>
1054 operator<=>(const nullable<T, B>& lhs, const U& rhs) noexcept;
1055
1071 template <class T, class B, class U, class UB>
1072 requires(mpl::weakly_equality_comparable_with<T, U>)
1073 constexpr bool operator==(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept;
1074
1091 template <class T, class B, std::three_way_comparable_with<T> U, class UB>
1092 constexpr std::compare_three_way_result_t<T, U>
1093 operator<=>(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept;
1094
1107 template <class T, mpl::boolean_like B = bool>
1108 constexpr nullable<T, B> make_nullable(T&& value, B&& flag = true);
1109
1123 template <std::ranges::range R, typename T = typename std::ranges::range_value_t<R>::value_type>
1124 requires(nullable_of<std::ranges::range_value_t<R>, T>)
1125 constexpr void zero_null_values(R& range, const T& default_value = T{});
1126
1140 template <class... T>
1141 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1142 class nullable_variant : public std::variant<T...>
1143 {
1144 public:
1145
1146 using base_type = std::variant<T...>;
1147 using base_type::base_type;
1148
1149 template <class U, mpl::boolean_like B>
1150 requires((std::same_as<nullable<U, B>, T> || ...))
1151 constexpr nullable_variant(const nullable<U, B>& value)
1152 : base_type(std::in_place_type<nullable<U, B>>, value)
1153 {
1154 }
1155
1156 constexpr nullable_variant(const nullable_variant&) = default;
1157 constexpr nullable_variant(nullable_variant&&) noexcept = default;
1158
1168 constexpr nullable_variant& operator=(const nullable_variant&);
1169
1179 constexpr nullable_variant& operator=(nullable_variant&&) noexcept;
1180
1188 constexpr explicit operator bool() const;
1189
1197 constexpr bool has_value() const;
1198 };
1199
1200 template <class T>
1201 struct is_nullable_variant : std::false_type
1202 {
1203 };
1204
1205 template <class... T>
1206 struct is_nullable_variant<nullable_variant<T...>> : std::true_type
1207 {
1208 };
1209
1210 template <class T>
1212}
1213
1214namespace std
1215{
1216 namespace mpl = sparrow::mpl;
1217
1218 // Specialization of basic_common_reference for nullable proxies so
1219 // we can use ranges algorithm on iterators returning nullable
1220 template <class T, mpl::boolean_like TB, class U, mpl::boolean_like UB, template <class> class TQual, template <class> class UQual>
1221 requires std::common_reference_with<T, U> && std::common_reference_with<TB, UB>
1222 struct basic_common_reference<sparrow::nullable<T, TB>, sparrow::nullable<U, UB>, TQual, UQual>
1223 {
1224 using type = sparrow::
1225 nullable<std::common_reference_t<TQual<T>, UQual<U>>, std::common_reference_t<TQual<TB>, UQual<UB>>>;
1226 };
1227}
1228
1229namespace sparrow
1230{
1231 /***************************
1232 * nullable implementation *
1233 ***************************/
1234
1235 template <class T, mpl::boolean_like B>
1236 constexpr nullable<T, B>::operator bool() const noexcept
1237 {
1238 return m_null_flag;
1239 }
1240
1241 template <class T, mpl::boolean_like B>
1242 constexpr bool nullable<T, B>::has_value() const noexcept
1243 {
1244 return m_null_flag;
1245 }
1246
1247 template <class T, mpl::boolean_like B>
1248 constexpr auto nullable<T, B>::null_flag() & noexcept -> flag_reference
1249 {
1250 return m_null_flag;
1251 }
1252
1253 template <class T, mpl::boolean_like B>
1254 constexpr auto nullable<T, B>::null_flag() const& noexcept -> flag_const_reference
1255 {
1256 return m_null_flag;
1257 }
1258
1259 template <class T, mpl::boolean_like B>
1260 constexpr auto nullable<T, B>::null_flag() && noexcept -> flag_rvalue_reference
1261 {
1262 if constexpr (std::is_reference_v<B>)
1263 {
1264 return m_null_flag;
1265 }
1266 else
1267 {
1268 return flag_rvalue_reference(m_null_flag);
1269 }
1270 }
1271
1272 template <class T, mpl::boolean_like B>
1273 constexpr auto nullable<T, B>::null_flag() const&& noexcept -> flag_const_rvalue_reference
1274 {
1275 if constexpr (std::is_reference_v<B>)
1276 {
1277 return m_null_flag;
1278 }
1279 else
1280 {
1281 return flag_const_rvalue_reference(m_null_flag);
1282 }
1283 }
1284
1285 template <class T, mpl::boolean_like B>
1286 constexpr auto nullable<T, B>::get() & noexcept -> reference
1287 {
1288 return m_value;
1289 }
1290
1291 template <class T, mpl::boolean_like B>
1292 constexpr auto nullable<T, B>::get() const& noexcept -> const_reference
1293 {
1294 return m_value;
1295 }
1296
1297 template <class T, mpl::boolean_like B>
1298 constexpr auto nullable<T, B>::get() && noexcept -> rvalue_reference
1299 {
1300 if constexpr (std::is_reference_v<T>)
1301 {
1302 return m_value;
1303 }
1304 else
1305 {
1306 return rvalue_reference(m_value);
1307 }
1308 }
1309
1310 template <class T, mpl::boolean_like B>
1311 constexpr auto nullable<T, B>::get() const&& noexcept -> const_rvalue_reference
1312 {
1313 if constexpr (std::is_reference_v<T>)
1314 {
1315 return m_value;
1316 }
1317 else
1318 {
1319 return const_rvalue_reference(m_value);
1320 }
1321 }
1322
1323 template <class T, mpl::boolean_like B>
1324 constexpr auto nullable<T, B>::value() & -> reference
1325 {
1326 throw_if_null();
1327 return get();
1328 }
1329
1330 template <class T, mpl::boolean_like B>
1331 constexpr auto nullable<T, B>::value() const& -> const_reference
1332 {
1333 throw_if_null();
1334 return get();
1335 }
1336
1337 template <class T, mpl::boolean_like B>
1339 {
1340 throw_if_null();
1341 return std::move(*this).get();
1342 }
1343
1344 template <class T, mpl::boolean_like B>
1346 {
1347 throw_if_null();
1348 return std::move(*this).get();
1349 }
1350
1351 template <class T, mpl::boolean_like B>
1352 template <class U>
1353 constexpr auto nullable<T, B>::value_or(U&& default_value) const& -> value_type
1354 {
1355 return *this ? get() : value_type(std::forward<U>(default_value));
1356 }
1357
1358 template <class T, mpl::boolean_like B>
1359 template <class U>
1360 constexpr auto nullable<T, B>::value_or(U&& default_value) && -> value_type
1361 {
1362 return *this ? get() : value_type(std::forward<U>(default_value));
1363 }
1364
1365 template <class T, mpl::boolean_like B>
1366 void nullable<T, B>::swap(self_type& other) noexcept
1367 {
1368 using std::swap;
1369 swap(m_value, other.m_value);
1370 swap(m_null_flag, other.m_null_flag);
1371 }
1372
1373 template <class T, mpl::boolean_like B>
1375 {
1376 m_null_flag = false;
1377 }
1378
1379 template <class T, mpl::boolean_like B>
1380 void nullable<T, B>::throw_if_null() const
1381 {
1382 if (!m_null_flag)
1383 {
1384 throw bad_nullable_access{};
1385 }
1386 }
1387
1388 template <class T, class B>
1389 constexpr void swap(nullable<T, B>& lhs, nullable<T, B>& rhs) noexcept
1390 {
1391 lhs.swap(rhs);
1392 }
1393
1394 template <class T, class B>
1395 constexpr bool operator==(const nullable<T, B>& lhs, nullval_t) noexcept
1396 {
1397 return !lhs;
1398 }
1399
1400 template <class T, class B>
1401 constexpr std::strong_ordering operator<=>(const nullable<T, B>& lhs, nullval_t) noexcept
1402 {
1403 return lhs <=> false;
1404 }
1405
1406 template <class T, class B, class U>
1407 requires(!is_nullable_v<U> && mpl::weakly_equality_comparable_with<T, U>)
1408 constexpr bool operator==(const nullable<T, B>& lhs, const U& rhs) noexcept
1409 {
1410 return lhs && (lhs.get() == rhs);
1411 }
1412
1413 template <class T, class B, class U>
1414 requires(!is_nullable_v<U> && std::three_way_comparable_with<U, T>)
1415 constexpr std::compare_three_way_result_t<T, U> operator<=>(const nullable<T, B>& lhs, const U& rhs) noexcept
1416 {
1417 return lhs ? lhs.get() <=> rhs : std::strong_ordering::less;
1418 }
1419
1420 template <class T, class B, class U, class UB>
1421 requires(mpl::weakly_equality_comparable_with<T, U>)
1422 constexpr bool operator==(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept
1423 {
1424 return rhs ? lhs == rhs.get() : !lhs;
1425 }
1426
1427 template <class T, class B, std::three_way_comparable_with<T> U, class UB>
1428 constexpr std::compare_three_way_result_t<T, U>
1429 operator<=>(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept
1430 {
1431 return (lhs && rhs) ? lhs.get() <=> rhs.get() : bool(lhs) <=> bool(rhs);
1432 }
1433
1434 template <class T, mpl::boolean_like B>
1435 constexpr nullable<T, B> make_nullable(T&& value, B&& flag)
1436 {
1437 return nullable<T, B>(std::forward<T>(value), std::forward<B>(flag));
1438 }
1439
1440 template <std::ranges::range R, typename T>
1441 requires(nullable_of<std::ranges::range_value_t<R>, T>)
1442 constexpr void zero_null_values(R& range, const T& default_value)
1443 {
1444 for (auto nullable_value : range)
1445 {
1446 if (!nullable_value.has_value())
1447 {
1448 nullable_value.get() = default_value;
1449 }
1450 }
1451 }
1452
1453 /***********************************
1454 * nullable_variant implementation *
1455 ***********************************/
1456
1457 template <class... T>
1458 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1459 constexpr nullable_variant<T...>& nullable_variant<T...>::operator=(const nullable_variant& rhs)
1460 {
1461 base_type::operator=(rhs);
1462 return *this;
1463 }
1464
1465 template <class... T>
1466 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1467 constexpr nullable_variant<T...>& nullable_variant<T...>::operator=(nullable_variant&& rhs) noexcept
1468 {
1469 base_type::operator=(std::move(rhs));
1470 return *this;
1471 }
1472
1473 template <class... T>
1474 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1475 constexpr nullable_variant<T...>::operator bool() const
1476 {
1477 return has_value();
1478 }
1479
1480 template <class... T>
1481 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1482 constexpr bool nullable_variant<T...>::has_value() const
1483 {
1484 return std::visit(
1485 [](const auto& v)
1486 {
1487 return v.has_value();
1488 },
1489#if SPARROW_GCC_11_2_WORKAROUND
1490 static_cast<const base_type&>(*this)
1491#else
1492 *this
1493#endif
1494 );
1495 }
1496}
1497
1498#if defined(__cpp_lib_format)
1499
1500template <typename T, sparrow::mpl::boolean_like B>
1501struct std::formatter<sparrow::nullable<T, B>>
1502{
1503 constexpr auto parse(format_parse_context& ctx)
1504 {
1505 auto pos = ctx.begin();
1506 while (pos != ctx.end() && *pos != '}')
1507 {
1508 m_format_string.push_back(*pos);
1509 ++pos;
1510 }
1511 m_format_string.push_back('}');
1512 return pos;
1513 }
1514
1515 auto format(const sparrow::nullable<T, B>& n, std::format_context& ctx) const
1516 {
1517 if (n.has_value())
1518 {
1519 return std::vformat_to(ctx.out(), m_format_string, std::make_format_args(n.get()));
1520 }
1521 else
1522 {
1523 return std::format_to(ctx.out(), "{}", "null");
1524 }
1525 }
1526
1527 std::string m_format_string = "{:";
1528};
1529
1530namespace sparrow
1531{
1532 template <typename T, mpl::boolean_like B>
1533 std::ostream& operator<<(std::ostream& os, const nullable<T, B>& value)
1534 {
1535 os << std::format("{}", value);
1536 return os;
1537 }
1538}
1539
1540template <class... T>
1541struct std::formatter<sparrow::nullable_variant<T...>>
1542{
1543 constexpr auto parse(format_parse_context& ctx)
1544 {
1545 auto pos = ctx.begin();
1546 while (pos != ctx.end() && *pos != '}')
1547 {
1548 m_format_string.push_back(*pos);
1549 ++pos;
1550 }
1551 m_format_string.push_back('}');
1552 return pos;
1553 }
1554
1555 auto format(const sparrow::nullable_variant<T...>& variant, std::format_context& ctx) const
1556 {
1557 if (variant.has_value())
1558 {
1559 return std::visit(
1560 [&](const auto& value)
1561 {
1562 return std::vformat_to(ctx.out(), m_format_string, std::make_format_args(value));
1563 },
1564 variant
1565 );
1566 }
1567 else
1568 {
1569 return std::vformat_to(ctx.out(), m_format_string, std::make_format_args("null"));
1570 }
1571 }
1572
1573 std::string m_format_string = "{:";
1574};
1575
1576namespace sparrow
1577{
1578 template <class... T>
1579 std::ostream& operator<<(std::ostream& os, const nullable_variant<T...>& value)
1580 {
1581 os << std::format("{}", value);
1582 return os;
1583 }
1584}
1585
1586template <>
1587struct std::formatter<sparrow::nullval_t>
1588{
1589 constexpr auto parse(format_parse_context& ctx)
1590 {
1591 return ctx.begin(); // Simple implementation
1592 }
1593
1594 auto format(const sparrow::nullval_t&, std::format_context& ctx) const
1595 {
1596 return std::format_to(ctx.out(), "nullval");
1597 }
1598};
1599
1600#endif
1601
1602namespace sparrow
1603{
1604 inline std::ostream& operator<<(std::ostream& os, const nullval_t&)
1605 {
1606 constexpr std::string_view nullval_str = "nullval";
1607 os << nullval_str;
1608 return os;
1609 }
1610}
1611
1612#undef SPARROW_CONSTEXPR
Exception thrown when accessing a null nullable value.
Definition nullable.hpp:147
const char * what() const noexcept override
Gets the descriptive error message.
Definition nullable.hpp:161
bad_nullable_access() noexcept=default
Variant of nullable types with has_value() convenience method.
constexpr nullable_variant(nullable_variant &&) noexcept=default
constexpr nullable_variant(const nullable_variant &)=default
constexpr bool has_value() const
Checks whether the active alternative contains a valid value.
std::variant< T... > base_type
constexpr nullable_variant(const nullable< U, B > &value)
constexpr nullable(const self_type &rhs)=default
Default copy constructor.
constexpr nullable(std::add_lvalue_reference_t< T > value, flag_type &&null_flag)
Constructor from value reference and moved flag.
Definition nullable.hpp:584
constexpr nullable(U &value, V &null_flag)
Constructor from const value and non-const flag reference (for non-reference types only).
Definition nullable.hpp:605
constexpr nullable(value_type &&value, flag_type &&null_flag)
Constructor from value and flag.
Definition nullable.hpp:538
constexpr nullable() noexcept
Default constructor creating a null nullable.
Definition nullable.hpp:342
friend class nullable
Definition nullable.hpp:974
constexpr nullable(nullval_t) noexcept
Constructor from nullval_t creating a null nullable.
Definition nullable.hpp:360
constexpr nullable(U &&value, V &&null_flag)
Constructor from two forwarding references (for value types with const qualifiers).
Definition nullable.hpp:629
typename value_traits::rvalue_reference rvalue_reference
Definition nullable.hpp:321
constexpr nullable(value_type &&value, std::add_lvalue_reference_t< B > null_flag)
Constructor from moved value and flag reference.
Definition nullable.hpp:569
constexpr self_type & operator=(const self_type &rhs) noexcept
Default copy assignment operator.
Definition nullable.hpp:684
constexpr value_type value_or(U &&default_value) const &
constexpr nullable(self_type &&rhs) noexcept=default
Default move constructor.
typename flag_traits::const_rvalue_reference flag_const_rvalue_reference
Definition nullable.hpp:328
constexpr self_type & operator=(nullval_t) noexcept
Assignment from nullval_t, setting nullable to null state.
Definition nullable.hpp:645
typename flag_traits::rvalue_reference flag_rvalue_reference
Definition nullable.hpp:327
constexpr self_type & operator=(self_type &&rhs) noexcept
Default move assignment operator.
Definition nullable.hpp:730
constexpr nullable(std::add_lvalue_reference_t< T > value, std::add_lvalue_reference_t< B > null_flag)
Constructor from lvalue references (for reference semantics).
Definition nullable.hpp:554
nullable_traits< inner_const_reference > value_traits
Definition nullable.hpp:317
typename value_traits::const_rvalue_reference const_rvalue_reference
Definition nullable.hpp:322
Concepts used to disambiguate the nullable class constructors.
Definition nullable.hpp:208
std::ostream & operator<<(std::ostream &stream, primesum::uint128_t n)
The __int128_t type (GCC/Clang) is not well supported by the C++ standard library (in 2016) so we hav...
Definition int128_t.hpp:48
std::conditional_t< std::is_reference_v< T >, const std::decay_t< U > &, std::decay_t< U > && > conditional_ref_t
Definition nullable.hpp:237
typename add_const_lvalue_reference< T >::type add_const_lvalue_reference_t
Convenience alias for add_const_lvalue_reference.
Definition mp_utils.hpp:766
constexpr std::compare_three_way_result_t< typename cloning_ptr< T1 >::pointer, typename cloning_ptr< T2 >::pointer > operator<=>(const cloning_ptr< T1 > &lhs, const cloning_ptr< T2 > &rhs) noexcept
Definition memory.hpp:474
constexpr void zero_null_values(R &range, const T &default_value=T{})
Sets null values in a range to a default value.
SPARROW_API bool operator==(const array &lhs, const array &rhs)
Compares the content of two arrays.
constexpr nullval_t nullval(0)
constexpr nullable_get_fn nullable_get
Definition nullable.hpp:102
constexpr bool is_nullable_variant_v
constexpr bool is_nullable_v
Definition nullable.hpp:62
std::ostream & operator<<(std::ostream &os, const nullval_t &)
SPARROW_API void swap(ArrowArray &lhs, ArrowArray &rhs) noexcept
Swaps the contents of the two ArrowArray objects.
constexpr nullable< T, B > make_nullable(T &&value, B &&flag=true)
Creates a nullable object with deduced types.
Extensions to the C++ standard library.
#define SPARROW_CONSTEXPR
Definition nullable.hpp:43
const_reference const_rvalue_reference
Definition nullable.hpp:137
std::add_lvalue_reference_t< value_type > reference
Definition nullable.hpp:134
std::add_lvalue_reference_t< std::add_const_t< value_type > > const_reference
Definition nullable.hpp:135
const value_type && const_rvalue_reference
Definition nullable.hpp:127
std::add_lvalue_reference_t< value_type > reference
Definition nullable.hpp:124
value_type && rvalue_reference
Definition nullable.hpp:126
std::add_lvalue_reference_t< std::add_const_t< value_type > > const_reference
Definition nullable.hpp:125
Sentinel type to indicate a nullable value is null.
Definition nullable.hpp:179
constexpr nullval_t(int)
Private constructor to prevent default construction.
Definition nullable.hpp:183
sparrow:: nullable< std::common_reference_t< TQual< T >, UQual< U > >, std::common_reference_t< TQual< TB >, UQual< UB > > > type