sparrow 1.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 /*
83 * Default traits for the nullable class. These traits should be specialized
84 * for proxy classes whose reference and const_reference types are not
85 * defined as usual. For instance:
86 *
87 * @code{.cpp}
88 * struct nullable_traits<string_proxy>
89 * {
90 * using value_type = std::string;
91 * using reference = string_proxy;
92 * using const_reference = std::string_view;
93 * using rvalue_reverence = std::string&&;
94 * using const_rvalue_reference = const std::string&&;
95 * };
96 * @endcode
97 */
98 template <class T>
100 {
101 using value_type = T;
102 using reference = std::add_lvalue_reference_t<value_type>;
103 using const_reference = std::add_lvalue_reference_t<std::add_const_t<value_type>>;
106 };
107
108 template <class T>
110 {
111 using value_type = T;
112 using reference = std::add_lvalue_reference_t<value_type>;
113 using const_reference = std::add_lvalue_reference_t<std::add_const_t<value_type>>;
116 };
117
124 class bad_nullable_access : public std::exception
125 {
126 public:
127
128 bad_nullable_access() noexcept = default;
129 bad_nullable_access(const bad_nullable_access&) noexcept = default;
130 bad_nullable_access& operator=(const bad_nullable_access&) noexcept = default;
131
139 [[nodiscard]] const char* what() const noexcept override
140 {
141 return message;
142 }
143
144 private:
145
146 static constexpr const char* message = "Invalid access to nullable underlying value";
147 };
148
157 {
161 constexpr explicit nullval_t(int)
162 {
163 }
164 };
165
178 inline constexpr nullval_t nullval(0);
179
180 namespace impl
181 {
185 template <class T, class TArgs, class U, class UArgs>
186 concept both_constructible_from_cref = std::constructible_from<T, mpl::add_const_lvalue_reference_t<TArgs>>
187 and std::constructible_from<U, mpl::add_const_lvalue_reference_t<UArgs>>;
188
189 template <class To1, class From1, class To2, class From2>
190 concept both_convertible_from_cref = std::convertible_to<mpl::add_const_lvalue_reference_t<From1>, To1>
191 and std::convertible_to<mpl::add_const_lvalue_reference_t<From2>, To2>;
192
193 template <class T, class... Args>
194 concept constructible_from_one = (std::constructible_from<T, Args> || ...);
195
196 template <class T, class... Args>
197 concept convertible_from_one = (std::convertible_to<Args, T> || ...);
198
199 template <class T, class... Args>
201
202 template <class T, class Arg>
204
205 // We prefer std::is_assignable_v to std::assignable_from<To, From> because
206 // std::assignable_from requires the existence of an implicit conversion
207 // from From to To
208 template <class To, class... Args>
209 concept assignable_from_one = (std::is_assignable_v<std::add_lvalue_reference<To>, Args> && ...);
210
211 template <class T, class Arg>
213
214 template <class T, class U>
215 using conditional_ref_t = std::conditional_t<std::is_reference_v<T>, const std::decay_t<U>&, std::decay_t<U>&&>;
216
217 template <class T, class Targs, class U, class UArgs>
218 concept both_constructible_from_cond_ref = std::constructible_from<T, conditional_ref_t<T, Targs>>
219 and std::constructible_from<U, conditional_ref_t<U, UArgs>>;
220
221 template <class To1, class From1, class To2, class From2>
222 concept both_convertible_from_cond_ref = std::convertible_to<conditional_ref_t<To1, From1>, To1>
223 and std::convertible_to<conditional_ref_t<To2, From2>, To2>;
224
225 template <class To1, class From1, class To2, class From2>
226 concept both_assignable_from_cref = std::is_assignable_v<
227 std::add_lvalue_reference_t<To1>,
229 and std::is_assignable_v<
230 std::add_lvalue_reference_t<To2>,
232
233 template <class To1, class From1, class To2, class From2>
234 concept both_assignable_from_cond_ref = std::is_assignable_v<
235 std::add_lvalue_reference_t<To1>,
237 and std::is_assignable_v<
238 std::add_lvalue_reference_t<To2>,
240 }
241
288 template <class T, mpl::boolean_like B = bool>
290 {
291 public:
292
307
319 template <std::default_initializable U = T, std::default_initializable BB = B>
320 constexpr nullable() noexcept
321 : m_value()
322 , m_null_flag(false)
323 {
324 }
325
337 template <std::default_initializable U = T, std::default_initializable BB = B>
338 constexpr nullable(nullval_t) noexcept
339 : m_value()
340 , m_null_flag(false)
341 {
342 }
343
356 template <class U>
357 requires(not std::same_as<self_type, std::decay_t<U>> and std::constructible_from<T, U &&>)
358 explicit(not std::convertible_to<U&&, T>) constexpr nullable(U&& value) noexcept(
359 noexcept(T(std::declval<U>()))
360 )
361 : m_value(std::forward<U>(value))
362 , m_null_flag(true)
363 {
364 }
365
376 constexpr nullable(const self_type& rhs) = default;
377
392 template <class TO, mpl::boolean_like BO>
397 )
398 : m_value(rhs.get())
399 , m_null_flag(rhs.null_flag())
400 {
401 }
402
403#ifdef __clang__
424 template <class TO, mpl::boolean_like BO>
425 requires(impl::both_constructible_from_cref<T, TO, B, BO> and std::same_as<std::decay_t<T>, bool>)
427 const nullable<TO, BO>& rhs
428 )
429 : m_value(rhs.get())
430 , m_null_flag(rhs.null_flag())
431 {
432 }
433#endif
434
445 constexpr nullable(self_type&& rhs) noexcept = default;
446
461 template <class TO, mpl::boolean_like BO>
466 )
467 : m_value(std::move(rhs).get())
468 , m_null_flag(std::move(rhs).null_flag())
469 {
470 }
471
472#ifdef __clang__
494 template <class TO, mpl::boolean_like BO>
496 and std::same_as<std::decay_t<T>, bool>)
499 )
500 : m_value(std::move(rhs).get())
501 , m_null_flag(std::move(rhs).null_flag())
502 {
503 }
504#endif
505
517 : m_value(std::move(value))
518 , m_null_flag(std::move(null_flag))
519 {
520 }
521
532 constexpr nullable(std::add_lvalue_reference_t<T> value, std::add_lvalue_reference_t<B> null_flag)
533 : m_value(value)
534 , m_null_flag(null_flag)
535 {
536 }
537
547 constexpr nullable(value_type&& value, std::add_lvalue_reference_t<B> null_flag)
548 : m_value(std::move(value))
549 , m_null_flag(null_flag)
550 {
551 }
552
562 constexpr nullable(std::add_lvalue_reference_t<T> value, flag_type&& null_flag)
563 : m_value(value)
564 , m_null_flag(std::move(null_flag))
565 {
566 }
567
577 template <class U, class V>
578 requires(std::same_as<std::remove_cvref_t<U>, T> && std::same_as<std::remove_cvref_t<V>, B>
579 && std::is_const_v<std::remove_reference_t<U>>
580 && not std::is_const_v<std::remove_reference_t<V>> && not std::is_reference_v<T>
581 && not std::is_reference_v<B> && std::is_lvalue_reference_v<U &&>
582 && std::is_lvalue_reference_v<V &&>)
583 constexpr nullable(U& value, V& null_flag)
584 : m_value(value)
585 , m_null_flag(null_flag)
586 {
587 }
588
602 template <class U, class V>
603 requires(std::same_as<std::remove_cvref_t<U>, T> && std::same_as<std::remove_cvref_t<V>, B>
604 && not std::is_reference_v<T> && not std::is_reference_v<B>
605 && (std::is_const_v<std::remove_reference_t<U>>
606 || std::is_const_v<std::remove_reference_t<V>>) )
607 constexpr nullable(U&& value, V&& null_flag)
608 : m_value(std::forward<U>(value))
609 , m_null_flag(std::forward<V>(null_flag))
610 {
611 }
612
623 constexpr self_type& operator=(nullval_t) noexcept
624 {
625 m_null_flag = false;
626 return *this;
627 }
628
642 template <class TO>
643 requires(not std::same_as<self_type, TO> and std::assignable_from<std::add_lvalue_reference_t<T>, TO>)
644 constexpr self_type& operator=(TO&& rhs) noexcept
645 {
646 m_value = std::forward<TO>(rhs);
647 m_null_flag = true;
648 return *this;
649 }
650
662 constexpr self_type& operator=(const self_type& rhs) noexcept
663 {
664 m_value = rhs.get();
665 m_null_flag = rhs.null_flag();
666 return *this;
667 }
668
684 template <class TO, mpl::boolean_like BO>
685 requires(
689 )
690 constexpr self_type& operator=(const nullable<TO, BO>& rhs) noexcept
691 {
692 m_value = rhs.get();
693 m_null_flag = rhs.null_flag();
694 return *this;
695 }
696
708 constexpr self_type& operator=(self_type&& rhs) noexcept
709 {
710 m_value = std::move(rhs).get();
711 m_null_flag = std::move(rhs).null_flag();
712 return *this;
713 }
714
730 template <class TO, mpl::boolean_like BO>
731 requires(
735 )
736 constexpr self_type& operator=(nullable<TO, BO>&& rhs) noexcept
737 {
738 m_value = std::move(rhs).get();
739 m_null_flag = std::move(rhs).null_flag();
740 return *this;
741 }
742
751 constexpr explicit operator bool() const noexcept;
752
761 [[nodiscard]] constexpr bool has_value() const noexcept;
762
771 [[nodiscard]] constexpr flag_reference null_flag() & noexcept;
772
780 [[nodiscard]] constexpr flag_const_reference null_flag() const& noexcept;
781
790 [[nodiscard]] constexpr flag_rvalue_reference null_flag() && noexcept;
791
800 [[nodiscard]] constexpr flag_const_rvalue_reference null_flag() const&& noexcept;
801
811 [[nodiscard]] constexpr reference get() & noexcept;
812
821 [[nodiscard]] constexpr const_reference get() const& noexcept;
822
831 [[nodiscard]] constexpr rvalue_reference get() && noexcept;
832
841 [[nodiscard]] constexpr const_rvalue_reference get() const&& noexcept;
842
853 [[nodiscard]] constexpr reference value() &;
854
865 [[nodiscard]] constexpr const_reference value() const&;
866
878 [[nodiscard]] constexpr rvalue_reference value() &&;
879
890 [[nodiscard]] constexpr const_rvalue_reference value() const&&;
891
903 template <class U>
904 [[nodiscard]] constexpr value_type value_or(U&& default_value) const&;
905
917 template <class U>
918 [[nodiscard]] constexpr value_type value_or(U&& default_value) &&;
919
928 void swap(self_type& other) noexcept;
929
937 void reset() noexcept;
938
939 private:
940
946 void throw_if_null() const;
947
948 T m_value;
949 B m_null_flag;
950
951 template <class TO, mpl::boolean_like BO>
952 friend class nullable;
953 };
954
966 template <class T, class B>
967 constexpr void swap(nullable<T, B>& lhs, nullable<T, B>& rhs) noexcept;
968
980 template <class T, class B>
981 constexpr bool operator==(const nullable<T, B>& lhs, nullval_t) noexcept;
982
995 template <class T, mpl::boolean_like B>
996 constexpr std::strong_ordering operator<=>(const nullable<T, B>& lhs, nullval_t dummy) noexcept;
997
1011 template <class T, class B, class U>
1012 requires(!is_nullable_v<U> && mpl::weakly_equality_comparable_with<T, U>)
1013 constexpr bool operator==(const nullable<T, B>& lhs, const U& rhs) noexcept;
1014
1029 template <class T, class B, class U>
1030 requires(!is_nullable_v<U> && std::three_way_comparable_with<U, T>)
1031 constexpr std::compare_three_way_result_t<T, U>
1032 operator<=>(const nullable<T, B>& lhs, const U& rhs) noexcept;
1033
1049 template <class T, class B, class U, class UB>
1050 requires(mpl::weakly_equality_comparable_with<T, U>)
1051 constexpr bool operator==(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept;
1052
1069 template <class T, class B, std::three_way_comparable_with<T> U, class UB>
1070 constexpr std::compare_three_way_result_t<T, U>
1071 operator<=>(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept;
1072
1085 template <class T, mpl::boolean_like B = bool>
1086 constexpr nullable<T, B> make_nullable(T&& value, B&& flag = true);
1087
1101 template <std::ranges::range R, typename T = typename std::ranges::range_value_t<R>::value_type>
1102 requires(nullable_of<std::ranges::range_value_t<R>, T>)
1103 constexpr void zero_null_values(R& range, const T& default_value = T{});
1104
1118 template <class... T>
1119 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1120 class nullable_variant : public std::variant<T...>
1121 {
1122 public:
1123
1124 using base_type = std::variant<T...>;
1125 using base_type::base_type;
1126
1127 constexpr nullable_variant(const nullable_variant&) = default;
1128 constexpr nullable_variant(nullable_variant&&) noexcept = default;
1129
1139 constexpr nullable_variant& operator=(const nullable_variant&);
1140
1150 constexpr nullable_variant& operator=(nullable_variant&&) noexcept;
1151
1159 constexpr explicit operator bool() const;
1160
1168 constexpr bool has_value() const;
1169 };
1170
1171 template <class T>
1172 struct is_nullable_variant : std::false_type
1173 {
1174 };
1175
1176 template <class... T>
1177 struct is_nullable_variant<nullable_variant<T...>> : std::true_type
1178 {
1179 };
1180
1181 template <class T>
1183}
1184
1185namespace std
1186{
1187 namespace mpl = sparrow::mpl;
1188
1189 // Specialization of basic_common_reference for nullable proxies so
1190 // we can use ranges algorithm on iterators returning nullable
1191 template <class T, mpl::boolean_like TB, class U, mpl::boolean_like UB, template <class> class TQual, template <class> class UQual>
1192 requires std::common_reference_with<T, U> && std::common_reference_with<TB, UB>
1193 struct basic_common_reference<sparrow::nullable<T, TB>, sparrow::nullable<U, UB>, TQual, UQual>
1194 {
1195 using type = sparrow::
1196 nullable<std::common_reference_t<TQual<T>, UQual<U>>, std::common_reference_t<TQual<TB>, UQual<UB>>>;
1197 };
1198}
1199
1200namespace sparrow
1201{
1202 /***************************
1203 * nullable implementation *
1204 ***************************/
1205
1206 template <class T, mpl::boolean_like B>
1207 constexpr nullable<T, B>::operator bool() const noexcept
1208 {
1209 return m_null_flag;
1210 }
1211
1212 template <class T, mpl::boolean_like B>
1213 constexpr bool nullable<T, B>::has_value() const noexcept
1214 {
1215 return m_null_flag;
1216 }
1217
1218 template <class T, mpl::boolean_like B>
1219 constexpr auto nullable<T, B>::null_flag() & noexcept -> flag_reference
1220 {
1221 return m_null_flag;
1222 }
1223
1224 template <class T, mpl::boolean_like B>
1225 constexpr auto nullable<T, B>::null_flag() const& noexcept -> flag_const_reference
1226 {
1227 return m_null_flag;
1228 }
1229
1230 template <class T, mpl::boolean_like B>
1231 constexpr auto nullable<T, B>::null_flag() && noexcept -> flag_rvalue_reference
1232 {
1233 if constexpr (std::is_reference_v<B>)
1234 {
1235 return m_null_flag;
1236 }
1237 else
1238 {
1239 return flag_rvalue_reference(m_null_flag);
1240 }
1241 }
1242
1243 template <class T, mpl::boolean_like B>
1244 constexpr auto nullable<T, B>::null_flag() const&& noexcept -> flag_const_rvalue_reference
1245 {
1246 if constexpr (std::is_reference_v<B>)
1247 {
1248 return m_null_flag;
1249 }
1250 else
1251 {
1252 return flag_const_rvalue_reference(m_null_flag);
1253 }
1254 }
1255
1256 template <class T, mpl::boolean_like B>
1257 constexpr auto nullable<T, B>::get() & noexcept -> reference
1258 {
1259 return m_value;
1260 }
1261
1262 template <class T, mpl::boolean_like B>
1263 constexpr auto nullable<T, B>::get() const& noexcept -> const_reference
1264 {
1265 return m_value;
1266 }
1267
1268 template <class T, mpl::boolean_like B>
1269 constexpr auto nullable<T, B>::get() && noexcept -> rvalue_reference
1270 {
1271 if constexpr (std::is_reference_v<T>)
1272 {
1273 return m_value;
1274 }
1275 else
1276 {
1277 return rvalue_reference(m_value);
1278 }
1279 }
1280
1281 template <class T, mpl::boolean_like B>
1282 constexpr auto nullable<T, B>::get() const&& noexcept -> const_rvalue_reference
1283 {
1284 if constexpr (std::is_reference_v<T>)
1285 {
1286 return m_value;
1287 }
1288 else
1289 {
1290 return const_rvalue_reference(m_value);
1291 }
1292 }
1293
1294 template <class T, mpl::boolean_like B>
1295 constexpr auto nullable<T, B>::value() & -> reference
1296 {
1297 throw_if_null();
1298 return get();
1299 }
1300
1301 template <class T, mpl::boolean_like B>
1302 constexpr auto nullable<T, B>::value() const& -> const_reference
1303 {
1304 throw_if_null();
1305 return get();
1306 }
1307
1308 template <class T, mpl::boolean_like B>
1310 {
1311 throw_if_null();
1312 return std::move(*this).get();
1313 }
1314
1315 template <class T, mpl::boolean_like B>
1317 {
1318 throw_if_null();
1319 return std::move(*this).get();
1320 }
1321
1322 template <class T, mpl::boolean_like B>
1323 template <class U>
1324 constexpr auto nullable<T, B>::value_or(U&& default_value) const& -> value_type
1325 {
1326 return *this ? get() : value_type(std::forward<U>(default_value));
1327 }
1328
1329 template <class T, mpl::boolean_like B>
1330 template <class U>
1331 constexpr auto nullable<T, B>::value_or(U&& default_value) && -> value_type
1332 {
1333 return *this ? get() : value_type(std::forward<U>(default_value));
1334 }
1335
1336 template <class T, mpl::boolean_like B>
1337 void nullable<T, B>::swap(self_type& other) noexcept
1338 {
1339 using std::swap;
1340 swap(m_value, other.m_value);
1341 swap(m_null_flag, other.m_null_flag);
1342 }
1343
1344 template <class T, mpl::boolean_like B>
1346 {
1347 m_null_flag = false;
1348 }
1349
1350 template <class T, mpl::boolean_like B>
1351 void nullable<T, B>::throw_if_null() const
1352 {
1353 if (!m_null_flag)
1354 {
1355 throw bad_nullable_access{};
1356 }
1357 }
1358
1359 template <class T, class B>
1360 constexpr void swap(nullable<T, B>& lhs, nullable<T, B>& rhs) noexcept
1361 {
1362 lhs.swap(rhs);
1363 }
1364
1365 template <class T, class B>
1366 constexpr bool operator==(const nullable<T, B>& lhs, nullval_t) noexcept
1367 {
1368 return !lhs;
1369 }
1370
1371 template <class T, class B>
1372 constexpr std::strong_ordering operator<=>(const nullable<T, B>& lhs, nullval_t) noexcept
1373 {
1374 return lhs <=> false;
1375 }
1376
1377 template <class T, class B, class U>
1378 requires(!is_nullable_v<U> && mpl::weakly_equality_comparable_with<T, U>)
1379 constexpr bool operator==(const nullable<T, B>& lhs, const U& rhs) noexcept
1380 {
1381 return lhs && (lhs.get() == rhs);
1382 }
1383
1384 template <class T, class B, class U>
1385 requires(!is_nullable_v<U> && std::three_way_comparable_with<U, T>)
1386 constexpr std::compare_three_way_result_t<T, U> operator<=>(const nullable<T, B>& lhs, const U& rhs) noexcept
1387 {
1388 return lhs ? lhs.get() <=> rhs : std::strong_ordering::less;
1389 }
1390
1391 template <class T, class B, class U, class UB>
1392 requires(mpl::weakly_equality_comparable_with<T, U>)
1393 constexpr bool operator==(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept
1394 {
1395 return rhs ? lhs == rhs.get() : !lhs;
1396 }
1397
1398 template <class T, class B, std::three_way_comparable_with<T> U, class UB>
1399 constexpr std::compare_three_way_result_t<T, U>
1400 operator<=>(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept
1401 {
1402 return (lhs && rhs) ? lhs.get() <=> rhs.get() : bool(lhs) <=> bool(rhs);
1403 }
1404
1405 template <class T, mpl::boolean_like B>
1406 constexpr nullable<T, B> make_nullable(T&& value, B&& flag)
1407 {
1408 return nullable<T, B>(std::forward<T>(value), std::forward<B>(flag));
1409 }
1410
1411 template <std::ranges::range R, typename T>
1412 requires(nullable_of<std::ranges::range_value_t<R>, T>)
1413 constexpr void zero_null_values(R& range, const T& default_value)
1414 {
1415 for (auto nullable_value : range)
1416 {
1417 if (!nullable_value.has_value())
1418 {
1419 nullable_value.get() = default_value;
1420 }
1421 }
1422 }
1423
1424 /***********************************
1425 * nullable_variant implementation *
1426 ***********************************/
1427
1428 template <class... T>
1429 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1430 constexpr nullable_variant<T...>& nullable_variant<T...>::operator=(const nullable_variant& rhs)
1431 {
1432 base_type::operator=(rhs);
1433 return *this;
1434 }
1435
1436 template <class... T>
1437 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1438 constexpr nullable_variant<T...>& nullable_variant<T...>::operator=(nullable_variant&& rhs) noexcept
1439 {
1440 base_type::operator=(std::move(rhs));
1441 return *this;
1442 }
1443
1444 template <class... T>
1445 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1446 constexpr nullable_variant<T...>::operator bool() const
1447 {
1448 return has_value();
1449 }
1450
1451 template <class... T>
1452 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1453 constexpr bool nullable_variant<T...>::has_value() const
1454 {
1455 return std::visit(
1456 [](const auto& v)
1457 {
1458 return v.has_value();
1459 },
1460#if SPARROW_GCC_11_2_WORKAROUND
1461 static_cast<const base_type&>(*this)
1462#else
1463 *this
1464#endif
1465 );
1466 }
1467}
1468
1469#if defined(__cpp_lib_format)
1470
1471template <typename T, sparrow::mpl::boolean_like B>
1472struct std::formatter<sparrow::nullable<T, B>>
1473{
1474 constexpr auto parse(format_parse_context& ctx)
1475 {
1476 auto pos = ctx.begin();
1477 while (pos != ctx.end() && *pos != '}')
1478 {
1479 m_format_string.push_back(*pos);
1480 ++pos;
1481 }
1482 m_format_string.push_back('}');
1483 return pos;
1484 }
1485
1486 auto format(const sparrow::nullable<T, B>& n, std::format_context& ctx) const
1487 {
1488 if (n.has_value())
1489 {
1490 return std::vformat_to(ctx.out(), m_format_string, std::make_format_args(n.get()));
1491 }
1492 else
1493 {
1494 return std::format_to(ctx.out(), "{}", "null");
1495 }
1496 }
1497
1498 std::string m_format_string = "{:";
1499};
1500
1501namespace sparrow
1502{
1503 template <typename T, mpl::boolean_like B>
1504 std::ostream& operator<<(std::ostream& os, const nullable<T, B>& value)
1505 {
1506 os << std::format("{}", value);
1507 return os;
1508 }
1509}
1510
1511template <class... T>
1512struct std::formatter<sparrow::nullable_variant<T...>>
1513{
1514 constexpr auto parse(format_parse_context& ctx)
1515 {
1516 auto pos = ctx.begin();
1517 while (pos != ctx.end() && *pos != '}')
1518 {
1519 m_format_string.push_back(*pos);
1520 ++pos;
1521 }
1522 m_format_string.push_back('}');
1523 return pos;
1524 }
1525
1526 auto format(const sparrow::nullable_variant<T...>& variant, std::format_context& ctx) const
1527 {
1528 if (variant.has_value())
1529 {
1530 return std::visit(
1531 [&](const auto& value)
1532 {
1533 return std::vformat_to(ctx.out(), m_format_string, std::make_format_args(value));
1534 },
1535 variant
1536 );
1537 }
1538 else
1539 {
1540 return std::vformat_to(ctx.out(), m_format_string, std::make_format_args("null"));
1541 }
1542 }
1543
1544 std::string m_format_string = "{:";
1545};
1546
1547namespace sparrow
1548{
1549 template <class... T>
1550 std::ostream& operator<<(std::ostream& os, const nullable_variant<T...>& value)
1551 {
1552 os << std::format("{}", value);
1553 return os;
1554 }
1555}
1556
1557template <>
1558struct std::formatter<sparrow::nullval_t>
1559{
1560 constexpr auto parse(format_parse_context& ctx)
1561 {
1562 return ctx.begin(); // Simple implementation
1563 }
1564
1565 auto format(const sparrow::nullval_t&, std::format_context& ctx) const
1566 {
1567 return std::format_to(ctx.out(), "nullval");
1568 }
1569};
1570
1571#endif
1572
1573namespace sparrow
1574{
1575 inline std::ostream& operator<<(std::ostream& os, const nullval_t&)
1576 {
1577 constexpr std::string_view nullval_str = "nullval";
1578 os << nullval_str;
1579 return os;
1580 }
1581}
1582
1583#undef SPARROW_CONSTEXPR
Exception thrown when accessing a null nullable value.
Definition nullable.hpp:125
const char * what() const noexcept override
Gets the descriptive error message.
Definition nullable.hpp:139
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:562
constexpr nullable(U &value, V &null_flag)
Constructor from const value and non-const flag reference (for non-reference types only).
Definition nullable.hpp:583
constexpr nullable(value_type &&value, flag_type &&null_flag)
Constructor from value and flag.
Definition nullable.hpp:516
constexpr nullable() noexcept
Default constructor creating a null nullable.
Definition nullable.hpp:320
friend class nullable
Definition nullable.hpp:952
constexpr nullable(nullval_t) noexcept
Constructor from nullval_t creating a null nullable.
Definition nullable.hpp:338
constexpr nullable(U &&value, V &&null_flag)
Constructor from two forwarding references (for value types with const qualifiers).
Definition nullable.hpp:607
typename value_traits::rvalue_reference rvalue_reference
Definition nullable.hpp:299
constexpr nullable(value_type &&value, std::add_lvalue_reference_t< B > null_flag)
Constructor from moved value and flag reference.
Definition nullable.hpp:547
constexpr self_type & operator=(const self_type &rhs) noexcept
Default copy assignment operator.
Definition nullable.hpp:662
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:306
constexpr self_type & operator=(nullval_t) noexcept
Assignment from nullval_t, setting nullable to null state.
Definition nullable.hpp:623
typename flag_traits::rvalue_reference flag_rvalue_reference
Definition nullable.hpp:305
constexpr self_type & operator=(self_type &&rhs) noexcept
Default move assignment operator.
Definition nullable.hpp:708
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:532
nullable_traits< inner_const_reference > value_traits
Definition nullable.hpp:295
typename value_traits::const_rvalue_reference const_rvalue_reference
Definition nullable.hpp:300
Concepts used to disambiguate the nullable class constructors.
Definition nullable.hpp:186
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:215
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 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:115
std::add_lvalue_reference_t< value_type > reference
Definition nullable.hpp:112
std::add_lvalue_reference_t< std::add_const_t< value_type > > const_reference
Definition nullable.hpp:113
const value_type && const_rvalue_reference
Definition nullable.hpp:105
std::add_lvalue_reference_t< value_type > reference
Definition nullable.hpp:102
value_type && rvalue_reference
Definition nullable.hpp:104
std::add_lvalue_reference_t< std::add_const_t< value_type > > const_reference
Definition nullable.hpp:103
Sentinel type to indicate a nullable value is null.
Definition nullable.hpp:157
constexpr nullval_t(int)
Private constructor to prevent default construction.
Definition nullable.hpp:161
sparrow:: nullable< std::common_reference_t< TQual< T >, UQual< U > >, std::common_reference_t< TQual< TB >, UQual< UB > > > type