sparrow 2.6.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 Arg>
220
221 // We prefer std::is_assignable_v to std::assignable_from<To, From> because
222 // std::assignable_from requires the existence of an implicit conversion
223 // from From to To
224 template <class To, class... Args>
225 concept assignable_from_one = (std::is_assignable_v<std::add_lvalue_reference<To>, Args> && ...);
226
227 template <class T, class Arg>
229
230 template <class T, class U>
231 using conditional_ref_t = std::conditional_t<std::is_reference_v<T>, const std::decay_t<U>&, std::decay_t<U>&&>;
232
233 template <class T, class Targs, class U, class UArgs>
234 concept both_constructible_from_cond_ref = std::constructible_from<T, conditional_ref_t<T, Targs>>
235 and std::constructible_from<U, conditional_ref_t<U, UArgs>>;
236
237 template <class To1, class From1, class To2, class From2>
238 concept both_convertible_from_cond_ref = std::convertible_to<conditional_ref_t<To1, From1>, To1>
239 and std::convertible_to<conditional_ref_t<To2, From2>, To2>;
240
241 template <class To1, class From1, class To2, class From2>
242 concept both_assignable_from_cref = std::is_assignable_v<
243 std::add_lvalue_reference_t<To1>,
245 and std::is_assignable_v<
246 std::add_lvalue_reference_t<To2>,
248
249 template <class To1, class From1, class To2, class From2>
250 concept both_assignable_from_cond_ref = std::is_assignable_v<
251 std::add_lvalue_reference_t<To1>,
253 and std::is_assignable_v<
254 std::add_lvalue_reference_t<To2>,
256
265 template <class T>
267 {
268 using type = std::remove_cvref_t<T>;
269 };
270
271 template <class T>
273
277 template <class N>
281
285 template <class X, class... Ts>
286 inline constexpr bool unique_alternative = ((0 + ... + int(std::same_as<X, Ts>)) == 1);
287
294 template <class Source, class Dest>
296 : materialized_mapping<typename Source::base_type, typename Dest::base_type>
297 {
298 };
299
300 template <class... U, class... T>
301 struct materialized_mapping<std::variant<U...>, std::variant<T...>>
302 : std::bool_constant<(unique_alternative<materialized_nullable_t<U>, T...> and ...)>
303 {
304 };
305
313 template <class Source, class Dest>
315
319 template <class Dest, class Source>
320 constexpr Dest materialize_variant(const Source& source)
321 {
322 return std::visit(
323 []<class N>(const N& value) -> Dest
324 {
325 using destination_nullable = materialized_nullable_t<N>;
326 if constexpr (std::constructible_from<destination_nullable, const N&>)
327 {
328 return Dest(std::in_place_type<destination_nullable>, value);
329 }
330 else
331 {
332 // Owning counterparts not constructible from the view itself (vector from span).
333 return Dest(
334 std::in_place_type<destination_nullable>,
335 typename destination_nullable::value_type(
336 value.get().begin(),
337 value.get().end()
338 ),
339 value.has_value()
340 );
341 }
342 },
343 static_cast<const typename Source::base_type&>(source)
344 );
345 }
346 }
347
394 template <class T, mpl::boolean_like B = bool>
396 {
397 public:
398
413
425 template <std::default_initializable U = T, std::default_initializable BB = B>
426 constexpr nullable() noexcept
427 : m_value()
428 , m_null_flag(false)
429 {
430 }
431
443 template <std::default_initializable U = T, std::default_initializable BB = B>
444 constexpr nullable(nullval_t) noexcept
445 : m_value()
446 , m_null_flag(false)
447 {
448 }
449
462 template <class U>
463 requires(not std::same_as<self_type, std::decay_t<U>> and std::constructible_from<T, U &&>)
464 explicit(not std::convertible_to<U&&, T>) constexpr nullable(U&& value) noexcept(
465 noexcept(T(std::declval<U>()))
466 )
467 : m_value(std::forward<U>(value))
468 , m_null_flag(true)
469 {
470 }
471
482 constexpr nullable(const self_type& rhs) = default;
483
498 template <class TO, mpl::boolean_like BO>
503 )
504 : m_value(rhs.get())
505 , m_null_flag(rhs.null_flag())
506 {
507 }
508
509#ifdef __clang__
530 template <class TO, mpl::boolean_like BO>
531 requires(impl::both_constructible_from_cref<T, TO, B, BO> and std::same_as<std::decay_t<T>, bool>)
533 const nullable<TO, BO>& rhs
534 )
535 : m_value(rhs.get())
536 , m_null_flag(rhs.null_flag())
537 {
538 }
539#endif
540
551 constexpr nullable(self_type&& rhs) noexcept = default;
552
567 template <class TO, mpl::boolean_like BO>
572 )
573 : m_value(std::move(rhs).get())
574 , m_null_flag(std::move(rhs).null_flag())
575 {
576 }
577
578#ifdef __clang__
600 template <class TO, mpl::boolean_like BO>
602 and std::same_as<std::decay_t<T>, bool>)
605 )
606 : m_value(std::move(rhs).get())
607 , m_null_flag(std::move(rhs).null_flag())
608 {
609 }
610#endif
611
623 : m_value(std::move(value))
624 , m_null_flag(std::move(null_flag))
625 {
626 }
627
638 constexpr nullable(std::add_lvalue_reference_t<T> value, std::add_lvalue_reference_t<B> null_flag)
639 : m_value(value)
640 , m_null_flag(null_flag)
641 {
642 }
643
653 constexpr nullable(value_type&& value, std::add_lvalue_reference_t<B> null_flag)
654 : m_value(std::move(value))
655 , m_null_flag(null_flag)
656 {
657 }
658
668 constexpr nullable(std::add_lvalue_reference_t<T> value, flag_type&& null_flag)
669 : m_value(value)
670 , m_null_flag(std::move(null_flag))
671 {
672 }
673
683 template <class U, class V>
684 requires(std::same_as<std::remove_cvref_t<U>, T> && std::same_as<std::remove_cvref_t<V>, B>
685 && std::is_const_v<std::remove_reference_t<U>>
686 && not std::is_const_v<std::remove_reference_t<V>> && not std::is_reference_v<T>
687 && not std::is_reference_v<B> && std::is_lvalue_reference_v<U &&>
688 && std::is_lvalue_reference_v<V &&>)
689 constexpr nullable(U& value, V& null_flag)
690 : m_value(value)
691 , m_null_flag(null_flag)
692 {
693 }
694
708 template <class U, class V>
709 requires(std::same_as<std::remove_cvref_t<U>, T> && std::same_as<std::remove_cvref_t<V>, B>
710 && not std::is_reference_v<T> && not std::is_reference_v<B>
711 && (std::is_const_v<std::remove_reference_t<U>>
712 || std::is_const_v<std::remove_reference_t<V>>) )
713 constexpr nullable(U&& value, V&& null_flag)
714 : m_value(std::forward<U>(value))
715 , m_null_flag(std::forward<V>(null_flag))
716 {
717 }
718
729 constexpr self_type& operator=(nullval_t) noexcept
730 {
731 m_null_flag = false;
732 return *this;
733 }
734
748 template <class TO>
749 requires(not std::same_as<self_type, TO> and std::assignable_from<std::add_lvalue_reference_t<T>, TO>)
750 constexpr self_type& operator=(TO&& rhs) noexcept(
751 std::is_nothrow_assignable_v<std::add_lvalue_reference_t<T>, TO>
752 && std::is_nothrow_assignable_v<std::add_lvalue_reference_t<B>, bool>
753 )
754 {
755 m_value = std::forward<TO>(rhs);
756 m_null_flag = true;
757 return *this;
758 }
759
771 constexpr self_type& operator=(const self_type& rhs) noexcept(
772 std::is_nothrow_assignable_v<std::add_lvalue_reference_t<T>, const_reference>
773 && std::is_nothrow_assignable_v<std::add_lvalue_reference_t<B>, flag_const_reference>
774 )
775 {
776 m_value = rhs.get();
777 m_null_flag = rhs.null_flag();
778 return *this;
779 }
780
796 template <class TO, mpl::boolean_like BO>
797 requires(
801 )
802 constexpr self_type& operator=(const nullable<TO, BO>& rhs) noexcept(
803 std::is_nothrow_assignable_v<std::add_lvalue_reference_t<T>, typename nullable<TO, BO>::const_reference>
804 && std::is_nothrow_assignable_v<std::add_lvalue_reference_t<B>, typename nullable<TO, BO>::flag_const_reference>
805 )
806 {
807 m_value = rhs.get();
808 m_null_flag = rhs.null_flag();
809 return *this;
810 }
811
823 constexpr self_type& operator=(self_type&& rhs) noexcept(
824 std::is_nothrow_assignable_v<std::add_lvalue_reference_t<T>, rvalue_reference>
825 && std::is_nothrow_assignable_v<std::add_lvalue_reference_t<B>, flag_rvalue_reference>
826 )
827 {
828 m_value = std::move(rhs).get();
829 m_null_flag = std::move(rhs).null_flag();
830 return *this;
831 }
832
848 template <class TO, mpl::boolean_like BO>
849 requires(
853 )
854 constexpr self_type& operator=(nullable<TO, BO>&& rhs) noexcept(
855 std::is_nothrow_assignable_v<std::add_lvalue_reference_t<T>, typename nullable<TO, BO>::rvalue_reference>
856 && std::is_nothrow_assignable_v<std::add_lvalue_reference_t<B>, typename nullable<TO, BO>::flag_rvalue_reference>
857 )
858 {
859 m_value = std::move(rhs).get();
860 m_null_flag = std::move(rhs).null_flag();
861 return *this;
862 }
863
872 constexpr explicit operator bool() const noexcept;
873
882 [[nodiscard]] constexpr bool has_value() const noexcept;
883
892 [[nodiscard]] constexpr flag_reference null_flag() & noexcept;
893
901 [[nodiscard]] constexpr flag_const_reference null_flag() const& noexcept;
902
911 [[nodiscard]] constexpr flag_rvalue_reference null_flag() && noexcept;
912
921 [[nodiscard]] constexpr flag_const_rvalue_reference null_flag() const&& noexcept;
922
932 [[nodiscard]] constexpr reference get() & noexcept;
933
942 [[nodiscard]] constexpr const_reference get() const& noexcept;
943
952 [[nodiscard]] constexpr rvalue_reference get() && noexcept;
953
962 [[nodiscard]] constexpr const_rvalue_reference get() const&& noexcept;
963
974 [[nodiscard]] constexpr reference value() &;
975
986 [[nodiscard]] constexpr const_reference value() const&;
987
999 [[nodiscard]] constexpr rvalue_reference value() &&;
1000
1011 [[nodiscard]] constexpr const_rvalue_reference value() const&&;
1012
1024 template <class U>
1025 [[nodiscard]] constexpr value_type value_or(U&& default_value) const&;
1026
1038 template <class U>
1039 [[nodiscard]] constexpr value_type value_or(U&& default_value) &&;
1040
1049 void swap(self_type& other) noexcept;
1050
1058 void reset() noexcept;
1059
1060 private:
1061
1067 void throw_if_null() const;
1068
1069 T m_value;
1070 B m_null_flag;
1071
1072 template <class TO, mpl::boolean_like BO>
1073 friend class nullable;
1074 };
1075
1087 template <class T, class B>
1088 constexpr void swap(nullable<T, B>& lhs, nullable<T, B>& rhs) noexcept;
1089
1101 template <class T, class B>
1102 constexpr bool operator==(const nullable<T, B>& lhs, nullval_t) noexcept;
1103
1116 template <class T, mpl::boolean_like B>
1117 constexpr std::strong_ordering operator<=>(const nullable<T, B>& lhs, nullval_t dummy) noexcept;
1118
1132 template <class T, class B, class U>
1133 requires(!is_nullable_v<U> && mpl::weakly_equality_comparable_with<T, U>)
1134 constexpr bool operator==(const nullable<T, B>& lhs, const U& rhs) noexcept;
1135
1150 template <class T, class B, class U>
1151 requires(!is_nullable_v<U> && std::three_way_comparable_with<U, T>)
1152 constexpr std::compare_three_way_result_t<T, U>
1153 operator<=>(const nullable<T, B>& lhs, const U& rhs) noexcept;
1154
1170 template <class T, class B, class U, class UB>
1171 requires(mpl::weakly_equality_comparable_with<T, U>)
1172 constexpr bool operator==(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept;
1173
1190 template <class T, class B, std::three_way_comparable_with<T> U, class UB>
1191 constexpr std::compare_three_way_result_t<T, U>
1192 operator<=>(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept;
1193
1206 template <class T, mpl::boolean_like B = bool>
1207 constexpr nullable<T, B> make_nullable(T&& value, B&& flag = true);
1208
1222 template <std::ranges::range R, typename T = typename std::ranges::range_value_t<R>::value_type>
1223 requires(nullable_of<std::ranges::range_value_t<R>, T>)
1224 constexpr void zero_null_values(R& range, const T& default_value = T{});
1225
1239 template <class... T>
1240 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1241 class nullable_variant : public std::variant<T...>
1242 {
1243 public:
1244
1245 using base_type = std::variant<T...>;
1246 using base_type::base_type;
1247
1248 template <class U, mpl::boolean_like B>
1249 requires((std::same_as<nullable<U, B>, T> || ...))
1250 constexpr nullable_variant(const nullable<U, B>& value)
1251 : base_type(std::in_place_type<nullable<U, B>>, value)
1252 {
1253 }
1254
1268 template <class... U>
1271 : base_type(impl::materialize_variant<base_type>(rhs))
1272 {
1273 }
1274
1287 template <class... U>
1290 {
1291 return *this = nullable_variant(rhs);
1292 }
1293
1294 constexpr nullable_variant(const nullable_variant&) = default;
1295 constexpr nullable_variant(nullable_variant&&) noexcept = default;
1296
1306 constexpr nullable_variant& operator=(const nullable_variant&);
1307
1317 constexpr nullable_variant& operator=(nullable_variant&&) noexcept;
1318
1326 constexpr explicit operator bool() const;
1327
1335 constexpr bool has_value() const;
1336 };
1337
1338 template <class T>
1339 struct is_nullable_variant : std::false_type
1340 {
1341 };
1342
1343 template <class... T>
1344 struct is_nullable_variant<nullable_variant<T...>> : std::true_type
1345 {
1346 };
1347
1348 template <class T>
1350}
1351
1352namespace std
1353{
1354 namespace mpl = sparrow::mpl;
1355
1356 // Specialization of basic_common_reference for nullable proxies so
1357 // we can use ranges algorithm on iterators returning nullable
1358 template <class T, mpl::boolean_like TB, class U, mpl::boolean_like UB, template <class> class TQual, template <class> class UQual>
1359 requires std::common_reference_with<T, U> && std::common_reference_with<TB, UB>
1360 struct basic_common_reference<sparrow::nullable<T, TB>, sparrow::nullable<U, UB>, TQual, UQual>
1361 {
1362 using type = sparrow::
1363 nullable<std::common_reference_t<TQual<T>, UQual<U>>, std::common_reference_t<TQual<TB>, UQual<UB>>>;
1364 };
1365}
1366
1367namespace sparrow
1368{
1369 /***************************
1370 * nullable implementation *
1371 ***************************/
1372
1373 template <class T, mpl::boolean_like B>
1374 constexpr nullable<T, B>::operator bool() const noexcept
1375 {
1376 return m_null_flag;
1377 }
1378
1379 template <class T, mpl::boolean_like B>
1380 constexpr bool nullable<T, B>::has_value() const noexcept
1381 {
1382 return m_null_flag;
1383 }
1384
1385 template <class T, mpl::boolean_like B>
1386 constexpr auto nullable<T, B>::null_flag() & noexcept -> flag_reference
1387 {
1388 return m_null_flag;
1389 }
1390
1391 template <class T, mpl::boolean_like B>
1392 constexpr auto nullable<T, B>::null_flag() const& noexcept -> flag_const_reference
1393 {
1394 return m_null_flag;
1395 }
1396
1397 template <class T, mpl::boolean_like B>
1398 constexpr auto nullable<T, B>::null_flag() && noexcept -> flag_rvalue_reference
1399 {
1400 if constexpr (std::is_reference_v<B>)
1401 {
1402 return m_null_flag;
1403 }
1404 else
1405 {
1406 return flag_rvalue_reference(m_null_flag);
1407 }
1408 }
1409
1410 template <class T, mpl::boolean_like B>
1411 constexpr auto nullable<T, B>::null_flag() const&& noexcept -> flag_const_rvalue_reference
1412 {
1413 if constexpr (std::is_reference_v<B>)
1414 {
1415 return m_null_flag;
1416 }
1417 else
1418 {
1419 return flag_const_rvalue_reference(m_null_flag);
1420 }
1421 }
1422
1423 template <class T, mpl::boolean_like B>
1424 constexpr auto nullable<T, B>::get() & noexcept -> reference
1425 {
1426 return m_value;
1427 }
1428
1429 template <class T, mpl::boolean_like B>
1430 constexpr auto nullable<T, B>::get() const& noexcept -> const_reference
1431 {
1432 return m_value;
1433 }
1434
1435 template <class T, mpl::boolean_like B>
1436 constexpr auto nullable<T, B>::get() && noexcept -> rvalue_reference
1437 {
1438 if constexpr (std::is_reference_v<T>)
1439 {
1440 return m_value;
1441 }
1442 else
1443 {
1444 return rvalue_reference(m_value);
1445 }
1446 }
1447
1448 template <class T, mpl::boolean_like B>
1449 constexpr auto nullable<T, B>::get() const&& noexcept -> const_rvalue_reference
1450 {
1451 if constexpr (std::is_reference_v<T>)
1452 {
1453 return m_value;
1454 }
1455 else
1456 {
1457 return const_rvalue_reference(m_value);
1458 }
1459 }
1460
1461 template <class T, mpl::boolean_like B>
1462 constexpr auto nullable<T, B>::value() & -> reference
1463 {
1464 throw_if_null();
1465 return get();
1466 }
1467
1468 template <class T, mpl::boolean_like B>
1469 constexpr auto nullable<T, B>::value() const& -> const_reference
1470 {
1471 throw_if_null();
1472 return get();
1473 }
1474
1475 template <class T, mpl::boolean_like B>
1477 {
1478 throw_if_null();
1479 return std::move(*this).get();
1480 }
1481
1482 template <class T, mpl::boolean_like B>
1484 {
1485 throw_if_null();
1486 return std::move(*this).get();
1487 }
1488
1489 template <class T, mpl::boolean_like B>
1490 template <class U>
1491 constexpr auto nullable<T, B>::value_or(U&& default_value) const& -> value_type
1492 {
1493 return *this ? get() : value_type(std::forward<U>(default_value));
1494 }
1495
1496 template <class T, mpl::boolean_like B>
1497 template <class U>
1498 constexpr auto nullable<T, B>::value_or(U&& default_value) && -> value_type
1499 {
1500 return *this ? get() : value_type(std::forward<U>(default_value));
1501 }
1502
1503 template <class T, mpl::boolean_like B>
1504 void nullable<T, B>::swap(self_type& other) noexcept
1505 {
1506 using std::swap;
1507 swap(m_value, other.m_value);
1508 swap(m_null_flag, other.m_null_flag);
1509 }
1510
1511 template <class T, mpl::boolean_like B>
1513 {
1514 m_null_flag = false;
1515 }
1516
1517 template <class T, mpl::boolean_like B>
1518 void nullable<T, B>::throw_if_null() const
1519 {
1520 if (!m_null_flag)
1521 {
1522 throw bad_nullable_access{};
1523 }
1524 }
1525
1526 template <class T, class B>
1527 constexpr void swap(nullable<T, B>& lhs, nullable<T, B>& rhs) noexcept
1528 {
1529 lhs.swap(rhs);
1530 }
1531
1532 template <class T, class B>
1533 constexpr bool operator==(const nullable<T, B>& lhs, nullval_t) noexcept
1534 {
1535 return !lhs;
1536 }
1537
1538 template <class T, class B>
1539 constexpr std::strong_ordering operator<=>(const nullable<T, B>& lhs, nullval_t) noexcept
1540 {
1541 return lhs <=> false;
1542 }
1543
1544 template <class T, class B, class U>
1545 requires(!is_nullable_v<U> && mpl::weakly_equality_comparable_with<T, U>)
1546 constexpr bool operator==(const nullable<T, B>& lhs, const U& rhs) noexcept
1547 {
1548 return lhs && (lhs.get() == rhs);
1549 }
1550
1551 template <class T, class B, class U>
1552 requires(!is_nullable_v<U> && std::three_way_comparable_with<U, T>)
1553 constexpr std::compare_three_way_result_t<T, U> operator<=>(const nullable<T, B>& lhs, const U& rhs) noexcept
1554 {
1555 return lhs ? lhs.get() <=> rhs : std::strong_ordering::less;
1556 }
1557
1558 template <class T, class B, class U, class UB>
1559 requires(mpl::weakly_equality_comparable_with<T, U>)
1560 constexpr bool operator==(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept
1561 {
1562 return rhs ? lhs == rhs.get() : !lhs;
1563 }
1564
1565 template <class T, class B, std::three_way_comparable_with<T> U, class UB>
1566 constexpr std::compare_three_way_result_t<T, U>
1567 operator<=>(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept
1568 {
1569 return (lhs && rhs) ? lhs.get() <=> rhs.get() : bool(lhs) <=> bool(rhs);
1570 }
1571
1572 template <class T, mpl::boolean_like B>
1573 constexpr nullable<T, B> make_nullable(T&& value, B&& flag)
1574 {
1575 return nullable<T, B>(std::forward<T>(value), std::forward<B>(flag));
1576 }
1577
1578 template <std::ranges::range R, typename T>
1579 requires(nullable_of<std::ranges::range_value_t<R>, T>)
1580 constexpr void zero_null_values(R& range, const T& default_value)
1581 {
1582 for (auto nullable_value : range)
1583 {
1584 if (!nullable_value.has_value())
1585 {
1586 nullable_value.get() = default_value;
1587 }
1588 }
1589 }
1590
1591 /***********************************
1592 * nullable_variant implementation *
1593 ***********************************/
1594
1595 template <class... T>
1596 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1597 constexpr nullable_variant<T...>& nullable_variant<T...>::operator=(const nullable_variant& rhs)
1598 {
1599 base_type::operator=(rhs);
1600 return *this;
1601 }
1602
1603 template <class... T>
1604 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1605 constexpr nullable_variant<T...>& nullable_variant<T...>::operator=(nullable_variant&& rhs) noexcept
1606 {
1607 base_type::operator=(std::move(rhs));
1608 return *this;
1609 }
1610
1611 template <class... T>
1612 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1613 constexpr nullable_variant<T...>::operator bool() const
1614 {
1615 return has_value();
1616 }
1617
1618 template <class... T>
1619 requires(sizeof...(T) > 0 && (is_nullable_v<T> && ...))
1620 constexpr bool nullable_variant<T...>::has_value() const
1621 {
1622 return std::visit(
1623 [](const auto& v)
1624 {
1625 return v.has_value();
1626 },
1627#if SPARROW_GCC_11_2_WORKAROUND
1628 static_cast<const base_type&>(*this)
1629#else
1630 *this
1631#endif
1632 );
1633 }
1634}
1635
1636#if defined(__cpp_lib_format)
1637
1638template <typename T, sparrow::mpl::boolean_like B>
1639struct std::formatter<sparrow::nullable<T, B>>
1640{
1641 constexpr auto parse(format_parse_context& ctx)
1642 {
1643 auto pos = ctx.begin();
1644 while (pos != ctx.end() && *pos != '}')
1645 {
1646 m_format_string.push_back(*pos);
1647 ++pos;
1648 }
1649 m_format_string.push_back('}');
1650 return pos;
1651 }
1652
1653 auto format(const sparrow::nullable<T, B>& n, std::format_context& ctx) const
1654 {
1655 if (n.has_value())
1656 {
1657 return std::vformat_to(ctx.out(), m_format_string, std::make_format_args(n.get()));
1658 }
1659 else
1660 {
1661 return std::format_to(ctx.out(), "{}", "null");
1662 }
1663 }
1664
1665 std::string m_format_string = "{:";
1666};
1667
1668namespace sparrow
1669{
1670 template <typename T, mpl::boolean_like B>
1671 std::ostream& operator<<(std::ostream& os, const nullable<T, B>& value)
1672 {
1673 os << std::format("{}", value);
1674 return os;
1675 }
1676}
1677
1678template <class... T>
1679struct std::formatter<sparrow::nullable_variant<T...>>
1680{
1681 constexpr auto parse(format_parse_context& ctx)
1682 {
1683 auto pos = ctx.begin();
1684 while (pos != ctx.end() && *pos != '}')
1685 {
1686 m_format_string.push_back(*pos);
1687 ++pos;
1688 }
1689 m_format_string.push_back('}');
1690 return pos;
1691 }
1692
1693 auto format(const sparrow::nullable_variant<T...>& variant, std::format_context& ctx) const
1694 {
1695 if (variant.has_value())
1696 {
1697 return std::visit(
1698 [&](const auto& value)
1699 {
1700 return std::vformat_to(ctx.out(), m_format_string, std::make_format_args(value));
1701 },
1702 variant
1703 );
1704 }
1705 else
1706 {
1707 return std::vformat_to(ctx.out(), m_format_string, std::make_format_args("null"));
1708 }
1709 }
1710
1711 std::string m_format_string = "{:";
1712};
1713
1714namespace sparrow
1715{
1716 template <class... T>
1717 std::ostream& operator<<(std::ostream& os, const nullable_variant<T...>& value)
1718 {
1719 os << std::format("{}", value);
1720 return os;
1721 }
1722}
1723
1724template <>
1725struct std::formatter<sparrow::nullval_t>
1726{
1727 constexpr auto parse(format_parse_context& ctx)
1728 {
1729 return ctx.begin(); // Simple implementation
1730 }
1731
1732 auto format(const sparrow::nullval_t&, std::format_context& ctx) const
1733 {
1734 return std::format_to(ctx.out(), "nullval");
1735 }
1736};
1737
1738#endif
1739
1740namespace sparrow
1741{
1742 inline std::ostream& operator<<(std::ostream& os, const nullval_t&)
1743 {
1744 constexpr std::string_view nullval_str = "nullval";
1745 os << nullval_str;
1746 return os;
1747 }
1748}
1749
1750#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(const nullable_variant< U... > &rhs)
Converting constructor from another nullable variant.
constexpr nullable_variant(nullable_variant &&) noexcept=default
constexpr nullable_variant & operator=(const nullable_variant< U... > &rhs)
Converting assignment operator from another nullable variant.
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:668
constexpr nullable(U &value, V &null_flag)
Constructor from const value and non-const flag reference (for non-reference types only).
Definition nullable.hpp:689
constexpr nullable(value_type &&value, flag_type &&null_flag)
Constructor from value and flag.
Definition nullable.hpp:622
constexpr nullable() noexcept
Default constructor creating a null nullable.
Definition nullable.hpp:426
friend class nullable
constexpr nullable(nullval_t) noexcept
Constructor from nullval_t creating a null nullable.
Definition nullable.hpp:444
constexpr nullable(U &&value, V &&null_flag)
Constructor from two forwarding references (for value types with const qualifiers).
Definition nullable.hpp:713
typename value_traits::rvalue_reference rvalue_reference
Definition nullable.hpp:405
constexpr nullable(value_type &&value, std::add_lvalue_reference_t< B > null_flag)
Constructor from moved value and flag reference.
Definition nullable.hpp:653
constexpr self_type & operator=(const self_type &rhs) noexcept(std::is_nothrow_assignable_v< std::add_lvalue_reference_t< T >, const_reference > &&std::is_nothrow_assignable_v< std::add_lvalue_reference_t< B >, flag_const_reference >)
Default copy assignment operator.
Definition nullable.hpp:771
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:412
constexpr self_type & operator=(nullval_t) noexcept
Assignment from nullval_t, setting nullable to null state.
Definition nullable.hpp:729
constexpr self_type & operator=(self_type &&rhs) noexcept(std::is_nothrow_assignable_v< std::add_lvalue_reference_t< T >, rvalue_reference > &&std::is_nothrow_assignable_v< std::add_lvalue_reference_t< B >, flag_rvalue_reference >)
Default move assignment operator.
Definition nullable.hpp:823
typename flag_traits::rvalue_reference flag_rvalue_reference
Definition nullable.hpp:411
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:638
nullable_traits< inner_const_reference > value_traits
Definition nullable.hpp:401
typename value_traits::const_rvalue_reference const_rvalue_reference
Definition nullable.hpp:406
Concepts used to disambiguate the nullable class constructors.
Definition nullable.hpp:208
Whether every alternative of Source has exactly one owning counterpart in Dest, so that a value of So...
Definition nullable.hpp:314
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
nullable< materialized_t< typename N::value_type >, materialized_t< typename N::flag_type > > materialized_nullable_t
Owning counterpart of a nullable: materialized value and flag.
Definition nullable.hpp:278
std::conditional_t< std::is_reference_v< T >, const std::decay_t< U > &, std::decay_t< U > && > conditional_ref_t
Definition nullable.hpp:231
constexpr Dest materialize_variant(const Source &source)
Converts the active alternative of a nullable variant to its owning counterpart.
Definition nullable.hpp:320
constexpr bool unique_alternative
Whether X is the type of exactly one of Ts.
Definition nullable.hpp:286
typename materialized< T >::type materialized_t
Definition nullable.hpp:272
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
Mapping of the alternatives of Source onto their owning counterpart in Dest.
Definition nullable.hpp:297
Owning counterpart of the type stored by a nullable.
Definition nullable.hpp:267
std::remove_cvref_t< T > type
Definition nullable.hpp:268
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