sparrow 2.4.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 defined(__cpp_lib_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 constexpr nullable_variant(const nullable_variant&) = default;
1150 constexpr nullable_variant(nullable_variant&&) noexcept = default;
1151
1161 constexpr nullable_variant& operator=(const nullable_variant&);
1162
1172 constexpr nullable_variant& operator=(nullable_variant&&) noexcept;
1173
1181 constexpr explicit operator bool() const;
1182
1190 constexpr bool has_value() const;
1191 };
1192
1193 template <class T>
1194 struct is_nullable_variant : std::false_type
1195 {
1196 };
1197
1198 template <class... T>
1199 struct is_nullable_variant<nullable_variant<T...>> : std::true_type
1200 {
1201 };
1202
1203 template <class T>
1205}
1206
1207namespace std
1208{
1209 namespace mpl = sparrow::mpl;
1210
1211 // Specialization of basic_common_reference for nullable proxies so
1212 // we can use ranges algorithm on iterators returning nullable
1213 template <class T, mpl::boolean_like TB, class U, mpl::boolean_like UB, template <class> class TQual, template <class> class UQual>
1214 requires std::common_reference_with<T, U> && std::common_reference_with<TB, UB>
1215 struct basic_common_reference<sparrow::nullable<T, TB>, sparrow::nullable<U, UB>, TQual, UQual>
1216 {
1217 using type = sparrow::
1218 nullable<std::common_reference_t<TQual<T>, UQual<U>>, std::common_reference_t<TQual<TB>, UQual<UB>>>;
1219 };
1220}
1221
1222namespace sparrow
1223{
1224 /***************************
1225 * nullable implementation *
1226 ***************************/
1227
1228 template <class T, mpl::boolean_like B>
1229 constexpr nullable<T, B>::operator bool() const noexcept
1230 {
1231 return m_null_flag;
1232 }
1233
1234 template <class T, mpl::boolean_like B>
1235 constexpr bool nullable<T, B>::has_value() const noexcept
1236 {
1237 return m_null_flag;
1238 }
1239
1240 template <class T, mpl::boolean_like B>
1241 constexpr auto nullable<T, B>::null_flag() & noexcept -> flag_reference
1242 {
1243 return m_null_flag;
1244 }
1245
1246 template <class T, mpl::boolean_like B>
1247 constexpr auto nullable<T, B>::null_flag() const& noexcept -> flag_const_reference
1248 {
1249 return m_null_flag;
1250 }
1251
1252 template <class T, mpl::boolean_like B>
1253 constexpr auto nullable<T, B>::null_flag() && noexcept -> flag_rvalue_reference
1254 {
1255 if constexpr (std::is_reference_v<B>)
1256 {
1257 return m_null_flag;
1258 }
1259 else
1260 {
1261 return flag_rvalue_reference(m_null_flag);
1262 }
1263 }
1264
1265 template <class T, mpl::boolean_like B>
1266 constexpr auto nullable<T, B>::null_flag() const&& noexcept -> flag_const_rvalue_reference
1267 {
1268 if constexpr (std::is_reference_v<B>)
1269 {
1270 return m_null_flag;
1271 }
1272 else
1273 {
1274 return flag_const_rvalue_reference(m_null_flag);
1275 }
1276 }
1277
1278 template <class T, mpl::boolean_like B>
1279 constexpr auto nullable<T, B>::get() & noexcept -> reference
1280 {
1281 return m_value;
1282 }
1283
1284 template <class T, mpl::boolean_like B>
1285 constexpr auto nullable<T, B>::get() const& noexcept -> const_reference
1286 {
1287 return m_value;
1288 }
1289
1290 template <class T, mpl::boolean_like B>
1291 constexpr auto nullable<T, B>::get() && noexcept -> rvalue_reference
1292 {
1293 if constexpr (std::is_reference_v<T>)
1294 {
1295 return m_value;
1296 }
1297 else
1298 {
1299 return rvalue_reference(m_value);
1300 }
1301 }
1302
1303 template <class T, mpl::boolean_like B>
1304 constexpr auto nullable<T, B>::get() const&& noexcept -> const_rvalue_reference
1305 {
1306 if constexpr (std::is_reference_v<T>)
1307 {
1308 return m_value;
1309 }
1310 else
1311 {
1312 return const_rvalue_reference(m_value);
1313 }
1314 }
1315
1316 template <class T, mpl::boolean_like B>
1317 constexpr auto nullable<T, B>::value() & -> reference
1318 {
1319 throw_if_null();
1320 return get();
1321 }
1322
1323 template <class T, mpl::boolean_like B>
1324 constexpr auto nullable<T, B>::value() const& -> const_reference
1325 {
1326 throw_if_null();
1327 return get();
1328 }
1329
1330 template <class T, mpl::boolean_like B>
1332 {
1333 throw_if_null();
1334 return std::move(*this).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>
1345 template <class U>
1346 constexpr auto nullable<T, B>::value_or(U&& default_value) const& -> value_type
1347 {
1348 return *this ? get() : value_type(std::forward<U>(default_value));
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) && -> 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 void nullable<T, B>::swap(self_type& other) noexcept
1360 {
1361 using std::swap;
1362 swap(m_value, other.m_value);
1363 swap(m_null_flag, other.m_null_flag);
1364 }
1365
1366 template <class T, mpl::boolean_like B>
1368 {
1369 m_null_flag = false;
1370 }
1371
1372 template <class T, mpl::boolean_like B>
1373 void nullable<T, B>::throw_if_null() const
1374 {
1375 if (!m_null_flag)
1376 {
1377 throw bad_nullable_access{};
1378 }
1379 }
1380
1381 template <class T, class B>
1382 constexpr void swap(nullable<T, B>& lhs, nullable<T, B>& rhs) noexcept
1383 {
1384 lhs.swap(rhs);
1385 }
1386
1387 template <class T, class B>
1388 constexpr bool operator==(const nullable<T, B>& lhs, nullval_t) noexcept
1389 {
1390 return !lhs;
1391 }
1392
1393 template <class T, class B>
1394 constexpr std::strong_ordering operator<=>(const nullable<T, B>& lhs, nullval_t) noexcept
1395 {
1396 return lhs <=> false;
1397 }
1398
1399 template <class T, class B, class U>
1400 requires(!is_nullable_v<U> && mpl::weakly_equality_comparable_with<T, U>)
1401 constexpr bool operator==(const nullable<T, B>& lhs, const U& rhs) noexcept
1402 {
1403 return lhs && (lhs.get() == rhs);
1404 }
1405
1406 template <class T, class B, class U>
1407 requires(!is_nullable_v<U> && std::three_way_comparable_with<U, T>)
1408 constexpr std::compare_three_way_result_t<T, U> operator<=>(const nullable<T, B>& lhs, const U& rhs) noexcept
1409 {
1410 return lhs ? lhs.get() <=> rhs : std::strong_ordering::less;
1411 }
1412
1413 template <class T, class B, class U, class UB>
1414 requires(mpl::weakly_equality_comparable_with<T, U>)
1415 constexpr bool operator==(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept
1416 {
1417 return rhs ? lhs == rhs.get() : !lhs;
1418 }
1419
1420 template <class T, class B, std::three_way_comparable_with<T> U, class UB>
1421 constexpr std::compare_three_way_result_t<T, U>
1422 operator<=>(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept
1423 {
1424 return (lhs && rhs) ? lhs.get() <=> rhs.get() : bool(lhs) <=> bool(rhs);
1425 }
1426
1427 template <class T, mpl::boolean_like B>
1428 constexpr nullable<T, B> make_nullable(T&& value, B&& flag)
1429 {
1430 return nullable<T, B>(std::forward<T>(value), std::forward<B>(flag));
1431 }
1432
1433 template <std::ranges::range R, typename T>
1434 requires(nullable_of<std::ranges::range_value_t<R>, T>)
1435 constexpr void zero_null_values(R& range, const T& default_value)
1436 {
1437 for (auto nullable_value : range)
1438 {
1439 if (!nullable_value.has_value())
1440 {
1441 nullable_value.get() = default_value;
1442 }
1443 }
1444 }
1445
1446 /***********************************
1447 * nullable_variant implementation *
1448 ***********************************/
1449
1450 template <class... T>
1451 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1452 constexpr nullable_variant<T...>& nullable_variant<T...>::operator=(const nullable_variant& rhs)
1453 {
1454 base_type::operator=(rhs);
1455 return *this;
1456 }
1457
1458 template <class... T>
1459 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1460 constexpr nullable_variant<T...>& nullable_variant<T...>::operator=(nullable_variant&& rhs) noexcept
1461 {
1462 base_type::operator=(std::move(rhs));
1463 return *this;
1464 }
1465
1466 template <class... T>
1467 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1468 constexpr nullable_variant<T...>::operator bool() const
1469 {
1470 return has_value();
1471 }
1472
1473 template <class... T>
1474 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1475 constexpr bool nullable_variant<T...>::has_value() const
1476 {
1477 return std::visit(
1478 [](const auto& v)
1479 {
1480 return v.has_value();
1481 },
1482#if SPARROW_GCC_11_2_WORKAROUND
1483 static_cast<const base_type&>(*this)
1484#else
1485 *this
1486#endif
1487 );
1488 }
1489}
1490
1491#if defined(__cpp_lib_format)
1492
1493template <typename T, sparrow::mpl::boolean_like B>
1494struct std::formatter<sparrow::nullable<T, B>>
1495{
1496 constexpr auto parse(format_parse_context& ctx)
1497 {
1498 auto pos = ctx.begin();
1499 while (pos != ctx.end() && *pos != '}')
1500 {
1501 m_format_string.push_back(*pos);
1502 ++pos;
1503 }
1504 m_format_string.push_back('}');
1505 return pos;
1506 }
1507
1508 auto format(const sparrow::nullable<T, B>& n, std::format_context& ctx) const
1509 {
1510 if (n.has_value())
1511 {
1512 return std::vformat_to(ctx.out(), m_format_string, std::make_format_args(n.get()));
1513 }
1514 else
1515 {
1516 return std::format_to(ctx.out(), "{}", "null");
1517 }
1518 }
1519
1520 std::string m_format_string = "{:";
1521};
1522
1523namespace sparrow
1524{
1525 template <typename T, mpl::boolean_like B>
1526 std::ostream& operator<<(std::ostream& os, const nullable<T, B>& value)
1527 {
1528 os << std::format("{}", value);
1529 return os;
1530 }
1531}
1532
1533template <class... T>
1534struct std::formatter<sparrow::nullable_variant<T...>>
1535{
1536 constexpr auto parse(format_parse_context& ctx)
1537 {
1538 auto pos = ctx.begin();
1539 while (pos != ctx.end() && *pos != '}')
1540 {
1541 m_format_string.push_back(*pos);
1542 ++pos;
1543 }
1544 m_format_string.push_back('}');
1545 return pos;
1546 }
1547
1548 auto format(const sparrow::nullable_variant<T...>& variant, std::format_context& ctx) const
1549 {
1550 if (variant.has_value())
1551 {
1552 return std::visit(
1553 [&](const auto& value)
1554 {
1555 return std::vformat_to(ctx.out(), m_format_string, std::make_format_args(value));
1556 },
1557 variant
1558 );
1559 }
1560 else
1561 {
1562 return std::vformat_to(ctx.out(), m_format_string, std::make_format_args("null"));
1563 }
1564 }
1565
1566 std::string m_format_string = "{:";
1567};
1568
1569namespace sparrow
1570{
1571 template <class... T>
1572 std::ostream& operator<<(std::ostream& os, const nullable_variant<T...>& value)
1573 {
1574 os << std::format("{}", value);
1575 return os;
1576 }
1577}
1578
1579template <>
1580struct std::formatter<sparrow::nullval_t>
1581{
1582 constexpr auto parse(format_parse_context& ctx)
1583 {
1584 return ctx.begin(); // Simple implementation
1585 }
1586
1587 auto format(const sparrow::nullval_t&, std::format_context& ctx) const
1588 {
1589 return std::format_to(ctx.out(), "nullval");
1590 }
1591};
1592
1593#endif
1594
1595namespace sparrow
1596{
1597 inline std::ostream& operator<<(std::ostream& os, const nullval_t&)
1598 {
1599 constexpr std::string_view nullval_str = "nullval";
1600 os << nullval_str;
1601 return os;
1602 }
1603}
1604
1605#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(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
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