Global constant representing a null value for nullable objects.
Global constant representing a null value for nullable objects. This constant is used to construct nullable objects in a null state or to assign null to existing nullable objects.
#pragma once
#include <compare>
#include <concepts>
#include <exception>
#if defined(__cpp_lib_format)
# include <format>
# include <ostream>
#endif
#include <type_traits>
#include <variant>
#if defined(SPARROW_CONSTEXPR)
# error "SPARROW_CONSTEXPR already defined"
#endif
#if defined(__clang__) && not defined(_LIBCPP_VERSION)
# define SPARROW_CONSTEXPR
#else
# define SPARROW_CONSTEXPR constexpr
#endif
{
template <class T, mpl::boolean_like B>
class nullable;
template <class T>
struct is_nullable : std::false_type
{
};
template <class T, mpl::boolean_like B>
struct is_nullable<nullable<T, B>> : std::true_type
{
};
template <class T>
template <class N, class T>
concept nullable_of =
is_nullable_v<N> && std::same_as<typename N::value_type, T>;
template <class N, class T>
concept nullable_of_convertible_to =
is_nullable_v<N> && std::convertible_to<typename N::value_type, T>;
template <class RangeOfNullables>
concept range_of_nullables = std::ranges::range<RangeOfNullables>
&& is_nullable<std::ranges::range_value_t<RangeOfNullables>>::value;
template <class T>
struct nullable_traits
{
using reference = std::add_lvalue_reference_t<value_type>;
using const_reference = std::add_lvalue_reference_t<std::add_const_t<value_type>>;
};
template <class T>
struct nullable_traits<T&>
{
using value_type = T;
using reference = std::add_lvalue_reference_t<value_type>;
using const_reference = std::add_lvalue_reference_t<std::add_const_t<value_type>>;
using rvalue_reference = reference;
using const_rvalue_reference = const_reference;
};
class bad_nullable_access : public std::exception
{
public:
[[nodiscard]]
const char*
what()
const noexcept override
{
return message;
}
private:
static constexpr const char* message = "Invalid access to nullable underlying value";
};
struct nullval_t
{
{
}
};
inline constexpr nullval_t
nullval(0);
namespace impl
{
template <class T, class TArgs, class U, class UArgs>
concept both_constructible_from_cref = std::constructible_from<T, mpl::add_const_lvalue_reference_t<TArgs>>
and std::constructible_from<U, mpl::add_const_lvalue_reference_t<UArgs>>;
template <class To1, class From1, class To2, class From2>
concept both_convertible_from_cref = std::convertible_to<mpl::add_const_lvalue_reference_t<From1>, To1>
and std::convertible_to<mpl::add_const_lvalue_reference_t<From2>, To2>;
template <class T, class... Args>
concept constructible_from_one = (std::constructible_from<T, Args> || ...);
template <class T, class... Args>
concept convertible_from_one = (std::convertible_to<Args, T> || ...);
template <class T, class... Args>
concept initializable_from_one = constructible_from_one<T, Args...> || convertible_from_one<T, Args...>;
template <class T, class Arg>
concept initializable_from_refs = initializable_from_one<T, Arg&, const Arg&, Arg&&, const Arg&&>;
template <class To, class... Args>
concept assignable_from_one = (std::is_assignable_v<std::add_lvalue_reference<To>, Args> && ...);
template <class T, class Arg>
concept assignable_from_refs = assignable_from_one<T, Arg&, const Arg&, Arg&&, const Arg&&>;
template <class T, class U>
using conditional_ref_t = std::conditional_t<std::is_reference_v<T>,
const std::decay_t<U>&, std::decay_t<U>&&>;
template <class T, class Targs, class U, class UArgs>
concept both_constructible_from_cond_ref = std::constructible_from<T, conditional_ref_t<T, Targs>>
and std::constructible_from<U, conditional_ref_t<U, UArgs>>;
template <class To1, class From1, class To2, class From2>
concept both_convertible_from_cond_ref = std::convertible_to<conditional_ref_t<To1, From1>, To1>
and std::convertible_to<conditional_ref_t<To2, From2>, To2>;
template <class To1, class From1, class To2, class From2>
concept both_assignable_from_cref = std::is_assignable_v<
std::add_lvalue_reference_t<To1>,
and std::is_assignable_v<
std::add_lvalue_reference_t<To2>,
template <class To1, class From1, class To2, class From2>
concept both_assignable_from_cond_ref = std::is_assignable_v<
std::add_lvalue_reference_t<To1>,
and std::is_assignable_v<
std::add_lvalue_reference_t<To2>,
}
template <class T, mpl::boolean_like B = bool>
class nullable
{
public:
template <std::default_initializable U = T, std::default_initializable BB = B>
: m_value()
, m_null_flag(false)
{
}
template <std::default_initializable U = T, std::default_initializable BB = B>
: m_value()
, m_null_flag(false)
{
}
template <class U>
requires(not std::same_as<self_type, std::decay_t<U>> and std::constructible_from<T, U &&>)
explicit(not std::convertible_to<U&&, T>)
constexpr nullable(U&&
value)
noexcept(
noexcept(T(std::declval<U>()))
)
: m_value(std::forward<U>(
value))
, m_null_flag(true)
{
}
template <class TO, mpl::boolean_like BO>
requires(impl::both_constructible_from_cref<T, TO, B, BO>
and not impl::initializable_from_refs<T, nullable<TO, BO>>)
)
, m_null_flag(
rhs.null_flag())
{
}
#ifdef __clang__
template <class TO, mpl::boolean_like BO>
requires(impl::both_constructible_from_cref<T, TO, B, BO> and std::same_as<std::decay_t<T>, bool>)
)
, m_null_flag(
rhs.null_flag())
{
}
#endif
template <class TO, mpl::boolean_like BO>
requires(impl::both_constructible_from_cond_ref<T, TO, B, BO>
and not impl::initializable_from_refs<T, nullable<TO, BO>>)
)
: m_value(std::move(
rhs).
get())
{
}
#ifdef __clang__
template <class TO, mpl::boolean_like BO>
requires(impl::both_constructible_from_cond_ref<T, TO, B, BO>
and std::same_as<std::decay_t<T>, bool>)
)
: m_value(std::move(
rhs).
get())
{
}
#endif
: m_value(std::move(
value))
{
}
{
}
: m_value(std::move(
value))
{
}
{
}
{
m_null_flag = false;
return *this;
}
template <class TO>
requires(not std::same_as<self_type, TO> and std::assignable_from<std::add_lvalue_reference_t<T>, TO>)
{
m_value = std::forward<TO>(
rhs);
m_null_flag = true;
return *this;
}
{
m_null_flag =
rhs.null_flag();
return *this;
}
template <class TO, mpl::boolean_like BO>
requires(
impl::both_assignable_from_cref<T, TO, B, BO>
and not impl::initializable_from_refs<T, nullable<TO, BO>>
and not impl::assignable_from_refs<T, nullable<TO, BO>>
)
{
m_null_flag =
rhs.null_flag();
return *this;
}
{
m_value = std::move(
rhs).get();
m_null_flag = std::move(
rhs).null_flag();
return *this;
}
template <class TO, mpl::boolean_like BO>
requires(
impl::both_assignable_from_cond_ref<T, TO, B, BO>
and not impl::initializable_from_refs<T, nullable<TO, BO>>
and not impl::assignable_from_refs<T, nullable<TO, BO>>
)
{
m_value = std::move(
rhs).get();
m_null_flag = std::move(
rhs).null_flag();
return *this;
}
constexpr explicit operator bool() const noexcept;
[[nodiscard]]
constexpr bool has_value()
const noexcept;
template <class U>
template <class U>
private:
void throw_if_null() const;
T m_value;
B m_null_flag;
template <class TO, mpl::boolean_like BO>
};
template <class T, class B>
constexpr void swap(nullable<T, B>& lhs, nullable<T, B>& rhs)
noexcept;
template <class T, class B>
constexpr bool operator==(
const nullable<T, B>& lhs, nullval_t)
noexcept;
template <class T, mpl::boolean_like B>
constexpr std::strong_ordering
operator<=>(
const nullable<T, B>& lhs, nullval_t dummy)
noexcept;
template <class T, class B, class U>
constexpr bool operator==(const nullable<T, B>& lhs, const U& rhs) noexcept;
template <class T, class B, class U>
constexpr std::compare_three_way_result_t<T, U>
operator<=>(
const nullable<T, B>& lhs,
const U& rhs)
noexcept;
template <class T, class B, class U, class UB>
requires(mpl::weakly_equality_comparable_with<T, U>)
constexpr bool operator==(const nullable<T, B>& lhs, const nullable<U, UB>& rhs) noexcept;
template <class T, class B, std::three_way_comparable_with<T> U, class UB>
constexpr std::compare_three_way_result_t<T, U>
operator<=>(
const nullable<T, B>& lhs,
const nullable<U, UB>& rhs)
noexcept;
template <class T, mpl::boolean_like B = bool>
constexpr nullable<T, B>
make_nullable(T&& value, B&& flag =
true);
template <std::ranges::range R, typename T = typename std::ranges::range_value_t<R>::value_type>
requires(nullable_of<std::ranges::range_value_t<R>, T>)
template <class... T>
class nullable_variant : public std::variant<T...>
{
public:
using base_type::base_type;
constexpr explicit operator bool() const;
};
}
{
template <class T, mpl::boolean_like TB, class U, mpl::boolean_like UB, template <class> class TQual, template <class> class UQual>
requires std::common_reference_with<T, U> && std::common_reference_with<TB, UB>
{
using type = sparrow::
nullable<std::common_reference_t<TQual<T>, UQual<U>>, std::common_reference_t<TQual<TB>, UQual<UB>>>;
};
}
{
template <class T, mpl::boolean_like B>
{
return m_null_flag;
}
template <class T, mpl::boolean_like B>
{
return m_null_flag;
}
template <class T, mpl::boolean_like B>
{
return m_null_flag;
}
template <class T, mpl::boolean_like B>
{
return m_null_flag;
}
template <class T, mpl::boolean_like B>
{
if constexpr (std::is_reference_v<B>)
{
return m_null_flag;
}
else
{
return flag_rvalue_reference(m_null_flag);
}
}
template <class T, mpl::boolean_like B>
{
if constexpr (std::is_reference_v<B>)
{
return m_null_flag;
}
else
{
return flag_const_rvalue_reference(m_null_flag);
}
}
template <class T, mpl::boolean_like B>
{
return m_value;
}
template <class T, mpl::boolean_like B>
{
return m_value;
}
template <class T, mpl::boolean_like B>
{
if constexpr (std::is_reference_v<T>)
{
return m_value;
}
else
{
return rvalue_reference(m_value);
}
}
template <class T, mpl::boolean_like B>
{
if constexpr (std::is_reference_v<T>)
{
return m_value;
}
else
{
return const_rvalue_reference(m_value);
}
}
template <class T, mpl::boolean_like B>
{
throw_if_null();
return get();
}
template <class T, mpl::boolean_like B>
{
throw_if_null();
return get();
}
template <class T, mpl::boolean_like B>
{
throw_if_null();
return std::move(*this).get();
}
template <class T, mpl::boolean_like B>
{
throw_if_null();
return std::move(*this).get();
}
template <class T, mpl::boolean_like B>
template <class U>
{
return *this ? get() : value_type(std::forward<U>(default_value));
}
template <class T, mpl::boolean_like B>
template <class U>
{
return *this ? get() : value_type(std::forward<U>(default_value));
}
template <class T, mpl::boolean_like B>
{
using std::swap;
swap(m_value, other.m_value);
swap(m_null_flag, other.m_null_flag);
}
template <class T, mpl::boolean_like B>
{
m_null_flag = false;
}
template <class T, mpl::boolean_like B>
void nullable<T, B>::throw_if_null() const
{
if (!m_null_flag)
{
}
}
template <class T, class B>
{
lhs.swap(rhs);
}
template <class T, class B>
{
return !lhs;
}
template <class T, class B>
{
return lhs <=> false;
}
template <class T, class B, class U>
constexpr bool operator==(
const nullable<T, B>& lhs,
const U& rhs)
noexcept
{
return lhs && (lhs.get() == rhs);
}
template <class T, class B, class U>
{
return lhs ? lhs.get() <=> rhs : std::strong_ordering::less;
}
template <class T, class B, class U, class UB>
{
return rhs ? lhs == rhs.get() : !lhs;
}
template <class T, class B, std::three_way_comparable_with<T> U, class UB>
constexpr std::compare_three_way_result_t<T, U>
{
return (lhs && rhs) ? lhs.get() <=> rhs.get() : bool(lhs) <=> bool(rhs);
}
template <class T, mpl::boolean_like B>
{
}
template <std::ranges::range R, typename T>
{
for (auto nullable_value : range)
{
if (!nullable_value.has_value())
{
nullable_value.get() = default_value;
}
}
}
template <class... T>
{
base_type::operator=(rhs);
return *this;
}
template <class... T>
{
base_type::operator=(std::move(rhs));
return *this;
}
template <class... T>
{
return has_value();
}
template <class... T>
{
return std::visit(
[](const auto& v)
{
return v.has_value();
},
#if SPARROW_GCC_11_2_WORKAROUND
static_cast<const base_type&>(*this)
#else
*this
#endif
);
}
}
#if defined(__cpp_lib_format)
template <typename T, sparrow::mpl::boolean_like B>
struct std::formatter<
sparrow::nullable<T, B>>
{
constexpr auto parse(format_parse_context& ctx)
{
auto pos = ctx.begin();
while (pos != ctx.end() && *pos != '}')
{
m_format_string.push_back(*pos);
++pos;
}
m_format_string.push_back('}');
return pos;
}
auto format(const sparrow::nullable<T, B>& n, std::format_context& ctx) const
{
{
return std::vformat_to(ctx.out(), m_format_string, std::make_format_args(n.
get()));
}
else
{
return std::format_to(ctx.out(), "{}", "null");
}
}
std::string m_format_string = "{:";
};
template <typename T, sparrow::mpl::boolean_like B>
{
os << std::format("{}", value);
return os;
}
template <class... T>
struct std::formatter<
sparrow::nullable_variant<T...>>
{
constexpr auto parse(format_parse_context& ctx)
{
auto pos = ctx.begin();
while (pos != ctx.end() && *pos != '}')
{
m_format_string.push_back(*pos);
++pos;
}
m_format_string.push_back('}');
return pos;
}
auto format(const sparrow::nullable_variant<T...>& variant, std::format_context& ctx) const
{
{
return std::visit(
[&](const auto& value)
{
return std::vformat_to(ctx.out(), m_format_string, std::make_format_args(value));
},
variant
);
}
else
{
return std::format_to(ctx.out(), "{}", "null");
}
}
std::string m_format_string = "{:";
};
template <class... T>
{
os << std::format("{}", value);
return os;
}
template <>
struct std::formatter<
sparrow::nullval_t>
{
constexpr auto parse(format_parse_context& ctx)
{
return ctx.begin();
}
auto format(const sparrow::nullval_t&, std::format_context& ctx) const
{
return std::format_to(ctx.out(), "nullval");
}
};
#endif
{
constexpr std::string_view nullval_str = "nullval";
os << nullval_str;
return os;
}
#undef SPARROW_CONSTEXPR
Exception thrown when accessing a null nullable value.
const char * what() const noexcept override
Gets the descriptive error message.
bad_nullable_access & operator=(const bad_nullable_access &) noexcept=default
bad_nullable_access() noexcept=default
Variant of nullable types with has_value() convenience method.
constexpr nullable_variant(const nullable_variant &)=default
constexpr nullable_variant & operator=(const nullable_variant &)
Copy assignment operator.
constexpr bool has_value() const
Checks whether the active alternative contains a valid value.
std::variant< T... > base_type
nullable_traits< B > flag_traits
typename value_traits::const_reference const_reference
typename flag_traits::reference flag_reference
typename flag_traits::const_reference flag_const_reference
typename flag_traits::value_type flag_type
nullable< T, B > self_type
constexpr reference value() &
Gets mutable reference to the value with null checking.
constexpr bool has_value() const noexcept
Checks whether the nullable contains a valid value.
void swap(self_type &other) noexcept
Swaps this nullable with another.
constexpr reference get() &noexcept
Gets mutable reference to the stored value.
typename value_traits::rvalue_reference rvalue_reference
typename value_traits::value_type value_type
constexpr value_type value_or(U &&default_value) const &
Gets the value or a default if null (const version).
typename flag_traits::const_rvalue_reference flag_const_rvalue_reference
constexpr self_type & operator=(nullval_t) noexcept
Assignment from nullval_t, setting nullable to null state.
constexpr flag_reference null_flag() &noexcept
Gets mutable reference to the validity flag.
typename flag_traits::rvalue_reference flag_rvalue_reference
typename value_traits::reference reference
nullable_traits< T > value_traits
typename value_traits::const_rvalue_reference const_rvalue_reference
void reset() noexcept
Resets the nullable to null state.
std::conditional_t< std::is_reference_v< T >, const std::decay_t< U > &, std::decay_t< U > && > conditional_ref_t
typename add_const_lvalue_reference< T >::type add_const_lvalue_reference_t
Convenience alias for add_const_lvalue_reference.
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
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.
SPARROW_API void swap(ArrowArray &lhs, ArrowArray &rhs)
Swaps the contents of the two ArrowArray objects.
constexpr nullval_t nullval(0)
constexpr bool is_nullable_v
constexpr nullable< T, B > make_nullable(T &&value, B &&flag=true)
Creates a nullable object with deduced types.
std::ostream & operator<<(std::ostream &os, const sparrow::nullval_t &)
#define SPARROW_CONSTEXPR
const value_type && const_rvalue_reference
std::add_lvalue_reference_t< value_type > reference
value_type && rvalue_reference
std::add_lvalue_reference_t< std::add_const_t< value_type > > const_reference
Sentinel type to indicate a nullable value is null.
constexpr nullval_t(int)
Private constructor to prevent default construction.