From 5a8d37bbe303fd779d9185abb20c3b344a8e42d8 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Thu, 4 Jul 2024 01:00:35 +0200 Subject: [PATCH 01/34] sparrow::optional implementation --- CMakeLists.txt | 1 + include/sparrow/mp_utils.hpp | 9 + include/sparrow/optional.hpp | 476 ++++++++++++++++++++++++++ test/CMakeLists.txt | 1 + test/test_optional.cpp | 629 +++++++++++++++++++++++++++++++++++ 5 files changed, 1116 insertions(+) create mode 100644 include/sparrow/optional.hpp create mode 100644 test/test_optional.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 53a872708..7f9f7b582 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,7 @@ set(SPARROW_HEADERS ${SPARROW_INCLUDE_DIR}/sparrow/memory.hpp ${SPARROW_INCLUDE_DIR}/sparrow/mp_utils.hpp ${SPARROW_INCLUDE_DIR}/sparrow/null_layout.hpp + ${SPARROW_INCLUDE_DIR}/sparrow/optional.hpp ${SPARROW_INCLUDE_DIR}/sparrow/sparrow_version.hpp ${SPARROW_INCLUDE_DIR}/sparrow/typed_array.hpp ${SPARROW_INCLUDE_DIR}/sparrow/variable_size_binary_layout.hpp diff --git a/include/sparrow/mp_utils.hpp b/include/sparrow/mp_utils.hpp index 3c9f11189..804ae2fb3 100644 --- a/include/sparrow/mp_utils.hpp +++ b/include/sparrow/mp_utils.hpp @@ -355,6 +355,15 @@ namespace sparrow::mpl ////////////////////////////////////////////////// //// Miscellaneous /////////////////////////////// + template + struct add_const_lvalue_reference + : std::add_lvalue_reference> + { + }; + + template + using add_const_lvalue_reference_t = typename add_const_lvalue_reference::type; + template struct constify : std::conditional { diff --git a/include/sparrow/optional.hpp b/include/sparrow/optional.hpp new file mode 100644 index 000000000..d3cc0ba9d --- /dev/null +++ b/include/sparrow/optional.hpp @@ -0,0 +1,476 @@ +// Copyright 2024 Man Group Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include + +#include "sparrow/mp_utils.hpp" + +namespace sparrow +{ + template + class optional; + + template + struct optional_traits + { + using value_type = std::decay_t; + using reference = value_type&; + using const_reference = const value_type&; + using rvalue_reference = value_type&&; + using const_rvalue_reference = const value_type&&; + }; + + template + struct optional_traits + { + using value_type = std::decay_t; + using unref_type = T; + using reference = T&; + using const_reference = std::add_lvalue_reference_t>; + using rvalue_reference = T&&; + using const_rvalue_reference = std::add_rvalue_reference_t>; + }; + + namespace util + { + template + concept both_constructible_from_cref = + std::constructible_from> and + std::constructible_from>; + + template + concept both_convertible_from_cref = + std::convertible_to, To1> and + std::convertible_to, To2>; + + template + concept constructible_from_one = + (std::constructible_from || ...); + + template + concept convertible_from_one = + (std::convertible_to || ...); + + template + concept initializable_from_one = + constructible_from_one || + convertible_from_one; + + template + concept initializable_from_refs = + initializable_from_one; + + template + concept assignable_from_one = + (std::assignable_from, Args> && ...); + + template + concept assignable_from_refs = + assignable_from_one; + + template + using conditional_ref_t = std::conditional_t, const std::decay_t&, std::decay_t&&>; + + template + concept both_constructible_from_cond_ref = + std::constructible_from> and + std::constructible_from>; + + template + concept both_convertible_from_cond_ref = + std::convertible_to, To1> and + std::convertible_to, To2>; + + template + concept both_assignable_from_cref = + std::assignable_from, mpl::add_const_lvalue_reference_t> and + std::assignable_from, mpl::add_const_lvalue_reference_t&>; + + template + concept both_assignable_from_cond_ref = + std::assignable_from, conditional_ref_t> and + std::assignable_from, conditional_ref_t>; + + template + struct is_optional : std::false_type + { + }; + + template + struct is_optional> : std::true_type + { + }; + + template + static constexpr bool is_optional_v = is_optional::value; + } + + template + class optional + { + public: + + using self_type = optional; + using traits = optional_traits; + using value_type = typename traits::value_type; + using reference = typename traits::reference; + using const_reference = typename traits::const_reference; + using rvalue_reference = typename traits::rvalue_reference; + using const_rvalue_reference = typename traits::const_rvalue_reference; + using flag_type = std::decay_t; + + constexpr optional() noexcept + : m_value() + , m_flag(false) + { + } + + constexpr optional(std::nullopt_t) noexcept + : m_value() + , m_flag(false) + { + } + + template + requires ( + not std::same_as> and + std::constructible_from + ) + explicit (not std::convertible_to) + constexpr optional(U&& value) + : m_value(std::forward(value)) + , m_flag(true) + { + } + + constexpr optional(const self_type&) = default; + + template + requires ( + util::both_constructible_from_cref and + not util::initializable_from_refs> + ) + explicit(not util::both_convertible_from_cref) + constexpr optional(const optional& rhs) + : m_value(rhs.m_value) + , m_flag(rhs.m_flag) + { + } + + constexpr optional(self_type&&) noexcept = default; + + template + requires ( + util::both_constructible_from_cond_ref and + not util::initializable_from_refs> + ) + explicit(not util::both_convertible_from_cond_ref) + constexpr optional(optional&& rhs) + : m_value(std::move(rhs.m_value)) + , m_flag(std::move(rhs.m_flag)) + { + } + + constexpr optional(value_type&& value, flag_type&& flag) + : m_value(std::move(value)) + , m_flag(std::move(flag)) + { + } + + constexpr optional(std::add_lvalue_reference_t value, std::add_lvalue_reference_t flag) + : m_value(value) + , m_flag(flag) + { + } + + constexpr optional(value_type&& value, std::add_lvalue_reference_t flag) + : m_value(std::move(value)) + , m_flag(flag) + { + } + + constexpr optional(std::add_lvalue_reference_t value, flag_type&& flag) + : m_value(value) + , m_flag(std::move(flag)) + { + } + + constexpr self_type& operator=(std::nullopt_t) + { + m_flag = false; + return *this; + } + + template + requires( + not std::same_as and + std::assignable_from, TO> + ) + constexpr self_type& operator=(TO&& rhs) + { + m_value = std::forward(rhs); + m_flag = true; + return *this; + } + + constexpr self_type& operator=(const self_type& rhs) + { + m_value = rhs.m_value; + m_flag = rhs.m_flag; + return *this; + } + + template + requires( + util::both_assignable_from_cref and + not util::initializable_from_refs> and + not util::assignable_from_refs> + ) + constexpr self_type& operator=(const optional& rhs) + { + m_value = rhs.m_value; + m_flag = rhs.m_flag; + return *this; + } + + constexpr self_type& operator=(self_type&& rhs) + { + m_value = std::move(rhs.m_value); + m_flag = std::move(rhs.m_flag); + return *this; + } + + template + requires( + util::both_assignable_from_cond_ref and + not util::initializable_from_refs> and + not util::assignable_from_refs> + ) + constexpr self_type& operator=(optional&& rhs) + { + m_value = std::move(rhs.m_value); + m_flag = std::move(rhs.m_flag); + return *this; + } + + constexpr bool has_value() const noexcept; + constexpr explicit operator bool() const noexcept; + + constexpr reference operator*() & noexcept; + constexpr const_reference operator*() const & noexcept; + constexpr rvalue_reference operator*() && noexcept; + constexpr const_rvalue_reference operator*() const && noexcept; + + constexpr reference value() &; + constexpr const_reference value() const &; + constexpr rvalue_reference value() &&; + constexpr const_rvalue_reference value() const &&; + + template + constexpr value_type value_or(U&& default_value) const &; + + template + constexpr value_type value_or(U&& default_value) &&; + + void swap(self_type& other) noexcept; + void reset() noexcept; + + private: + + void throw_if_empty() const; + + T m_value; + B m_flag; + + template + friend class optional; + }; + + template + constexpr void swap(optional& lhs, optional& rhs) noexcept + { + lhs.swap(rhs); + } + + template + constexpr bool operator==(const optional& lhs, std::nullopt_t) noexcept; + + template + constexpr std::strong_ordering operator<=>(const optional& lhs, std::nullopt_t) noexcept; + + template + constexpr bool operator==(const optional& lhs, const U& rhs) noexcept; + + template + requires (!util::is_optional_v && std::three_way_comparable_with) + constexpr std::compare_three_way_result_t + operator<=>(const optional& lhs, const U& rhs) noexcept; + + template + constexpr bool operator==(const optional& lhs, const optional& rhs) noexcept; + + template U, class UB> + constexpr std::compare_three_way_result_t + operator<=>(const optional& lhs, const optional& rhs) noexcept; + + /*************************** + * optional implementation * + ***************************/ + + template + constexpr bool optional::has_value() const noexcept + { + return m_flag; + } + + template + constexpr optional::operator bool() const noexcept + { + return m_flag; + } + + template + constexpr auto optional::operator*() & noexcept -> reference + { + return m_value; + } + + template + constexpr auto optional::operator*() const & noexcept -> const_reference + { + return m_value; + } + + template + constexpr auto optional::operator*() && noexcept -> rvalue_reference + { + return std::move(m_value); + } + + template + constexpr auto optional::operator*() const && noexcept -> const_rvalue_reference + { + return std::move(m_value); + } + + template + constexpr auto optional::value() & -> reference + { + throw_if_empty(); + return m_value; + } + + template + constexpr auto optional::value() const & -> const_reference + { + throw_if_empty(); + return m_value; + } + + template + constexpr auto optional::value() && -> rvalue_reference + { + throw_if_empty(); + return std::move(m_value); + } + + template + constexpr auto optional::value() const && -> const_rvalue_reference + { + throw_if_empty(); + return std::move(m_value); + } + + template + template + constexpr auto optional::value_or(U&& default_value) const & -> value_type + { + return bool(*this) ? **this : static_cast(std::forward(default_value)); + } + + template + template + constexpr auto optional::value_or(U&& default_value) && -> value_type + { + return bool(*this) ? std::move(**this) : static_cast(std::forward(default_value)); + } + + template + void optional::swap(self_type& other) noexcept + { + using std::swap; + swap(m_value, other.m_value); + swap(m_flag, other.m_flag); + } + + template + void optional::reset() noexcept + { + m_flag = false; + } + + template + void optional::throw_if_empty() const + { + if (!m_flag) + { + throw std::bad_optional_access{}; + } + } + + template + constexpr bool operator==(const optional& lhs, std::nullopt_t) noexcept + { + return !lhs; + } + + template + constexpr std::strong_ordering operator<=>(const optional& lhs, std::nullopt_t) noexcept + { + return bool(lhs) <=> false; + } + + template + constexpr bool operator==(const optional& lhs, const U& rhs) noexcept + { + return bool(lhs) && *lhs == rhs; + } + + template + requires (!util::is_optional_v && std::three_way_comparable_with) + constexpr std::compare_three_way_result_t + operator<=>(const optional& lhs, const U& rhs) noexcept + { + return bool(lhs) ? *lhs <=> rhs : std::strong_ordering::less; + } + + template + constexpr bool operator==(const optional& lhs, const optional& rhs) noexcept + { + return bool(rhs) ? lhs == *rhs : !bool(lhs); + } + + template U, class UB> + constexpr std::compare_three_way_result_t + operator<=>(const optional& lhs, const optional& rhs) noexcept + { + return (bool(lhs) && bool(rhs)) ? *lhs <=> *rhs : bool(lhs) <=> bool(rhs); + } +} + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e1d259e27..81538a24c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -52,6 +52,7 @@ set(SPARROW_TESTS_SOURCES test_memory.cpp test_mpl.cpp test_null_layout.cpp + test_optional.cpp test_traits.cpp test_typed_array.cpp test_typed_array_timestamp.cpp diff --git a/test/test_optional.cpp b/test/test_optional.cpp new file mode 100644 index 000000000..d1ee64790 --- /dev/null +++ b/test/test_optional.cpp @@ -0,0 +1,629 @@ +// Copyright 2024 Man Group Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "sparrow/optional.hpp" + +#include "doctest/doctest.h" + +namespace sparrow +{ + using optional_double = optional; + using optional_int = optional; + + TEST_SUITE("optional value") + { + TEST_CASE("constructors") + { + { + optional_double d; + CHECK_FALSE(d.has_value()); + } + + { + optional_double d(std::nullopt); + CHECK_FALSE(d.has_value()); + } + + { + optional_double d(1.2); + CHECK(d.has_value()); + CHECK_EQ(d.value(), 1.2); + } + + { + int i = 3; + optional_double d(i); + CHECK(d.has_value()); + CHECK_EQ(d.value(), 3.); + } + + { + double val = 1.2; + bool b1 = true; + + optional_double td1(val, b1); + optional_double td2(std::move(val), b1); + optional_double td3(val, std::move(b1)); + optional_double td4(std::move(val), std::move(b1)); + + CHECK(td1.has_value()); + CHECK_EQ(td1.value(), val); + CHECK(td2.has_value()); + CHECK_EQ(td2.value(), val); + CHECK(td3.has_value()); + CHECK_EQ(td3.value(), val); + CHECK(td4.has_value()); + CHECK_EQ(td4.value(), val); + + bool b2 = false; + optional_double fd1(val, b2); + optional_double fd2(std::move(val), b2); + optional_double fd3(val, std::move(b2)); + optional_double fd4(std::move(val), std::move(b2)); + + CHECK_FALSE(fd1.has_value()); + CHECK_FALSE(fd2.has_value()); + CHECK_FALSE(fd3.has_value()); + CHECK_FALSE(fd4.has_value()); + } + } + + TEST_CASE("copy constructors") + { + { + optional_double d1(1.2); + optional_double d2(d1); + CHECK(d2.has_value()); + CHECK_EQ(d1.value(), d2.value()); + } + + { + optional_int i(2); + optional_double d(i); + CHECK(d.has_value()); + CHECK_EQ(i.value(), d.value()); + } + + { + optional_double d1(std::nullopt); + optional_double d2(d1); + CHECK_FALSE(d2.has_value()); + } + } + + TEST_CASE("move constructors") + { + { + optional_double d0(1.2); + optional_double d1(d0); + optional_double d2(std::move(d0)); + CHECK(d2.has_value()); + CHECK_EQ(d1.value(), d2.value()); + } + { + optional_int i(2); + optional_int ci(i); + optional_double d(std::move(i)); + CHECK(d.has_value()); + CHECK_EQ(ci.value(), d.value()); + } + { + optional_double d1(std::nullopt); + optional_double d2(std::move(d1)); + CHECK_FALSE(d2.has_value()); + } + } + + TEST_CASE("copy assign") + { + { + optional_double d1(1.2); + optional_double d2(2.5); + d2 = d1; + CHECK(d2.has_value()); + CHECK_EQ(d1.value(), d2.value()); + } + { + optional_int d1(1); + optional_double d2(2.5); + d2 = d1; + CHECK(d2.has_value()); + CHECK_EQ(d1.value(), d2.value()); + } + { + optional_double d1(std::nullopt); + optional_double d2(2.5); + d2 = d1; + CHECK_FALSE(d2.has_value()); + } + } + + TEST_CASE("move assign") + { + { + optional_double d0(1.2); + optional_double d1(d0); + optional_double d2(2.5); + d2 = std::move(d0); + CHECK(d2.has_value()); + CHECK_EQ(d1.value(), d2.value()); + } + { + optional_int d0(1); + optional_int d1(d0); + optional_double d2(2.5); + d2 = std::move(d0); + CHECK(d2.has_value()); + CHECK_EQ(d1.value(), d2.value()); + } + { + optional_double d1(std::nullopt); + optional_double d2(2.3); + d2 = std::move(d1); + CHECK_FALSE(d2.has_value()); + } + } + + TEST_CASE("conversion to bool") + { + optional_double d1(1.2); + CHECK(d1); + + optional_double d2(std::nullopt); + CHECK_FALSE(d2); + } + + TEST_CASE("value / operator*") + { + constexpr double initial = 1.2; + constexpr double expected = 2.5; + + { + optional_double d(initial); + optional_double& d1(d); + d1.value() = expected; + CHECK_EQ(d.value(), expected); + CHECK_EQ(*d, expected); + } + { + optional_double d(initial); + const optional_double& d2(d); + CHECK_EQ(d2.value(), initial); + CHECK_EQ(*d2, initial); + } + { + optional_double d(initial); + optional_double&& d3(std::move(d)); + d3.value() = expected; + CHECK_EQ(d.value(), expected); + CHECK_EQ(*d, expected); + } + { + optional_double d(initial); + const optional_double&& d4(std::move(d)); + CHECK_EQ(d4.value(), initial); + CHECK_EQ(*d4, initial); + } + + optional_double empty = std::nullopt; + CHECK_THROWS_AS(empty.value(), std::bad_optional_access); + CHECK_NOTHROW(*empty); + } + + TEST_CASE("value_or") + { + constexpr double initial = 1.2; + constexpr double expected = 2.5; + + optional_double d(initial); + optional_double empty(std::nullopt); + + { + const optional_double& ref(d); + const optional_double& ref_empty(empty); + + double res = ref.value_or(expected); + double res_empty = ref_empty.value_or(expected); + + CHECK_EQ(res, initial); + CHECK_EQ(res_empty, expected); + } + + { + optional_double&& ref(std::move(d)); + optional_double&& ref_empty(std::move(empty)); + + double res = ref.value_or(expected); + double res_empty = ref_empty.value_or(expected); + + CHECK_EQ(res, initial); + CHECK_EQ(res_empty, expected); + } + + } + + TEST_CASE("swap") + { + constexpr double initial = 1.2; + constexpr double expected = 2.5; + optional_double d1(initial); + optional_double d2(expected); + optional_double empty(std::nullopt); + + swap(d1, d2); + CHECK_EQ(d1.value(), expected); + CHECK_EQ(d2.value(), initial); + + swap(d1, empty); + CHECK_EQ(empty.value(), expected); + CHECK_FALSE(d1.has_value()); + } + + TEST_CASE("reset") + { + constexpr double initial = 1.2; + optional_double d(initial); + d.reset(); + CHECK_FALSE(d.has_value()); + } + + TEST_CASE("equality comparison") + { + constexpr double initial = 1.2; + constexpr double other = 2.5; + + optional_double d1(initial); + optional_double d2(other); + optional_double empty; + + CHECK(d1 == d1); + CHECK(d1 == d1.value()); + CHECK(d1 != d2); + CHECK(d1 != d2.value()); + CHECK(d1 != empty); + CHECK(empty == empty); + } + + TEST_CASE("inequality comparison") + { + constexpr double initial = 1.2; + constexpr double other = 2.5; + + optional_double d1(initial); + optional_double d2(other); + optional_double empty; + + // opearator <= + CHECK(d1 <= d1); + CHECK(d1 <= d1.value()); + CHECK(d1 <= d2); + CHECK(d1 <= d2.value()); + CHECK_FALSE(d2 <= d1); + CHECK_FALSE(d2 <= d1.value()); + CHECK(empty <= d1); + CHECK_FALSE(d1 <= empty); + + // operator >= + CHECK(d1 >= d1); + CHECK(d1 >= d1.value()); + CHECK(d2 >= d1); + CHECK(d2 >= d1.value()); + CHECK_FALSE(d1 >= d2); + CHECK_FALSE(d1 >= d2.value()); + CHECK(d1 >= empty); + CHECK_FALSE(empty >= d1); + + // operator< + CHECK_FALSE(d1 < d1); + CHECK_FALSE(d1 < d1.value()); + CHECK(d1 < d2); + CHECK(d1 < d2.value()); + CHECK(empty < d1); + CHECK_FALSE(d1 < empty); + + // oprator> + CHECK_FALSE(d1 > d1); + CHECK_FALSE(d1 > d1.value()); + CHECK(d2 > d1); + CHECK(d2 > d1.value()); + CHECK(d1 > empty); + CHECK_FALSE(empty > d1); + } + } + + using optional_proxy = optional; + + TEST_SUITE("optional proxy") + { + TEST_CASE("constructors") + { + double val = 1.2; + bool b1 = true; + + optional_proxy td(val); + CHECK(td.has_value()); + CHECK_EQ(td.value(), val); + + optional_proxy td1(val, b1); + optional_proxy td2(val, std::move(b1)); + + CHECK(td1.has_value()); + CHECK_EQ(td1.value(), val); + CHECK(td2.has_value()); + CHECK_EQ(td2.value(), val); + + bool b2 = false; + optional_proxy fd1(val, b2); + optional_proxy fd2(val, std::move(b2)); + + CHECK_FALSE(fd1.has_value()); + CHECK_FALSE(fd2.has_value()); + } + + TEST_CASE("copy constructors") + { + double val = 1.2; + optional_proxy d1(val); + optional_proxy d2(d1); + CHECK(d2.has_value()); + CHECK_EQ(d1.value(), d2.value()); + } + + TEST_CASE("move constructor") + { + double val = 1.2; + optional_proxy d1(val); + optional_proxy d2(std::move(d1)); + CHECK(d2.has_value()); + CHECK_EQ(d2.value(), val); + } + + TEST_CASE("copy assign") + { + { + double initial = 1.2; + double expected = 2.5; + optional_proxy d1(initial); + optional_proxy d2(expected); + d2 = d1; + CHECK(d2.has_value()); + CHECK_EQ(d1.value(), d2.value()); + CHECK_EQ(initial, expected); + } + + { + double initial = 1.2; + double expected = 2.5; + optional_double d1(initial); + optional_proxy d2(expected); + d2 = d1; + CHECK(d2.has_value()); + CHECK_EQ(d1.value(), d2.value()); + CHECK_EQ(initial, expected); + } + + { + double initial = 1.2; + optional_proxy d2(initial); + d2 = std::nullopt; + CHECK_FALSE(d2.has_value()); + } + } + + TEST_CASE("move assign") + { + { + double initial = 1.2; + double expected = 2.5; + optional_proxy d1(initial); + optional_proxy d2(expected); + d2 = std::move(d1); + CHECK(d2.has_value()); + CHECK_EQ(d1.value(), d2.value()); + CHECK_EQ(initial, expected); + + } + + { + double initial = 1.2; + double expected = 2.5; + optional_double d1(initial); + optional_proxy d2(expected); + d2 = std::move(d1); + CHECK(d2.has_value()); + CHECK_EQ(d1.value(), d2.value()); + CHECK_EQ(initial, expected); + } + } + + TEST_CASE("conversion to bool") + { + double val = 1.2; + optional_proxy d1(val); + CHECK(d1); + + d1 = std::nullopt; + CHECK_FALSE(d1); + } + + TEST_CASE("value / operator*") + { + double initial = 1.2; + double expected = 2.5; + + { + optional_proxy d(initial); + optional_proxy& d1(d); + d1.value() = expected; + CHECK_EQ(d.value(), expected); + CHECK_EQ(*d, expected); + } + { + optional_proxy d(initial); + const optional_proxy& d2(d); + CHECK_EQ(d2.value(), initial); + CHECK_EQ(*d2, initial); + } + { + optional_proxy d(initial); + optional_proxy&& d3(std::move(d)); + d3.value() = expected; + CHECK_EQ(d.value(), expected); + CHECK_EQ(*d, expected); + } + { + optional_proxy d(initial); + const optional_proxy&& d4(std::move(d)); + CHECK_EQ(d4.value(), initial); + CHECK_EQ(*d4, initial); + } + + { + optional_proxy empty(initial); + empty = std::nullopt; + CHECK_THROWS_AS(empty.value(), std::bad_optional_access); + CHECK_NOTHROW(*empty); + } + } + + TEST_CASE("value_or") + { + double initial = 1.2; + double expected = 2.5; + + optional_proxy d(initial); + optional_proxy empty(initial); + empty = std::nullopt; + + { + const optional_proxy& ref(d); + const optional_proxy& ref_empty(empty); + + double res = ref.value_or(expected); + double res_empty = ref_empty.value_or(expected); + + CHECK_EQ(res, initial); + CHECK_EQ(res_empty, expected); + } + + { + optional_proxy&& ref(std::move(d)); + optional_proxy&& ref_empty(std::move(empty)); + + double res = ref.value_or(expected); + double res_empty = ref_empty.value_or(expected); + + CHECK_EQ(res, initial); + CHECK_EQ(res_empty, expected); + } + } + + TEST_CASE("swap") + { + double initial = 1.2; + double expected = 2.5; + double initial_bu = initial; + double expected_bu = expected; + double empty_val = 3.7; + optional_proxy d1(initial); + optional_proxy d2(expected); + optional_proxy empty(empty_val); + empty = std::nullopt; + + swap(d1, d2); + CHECK_EQ(d1.value(), expected_bu); + CHECK_EQ(d2.value(), initial_bu); + + swap(d1, empty); + CHECK_EQ(empty.value(), expected_bu); + CHECK_FALSE(d1.has_value()); + } + + TEST_CASE("reset") + { + double initial = 1.2; + optional_proxy d(initial); + d.reset(); + CHECK_FALSE(d.has_value()); + } + + TEST_CASE("equality comparison") + { + double initial = 1.2; + double other = 2.5; + double empty_val = 3.7; + + optional_proxy d1(initial); + optional_proxy d2(other); + optional_proxy empty(empty_val); + empty = std::nullopt; + + CHECK(d1 == d1); + CHECK(d1 == d1.value()); + CHECK(d1 != d2); + CHECK(d1 != d2.value()); + CHECK(d1 != empty); + CHECK(empty == empty); + } + + TEST_CASE("inequality comparison") + { + double initial = 1.2; + double other = 2.5; + double empty_val = 3.7; + + optional_proxy d1(initial); + optional_proxy d2(other); + optional_proxy empty(empty_val); + empty = std::nullopt; + + // opearator <= + CHECK(d1 <= d1); + CHECK(d1 <= d1.value()); + CHECK(d1 <= d2); + CHECK(d1 <= d2.value()); + CHECK_FALSE(d2 <= d1); + CHECK_FALSE(d2 <= d1.value()); + CHECK(empty <= d1); + CHECK_FALSE(d1 <= empty); + + // operator >= + CHECK(d1 >= d1); + CHECK(d1 >= d1.value()); + CHECK(d2 >= d1); + CHECK(d2 >= d1.value()); + CHECK_FALSE(d1 >= d2); + CHECK_FALSE(d1 >= d2.value()); + CHECK(d1 >= empty); + CHECK_FALSE(empty >= d1); + + // operator< + CHECK_FALSE(d1 < d1); + CHECK_FALSE(d1 < d1.value()); + CHECK(d1 < d2); + CHECK(d1 < d2.value()); + CHECK(empty < d1); + CHECK_FALSE(d1 < empty); + + // oprator> + CHECK_FALSE(d1 > d1); + CHECK_FALSE(d1 > d1.value()); + CHECK(d2 > d1); + CHECK(d2 > d1.value()); + CHECK(d1 > empty); + CHECK_FALSE(empty > d1); + } + } +} + From bf7282c827fa4bd0abb96ba3d73e9b4ee3f17143 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:02:08 +0200 Subject: [PATCH 02/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index d1ee64790..ad9ad5c67 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -37,7 +37,7 @@ namespace sparrow { optional_double d(1.2); - CHECK(d.has_value()); + REQUIRE(d.has_value()); CHECK_EQ(d.value(), 1.2); } From 419d08bd6b834e72da39efba86a7a65ae2ff0d81 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:02:21 +0200 Subject: [PATCH 03/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index ad9ad5c67..c0566b935 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -57,13 +57,13 @@ namespace sparrow optional_double td3(val, std::move(b1)); optional_double td4(std::move(val), std::move(b1)); - CHECK(td1.has_value()); + REQUIRE(td1.has_value()); CHECK_EQ(td1.value(), val); - CHECK(td2.has_value()); + REQUIRE(td2.has_value()); CHECK_EQ(td2.value(), val); - CHECK(td3.has_value()); + REQUIRE(td3.has_value()); CHECK_EQ(td3.value(), val); - CHECK(td4.has_value()); + REQUIRE(td4.has_value()); CHECK_EQ(td4.value(), val); bool b2 = false; From 7f7476f372030f9bc38b1535ccbac43a194c92ed Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:02:34 +0200 Subject: [PATCH 04/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index c0566b935..d23e79205 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -108,7 +108,7 @@ namespace sparrow optional_double d0(1.2); optional_double d1(d0); optional_double d2(std::move(d0)); - CHECK(d2.has_value()); + REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } { From 8050eb2fd211ef2b1d5fd3096cfae3679393c401 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:31:03 +0200 Subject: [PATCH 05/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index d23e79205..830f7f844 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -44,7 +44,7 @@ namespace sparrow { int i = 3; optional_double d(i); - CHECK(d.has_value()); + REQUIRE(d.has_value()); CHECK_EQ(d.value(), 3.); } From 94554d3418b06006b52cc778b51a84e1d12c853d Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:31:12 +0200 Subject: [PATCH 06/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 830f7f844..9a1b0c235 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -84,7 +84,7 @@ namespace sparrow { optional_double d1(1.2); optional_double d2(d1); - CHECK(d2.has_value()); + REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } From 8954026fa7efa9a90263e9185a0238c0d2816030 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:31:18 +0200 Subject: [PATCH 07/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 9a1b0c235..45b50475c 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -91,7 +91,7 @@ namespace sparrow { optional_int i(2); optional_double d(i); - CHECK(d.has_value()); + REQUIRE(d.has_value()); CHECK_EQ(i.value(), d.value()); } From a5751a0c0aaea39b402b31639f386f1da54a056e Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:55:44 +0200 Subject: [PATCH 08/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 45b50475c..e33c75b3a 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -115,7 +115,7 @@ namespace sparrow optional_int i(2); optional_int ci(i); optional_double d(std::move(i)); - CHECK(d.has_value()); + REQUIRE(d.has_value()); CHECK_EQ(ci.value(), d.value()); } { From 49d609a6a22860516325f7d7df4ee58c86f72c55 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:55:51 +0200 Subject: [PATCH 09/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index e33c75b3a..e6cc354f6 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -385,7 +385,7 @@ namespace sparrow double val = 1.2; optional_proxy d1(val); optional_proxy d2(std::move(d1)); - CHECK(d2.has_value()); + REQUIRE(d2.has_value()); CHECK_EQ(d2.value(), val); } From 97c6451ec30a307cb3b63c390e8cb46714509a01 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:55:57 +0200 Subject: [PATCH 10/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index e6cc354f6..b512c11b6 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -397,7 +397,7 @@ namespace sparrow optional_proxy d1(initial); optional_proxy d2(expected); d2 = d1; - CHECK(d2.has_value()); + REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); CHECK_EQ(initial, expected); } From e0a285618a8477f527fbabf8f1dc3f03078bd927 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:56:03 +0200 Subject: [PATCH 11/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index b512c11b6..2d9f8fcfb 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -408,7 +408,7 @@ namespace sparrow optional_double d1(initial); optional_proxy d2(expected); d2 = d1; - CHECK(d2.has_value()); + REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); CHECK_EQ(initial, expected); } From 6e5bf3a5e3decf75e8b82247b515083de77a6a45 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:56:09 +0200 Subject: [PATCH 12/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 2d9f8fcfb..7e867ca22 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -441,7 +441,7 @@ namespace sparrow optional_double d1(initial); optional_proxy d2(expected); d2 = std::move(d1); - CHECK(d2.has_value()); + REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); CHECK_EQ(initial, expected); } From 61510c05006ad91d396d20093b24939f445aa565 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:56:17 +0200 Subject: [PATCH 13/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 7e867ca22..23c1482e6 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -429,7 +429,7 @@ namespace sparrow optional_proxy d1(initial); optional_proxy d2(expected); d2 = std::move(d1); - CHECK(d2.has_value()); + REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); CHECK_EQ(initial, expected); From 3f82a7fba608ea24198ae0506a92f7c1205d50e1 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:56:31 +0200 Subject: [PATCH 14/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 23c1482e6..628c4c854 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -131,7 +131,7 @@ namespace sparrow optional_double d1(1.2); optional_double d2(2.5); d2 = d1; - CHECK(d2.has_value()); + REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } { From 1413158a6b47399b9ed02e289429c579c7e680de Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:56:43 +0200 Subject: [PATCH 15/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 628c4c854..b650f2295 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -138,7 +138,7 @@ namespace sparrow optional_int d1(1); optional_double d2(2.5); d2 = d1; - CHECK(d2.has_value()); + REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } { From ed3e87326b9057cf3dfb92844068aef640e105cf Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:56:52 +0200 Subject: [PATCH 16/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index b650f2295..8a9a99ba4 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -156,7 +156,7 @@ namespace sparrow optional_double d1(d0); optional_double d2(2.5); d2 = std::move(d0); - CHECK(d2.has_value()); + REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } { From 87a4055ab1b28d14091ee31ad39602bd74432685 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:57:45 +0200 Subject: [PATCH 17/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 8a9a99ba4..645d33228 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -164,7 +164,7 @@ namespace sparrow optional_int d1(d0); optional_double d2(2.5); d2 = std::move(d0); - CHECK(d2.has_value()); + REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } { From 5851c8db2169d8c678666ed20f81aacd2c81a111 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:57:58 +0200 Subject: [PATCH 18/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 645d33228..93e702df3 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -352,7 +352,7 @@ namespace sparrow bool b1 = true; optional_proxy td(val); - CHECK(td.has_value()); + REQUIRE(td.has_value()); CHECK_EQ(td.value(), val); optional_proxy td1(val, b1); From 3a0f607b248e6ea053652e494597dc6f5124420a Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:58:19 +0200 Subject: [PATCH 19/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 93e702df3..54158240f 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -358,9 +358,9 @@ namespace sparrow optional_proxy td1(val, b1); optional_proxy td2(val, std::move(b1)); - CHECK(td1.has_value()); + REQUIRE(td1.has_value()); CHECK_EQ(td1.value(), val); - CHECK(td2.has_value()); + REQUIRE(td2.has_value()); CHECK_EQ(td2.value(), val); bool b2 = false; From be084913c6c9c097d2f49228513513b2debfcff5 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 09:58:30 +0200 Subject: [PATCH 20/34] Update test/test_optional.cpp Co-authored-by: Alexis Placet --- test/test_optional.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 54158240f..c96b21098 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -376,7 +376,7 @@ namespace sparrow double val = 1.2; optional_proxy d1(val); optional_proxy d2(d1); - CHECK(d2.has_value()); + REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } From a86165fe6889a4a0cfe3e24a0ff73f23afa656b3 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 10:35:05 +0200 Subject: [PATCH 21/34] Added make_optional helper --- include/sparrow/optional.hpp | 18 +++++++++ test/test_optional.cpp | 76 ++++++++++++++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/include/sparrow/optional.hpp b/include/sparrow/optional.hpp index d3cc0ba9d..37b83d779 100644 --- a/include/sparrow/optional.hpp +++ b/include/sparrow/optional.hpp @@ -121,6 +121,14 @@ namespace sparrow static constexpr bool is_optional_v = is_optional::value; } + /** + * The optional class is similar to std::optional, with two major differences: + * - it can act as a proxy, meaning its template parameter can be lvalue references + * or lvalue const references + * - the semantic for empty optoinal: resetting an non empty optional does not destruct + * the contained value. Allocating an empty optional construct the contained value. This + * behavior is temporary and will be changed in the near future. + */ template class optional { @@ -329,6 +337,9 @@ namespace sparrow constexpr std::compare_three_way_result_t operator<=>(const optional& lhs, const optional& rhs) noexcept; + template + constexpr optional make_optional(T&& value, B&& flag = true); + /*************************** * optional implementation * ***************************/ @@ -472,5 +483,12 @@ namespace sparrow { return (bool(lhs) && bool(rhs)) ? *lhs <=> *rhs : bool(lhs) <=> bool(rhs); } + + template + constexpr optional make_optional(T&& value, B&& flag) + { + return optional(std::forward(value), std::forward(flag)); + } + } diff --git a/test/test_optional.cpp b/test/test_optional.cpp index c96b21098..ec9e1fcd5 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -25,22 +25,26 @@ namespace sparrow { TEST_CASE("constructors") { + SUBCASE("default") { optional_double d; CHECK_FALSE(d.has_value()); } - + + SUBCASE("from nullopt") { optional_double d(std::nullopt); CHECK_FALSE(d.has_value()); } + SUBCASE("from value") { optional_double d(1.2); REQUIRE(d.has_value()); CHECK_EQ(d.value(), 1.2); } + SUBCASE("from value with conversion") { int i = 3; optional_double d(i); @@ -48,6 +52,7 @@ namespace sparrow CHECK_EQ(d.value(), 3.); } + SUBCASE("from value and flag") { double val = 1.2; bool b1 = true; @@ -81,6 +86,7 @@ namespace sparrow TEST_CASE("copy constructors") { + SUBCASE("default") { optional_double d1(1.2); optional_double d2(d1); @@ -88,6 +94,7 @@ namespace sparrow CHECK_EQ(d1.value(), d2.value()); } + SUBCASE("with conversion") { optional_int i(2); optional_double d(i); @@ -95,6 +102,7 @@ namespace sparrow CHECK_EQ(i.value(), d.value()); } + SUBCASE("from empty optional") { optional_double d1(std::nullopt); optional_double d2(d1); @@ -104,6 +112,7 @@ namespace sparrow TEST_CASE("move constructors") { + SUBCASE("default") { optional_double d0(1.2); optional_double d1(d0); @@ -111,6 +120,8 @@ namespace sparrow REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } + + SUBCASE("with conversion") { optional_int i(2); optional_int ci(i); @@ -118,6 +129,8 @@ namespace sparrow REQUIRE(d.has_value()); CHECK_EQ(ci.value(), d.value()); } + + SUBCASE("from empty optional") { optional_double d1(std::nullopt); optional_double d2(std::move(d1)); @@ -127,6 +140,7 @@ namespace sparrow TEST_CASE("copy assign") { + SUBCASE("default") { optional_double d1(1.2); optional_double d2(2.5); @@ -134,6 +148,8 @@ namespace sparrow REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } + + SUBCASE("with conversion") { optional_int d1(1); optional_double d2(2.5); @@ -141,6 +157,8 @@ namespace sparrow REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } + + SUBCASE("from empty optional") { optional_double d1(std::nullopt); optional_double d2(2.5); @@ -151,6 +169,7 @@ namespace sparrow TEST_CASE("move assign") { + SUBCASE("default") { optional_double d0(1.2); optional_double d1(d0); @@ -159,6 +178,8 @@ namespace sparrow REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } + + SUBCASE("with conversion") { optional_int d0(1); optional_int d1(d0); @@ -167,6 +188,8 @@ namespace sparrow REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } + + SUBCASE("from empty optional") { optional_double d1(std::nullopt); optional_double d2(2.3); @@ -189,6 +212,7 @@ namespace sparrow constexpr double initial = 1.2; constexpr double expected = 2.5; + SUBCASE("& overload") { optional_double d(initial); optional_double& d1(d); @@ -196,12 +220,16 @@ namespace sparrow CHECK_EQ(d.value(), expected); CHECK_EQ(*d, expected); } + + SUBCASE("const & overload") { optional_double d(initial); const optional_double& d2(d); CHECK_EQ(d2.value(), initial); CHECK_EQ(*d2, initial); } + + SUBCASE("&& overload") { optional_double d(initial); optional_double&& d3(std::move(d)); @@ -209,6 +237,8 @@ namespace sparrow CHECK_EQ(d.value(), expected); CHECK_EQ(*d, expected); } + + SUBCASE("const && overload") { optional_double d(initial); const optional_double&& d4(std::move(d)); @@ -216,9 +246,12 @@ namespace sparrow CHECK_EQ(*d4, initial); } - optional_double empty = std::nullopt; - CHECK_THROWS_AS(empty.value(), std::bad_optional_access); - CHECK_NOTHROW(*empty); + SUBCASE("empty") + { + optional_double empty = std::nullopt; + CHECK_THROWS_AS(empty.value(), std::bad_optional_access); + CHECK_NOTHROW(*empty); + } } TEST_CASE("value_or") @@ -229,6 +262,7 @@ namespace sparrow optional_double d(initial); optional_double empty(std::nullopt); + SUBCASE("const & overload") { const optional_double& ref(d); const optional_double& ref_empty(empty); @@ -240,6 +274,7 @@ namespace sparrow CHECK_EQ(res_empty, expected); } + SUBCASE("&& overload") { optional_double&& ref(std::move(d)); optional_double&& ref_empty(std::move(empty)); @@ -340,6 +375,15 @@ namespace sparrow CHECK(d1 > empty); CHECK_FALSE(empty > d1); } + + TEST_CASE("make_optional") + { + double value = 2.5; + auto opt = make_optional(std::move(value), true); + static_assert(std::same_as, optional_double>); + REQUIRE(opt.has_value()); + CHECK_EQ(opt.value(), value); + } } using optional_proxy = optional; @@ -391,6 +435,7 @@ namespace sparrow TEST_CASE("copy assign") { + SUBCASE("default") { double initial = 1.2; double expected = 2.5; @@ -402,6 +447,7 @@ namespace sparrow CHECK_EQ(initial, expected); } + SUBCASE("with conversion") { double initial = 1.2; double expected = 2.5; @@ -413,6 +459,7 @@ namespace sparrow CHECK_EQ(initial, expected); } + SUBCASE("from empty optional") { double initial = 1.2; optional_proxy d2(initial); @@ -423,6 +470,7 @@ namespace sparrow TEST_CASE("move assign") { + SUBCASE("default") { double initial = 1.2; double expected = 2.5; @@ -435,6 +483,7 @@ namespace sparrow } + SUBCASE("with conversion") { double initial = 1.2; double expected = 2.5; @@ -462,6 +511,7 @@ namespace sparrow double initial = 1.2; double expected = 2.5; + SUBCASE("& overload") { optional_proxy d(initial); optional_proxy& d1(d); @@ -469,12 +519,16 @@ namespace sparrow CHECK_EQ(d.value(), expected); CHECK_EQ(*d, expected); } + + SUBCASE("const & overload") { optional_proxy d(initial); const optional_proxy& d2(d); CHECK_EQ(d2.value(), initial); CHECK_EQ(*d2, initial); } + + SUBCASE("&& overload") { optional_proxy d(initial); optional_proxy&& d3(std::move(d)); @@ -482,6 +536,8 @@ namespace sparrow CHECK_EQ(d.value(), expected); CHECK_EQ(*d, expected); } + + SUBCASE("const && overload") { optional_proxy d(initial); const optional_proxy&& d4(std::move(d)); @@ -489,6 +545,7 @@ namespace sparrow CHECK_EQ(*d4, initial); } + SUBCASE("empty") { optional_proxy empty(initial); empty = std::nullopt; @@ -506,6 +563,7 @@ namespace sparrow optional_proxy empty(initial); empty = std::nullopt; + SUBCASE("const & overload") { const optional_proxy& ref(d); const optional_proxy& ref_empty(empty); @@ -517,6 +575,7 @@ namespace sparrow CHECK_EQ(res_empty, expected); } + SUBCASE("&& overload") { optional_proxy&& ref(std::move(d)); optional_proxy&& ref_empty(std::move(empty)); @@ -624,6 +683,15 @@ namespace sparrow CHECK(d1 > empty); CHECK_FALSE(empty > d1); } + + TEST_CASE("make_optional") + { + double value = 2.7; + auto opt = make_optional(value, true); + static_assert(std::same_as, optional_proxy>); + REQUIRE(opt.has_value()); + CHECK_EQ(opt.value(), value); + } } } From 9734b9783eee200c63a93e3eb4118a790bc3eb5f Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 11:10:44 +0200 Subject: [PATCH 22/34] Added constraint on optional flag type parameter --- include/sparrow/optional.hpp | 68 ++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/include/sparrow/optional.hpp b/include/sparrow/optional.hpp index 37b83d779..50776d79e 100644 --- a/include/sparrow/optional.hpp +++ b/include/sparrow/optional.hpp @@ -23,7 +23,10 @@ namespace sparrow { - template + template + concept boolean_like = std::constructible_from and std::convertible_to; + + template class optional; template @@ -112,7 +115,7 @@ namespace sparrow { }; - template + template struct is_optional> : std::true_type { }; @@ -125,11 +128,11 @@ namespace sparrow * The optional class is similar to std::optional, with two major differences: * - it can act as a proxy, meaning its template parameter can be lvalue references * or lvalue const references - * - the semantic for empty optoinal: resetting an non empty optional does not destruct + * - the semantic for empty optional: resetting an non empty optional does not destruct * the contained value. Allocating an empty optional construct the contained value. This * behavior is temporary and will be changed in the near future. */ - template + template class optional { public: @@ -169,7 +172,7 @@ namespace sparrow constexpr optional(const self_type&) = default; - template + template requires ( util::both_constructible_from_cref and not util::initializable_from_refs> @@ -183,7 +186,7 @@ namespace sparrow constexpr optional(self_type&&) noexcept = default; - template + template requires ( util::both_constructible_from_cond_ref and not util::initializable_from_refs> @@ -244,7 +247,7 @@ namespace sparrow return *this; } - template + template requires( util::both_assignable_from_cref and not util::initializable_from_refs> and @@ -264,7 +267,7 @@ namespace sparrow return *this; } - template + template requires( util::both_assignable_from_cond_ref and not util::initializable_from_refs> and @@ -306,20 +309,17 @@ namespace sparrow T m_value; B m_flag; - template + template friend class optional; }; template - constexpr void swap(optional& lhs, optional& rhs) noexcept - { - lhs.swap(rhs); - } + constexpr void swap(optional& lhs, optional& rhs) noexcept; template constexpr bool operator==(const optional& lhs, std::nullopt_t) noexcept; - template + template constexpr std::strong_ordering operator<=>(const optional& lhs, std::nullopt_t) noexcept; template @@ -337,92 +337,92 @@ namespace sparrow constexpr std::compare_three_way_result_t operator<=>(const optional& lhs, const optional& rhs) noexcept; - template + template constexpr optional make_optional(T&& value, B&& flag = true); /*************************** * optional implementation * ***************************/ - template + template constexpr bool optional::has_value() const noexcept { return m_flag; } - template + template constexpr optional::operator bool() const noexcept { return m_flag; } - template + template constexpr auto optional::operator*() & noexcept -> reference { return m_value; } - template + template constexpr auto optional::operator*() const & noexcept -> const_reference { return m_value; } - template + template constexpr auto optional::operator*() && noexcept -> rvalue_reference { return std::move(m_value); } - template + template constexpr auto optional::operator*() const && noexcept -> const_rvalue_reference { return std::move(m_value); } - template + template constexpr auto optional::value() & -> reference { throw_if_empty(); return m_value; } - template + template constexpr auto optional::value() const & -> const_reference { throw_if_empty(); return m_value; } - template + template constexpr auto optional::value() && -> rvalue_reference { throw_if_empty(); return std::move(m_value); } - template + template constexpr auto optional::value() const && -> const_rvalue_reference { throw_if_empty(); return std::move(m_value); } - template + template template constexpr auto optional::value_or(U&& default_value) const & -> value_type { return bool(*this) ? **this : static_cast(std::forward(default_value)); } - template + template template constexpr auto optional::value_or(U&& default_value) && -> value_type { return bool(*this) ? std::move(**this) : static_cast(std::forward(default_value)); } - template + template void optional::swap(self_type& other) noexcept { using std::swap; @@ -430,13 +430,13 @@ namespace sparrow swap(m_flag, other.m_flag); } - template + template void optional::reset() noexcept { m_flag = false; } - template + template void optional::throw_if_empty() const { if (!m_flag) @@ -445,6 +445,12 @@ namespace sparrow } } + template + constexpr void swap(optional& lhs, optional& rhs) noexcept + { + lhs.swap(rhs); + } + template constexpr bool operator==(const optional& lhs, std::nullopt_t) noexcept { @@ -484,7 +490,7 @@ namespace sparrow return (bool(lhs) && bool(rhs)) ? *lhs <=> *rhs : bool(lhs) <=> bool(rhs); } - template + template constexpr optional make_optional(T&& value, B&& flag) { return optional(std::forward(value), std::forward(flag)); From 60df3a5079734194aaabbcaf3a734cd13fe18d4b Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 16:47:10 +0200 Subject: [PATCH 23/34] optional -> nullable --- .../sparrow/{optional.hpp => nullable.hpp} | 138 +++++----- test/{test_optional.cpp => test_nullable.cpp} | 260 +++++++++--------- 2 files changed, 199 insertions(+), 199 deletions(-) rename include/sparrow/{optional.hpp => nullable.hpp} (75%) rename test/{test_optional.cpp => test_nullable.cpp} (71%) diff --git a/include/sparrow/optional.hpp b/include/sparrow/nullable.hpp similarity index 75% rename from include/sparrow/optional.hpp rename to include/sparrow/nullable.hpp index 50776d79e..40bcf117b 100644 --- a/include/sparrow/optional.hpp +++ b/include/sparrow/nullable.hpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include "sparrow/mp_utils.hpp" @@ -27,10 +27,10 @@ namespace sparrow concept boolean_like = std::constructible_from and std::convertible_to; template - class optional; + class nullable; template - struct optional_traits + struct nullable_traits { using value_type = std::decay_t; using reference = value_type&; @@ -40,7 +40,7 @@ namespace sparrow }; template - struct optional_traits + struct nullable_traits { using value_type = std::decay_t; using unref_type = T; @@ -111,34 +111,34 @@ namespace sparrow std::assignable_from, conditional_ref_t>; template - struct is_optional : std::false_type + struct is_nullable : std::false_type { }; template - struct is_optional> : std::true_type + struct is_nullable> : std::true_type { }; template - static constexpr bool is_optional_v = is_optional::value; + static constexpr bool is_nullable_v = is_nullable::value; } /** - * The optional class is similar to std::optional, with two major differences: + * The nullable class is similar to std::nullable, with two major differences: * - it can act as a proxy, meaning its template parameter can be lvalue references * or lvalue const references - * - the semantic for empty optional: resetting an non empty optional does not destruct - * the contained value. Allocating an empty optional construct the contained value. This + * - the semantic for empty nullable: resetting an non empty nullable does not destruct + * the contained value. Allocating an empty nullable construct the contained value. This * behavior is temporary and will be changed in the near future. */ template - class optional + class nullable { public: - using self_type = optional; - using traits = optional_traits; + using self_type = nullable; + using traits = nullable_traits; using value_type = typename traits::value_type; using reference = typename traits::reference; using const_reference = typename traits::const_reference; @@ -146,13 +146,13 @@ namespace sparrow using const_rvalue_reference = typename traits::const_rvalue_reference; using flag_type = std::decay_t; - constexpr optional() noexcept + constexpr nullable() noexcept : m_value() , m_flag(false) { } - constexpr optional(std::nullopt_t) noexcept + constexpr nullable(std::nullopt_t) noexcept : m_value() , m_flag(false) { @@ -164,59 +164,59 @@ namespace sparrow std::constructible_from ) explicit (not std::convertible_to) - constexpr optional(U&& value) + constexpr nullable(U&& value) : m_value(std::forward(value)) , m_flag(true) { } - constexpr optional(const self_type&) = default; + constexpr nullable(const self_type&) = default; template requires ( util::both_constructible_from_cref and - not util::initializable_from_refs> + not util::initializable_from_refs> ) explicit(not util::both_convertible_from_cref) - constexpr optional(const optional& rhs) + constexpr nullable(const nullable& rhs) : m_value(rhs.m_value) , m_flag(rhs.m_flag) { } - constexpr optional(self_type&&) noexcept = default; + constexpr nullable(self_type&&) noexcept = default; template requires ( util::both_constructible_from_cond_ref and - not util::initializable_from_refs> + not util::initializable_from_refs> ) explicit(not util::both_convertible_from_cond_ref) - constexpr optional(optional&& rhs) + constexpr nullable(nullable&& rhs) : m_value(std::move(rhs.m_value)) , m_flag(std::move(rhs.m_flag)) { } - constexpr optional(value_type&& value, flag_type&& flag) + constexpr nullable(value_type&& value, flag_type&& flag) : m_value(std::move(value)) , m_flag(std::move(flag)) { } - constexpr optional(std::add_lvalue_reference_t value, std::add_lvalue_reference_t flag) + constexpr nullable(std::add_lvalue_reference_t value, std::add_lvalue_reference_t flag) : m_value(value) , m_flag(flag) { } - constexpr optional(value_type&& value, std::add_lvalue_reference_t flag) + constexpr nullable(value_type&& value, std::add_lvalue_reference_t flag) : m_value(std::move(value)) , m_flag(flag) { } - constexpr optional(std::add_lvalue_reference_t value, flag_type&& flag) + constexpr nullable(std::add_lvalue_reference_t value, flag_type&& flag) : m_value(value) , m_flag(std::move(flag)) { @@ -250,10 +250,10 @@ namespace sparrow template requires( util::both_assignable_from_cref and - not util::initializable_from_refs> and - not util::assignable_from_refs> + not util::initializable_from_refs> and + not util::assignable_from_refs> ) - constexpr self_type& operator=(const optional& rhs) + constexpr self_type& operator=(const nullable& rhs) { m_value = rhs.m_value; m_flag = rhs.m_flag; @@ -270,10 +270,10 @@ namespace sparrow template requires( util::both_assignable_from_cond_ref and - not util::initializable_from_refs> and - not util::assignable_from_refs> + not util::initializable_from_refs> and + not util::assignable_from_refs> ) - constexpr self_type& operator=(optional&& rhs) + constexpr self_type& operator=(nullable&& rhs) { m_value = std::move(rhs.m_value); m_flag = std::move(rhs.m_flag); @@ -310,99 +310,99 @@ namespace sparrow B m_flag; template - friend class optional; + friend class nullable; }; template - constexpr void swap(optional& lhs, optional& rhs) noexcept; + constexpr void swap(nullable& lhs, nullable& rhs) noexcept; template - constexpr bool operator==(const optional& lhs, std::nullopt_t) noexcept; + constexpr bool operator==(const nullable& lhs, std::nullopt_t) noexcept; template - constexpr std::strong_ordering operator<=>(const optional& lhs, std::nullopt_t) noexcept; + constexpr std::strong_ordering operator<=>(const nullable& lhs, std::nullopt_t) noexcept; template - constexpr bool operator==(const optional& lhs, const U& rhs) noexcept; + constexpr bool operator==(const nullable& lhs, const U& rhs) noexcept; template - requires (!util::is_optional_v && std::three_way_comparable_with) + requires (!util::is_nullable_v && std::three_way_comparable_with) constexpr std::compare_three_way_result_t - operator<=>(const optional& lhs, const U& rhs) noexcept; + operator<=>(const nullable& lhs, const U& rhs) noexcept; template - constexpr bool operator==(const optional& lhs, const optional& rhs) noexcept; + constexpr bool operator==(const nullable& lhs, const nullable& rhs) noexcept; template U, class UB> constexpr std::compare_three_way_result_t - operator<=>(const optional& lhs, const optional& rhs) noexcept; + operator<=>(const nullable& lhs, const nullable& rhs) noexcept; template - constexpr optional make_optional(T&& value, B&& flag = true); + constexpr nullable make_nullable(T&& value, B&& flag = true); /*************************** - * optional implementation * + * nullable implementation * ***************************/ template - constexpr bool optional::has_value() const noexcept + constexpr bool nullable::has_value() const noexcept { return m_flag; } template - constexpr optional::operator bool() const noexcept + constexpr nullable::operator bool() const noexcept { return m_flag; } template - constexpr auto optional::operator*() & noexcept -> reference + constexpr auto nullable::operator*() & noexcept -> reference { return m_value; } template - constexpr auto optional::operator*() const & noexcept -> const_reference + constexpr auto nullable::operator*() const & noexcept -> const_reference { return m_value; } template - constexpr auto optional::operator*() && noexcept -> rvalue_reference + constexpr auto nullable::operator*() && noexcept -> rvalue_reference { return std::move(m_value); } template - constexpr auto optional::operator*() const && noexcept -> const_rvalue_reference + constexpr auto nullable::operator*() const && noexcept -> const_rvalue_reference { return std::move(m_value); } template - constexpr auto optional::value() & -> reference + constexpr auto nullable::value() & -> reference { throw_if_empty(); return m_value; } template - constexpr auto optional::value() const & -> const_reference + constexpr auto nullable::value() const & -> const_reference { throw_if_empty(); return m_value; } template - constexpr auto optional::value() && -> rvalue_reference + constexpr auto nullable::value() && -> rvalue_reference { throw_if_empty(); return std::move(m_value); } template - constexpr auto optional::value() const && -> const_rvalue_reference + constexpr auto nullable::value() const && -> const_rvalue_reference { throw_if_empty(); return std::move(m_value); @@ -410,20 +410,20 @@ namespace sparrow template template - constexpr auto optional::value_or(U&& default_value) const & -> value_type + constexpr auto nullable::value_or(U&& default_value) const & -> value_type { return bool(*this) ? **this : static_cast(std::forward(default_value)); } template template - constexpr auto optional::value_or(U&& default_value) && -> value_type + constexpr auto nullable::value_or(U&& default_value) && -> value_type { return bool(*this) ? std::move(**this) : static_cast(std::forward(default_value)); } template - void optional::swap(self_type& other) noexcept + void nullable::swap(self_type& other) noexcept { using std::swap; swap(m_value, other.m_value); @@ -431,69 +431,69 @@ namespace sparrow } template - void optional::reset() noexcept + void nullable::reset() noexcept { m_flag = false; } template - void optional::throw_if_empty() const + void nullable::throw_if_empty() const { if (!m_flag) { - throw std::bad_optional_access{}; + throw std::bad_nullable_access{}; } } template - constexpr void swap(optional& lhs, optional& rhs) noexcept + constexpr void swap(nullable& lhs, nullable& rhs) noexcept { lhs.swap(rhs); } template - constexpr bool operator==(const optional& lhs, std::nullopt_t) noexcept + constexpr bool operator==(const nullable& lhs, std::nullopt_t) noexcept { return !lhs; } template - constexpr std::strong_ordering operator<=>(const optional& lhs, std::nullopt_t) noexcept + constexpr std::strong_ordering operator<=>(const nullable& lhs, std::nullopt_t) noexcept { return bool(lhs) <=> false; } template - constexpr bool operator==(const optional& lhs, const U& rhs) noexcept + constexpr bool operator==(const nullable& lhs, const U& rhs) noexcept { return bool(lhs) && *lhs == rhs; } template - requires (!util::is_optional_v && std::three_way_comparable_with) + requires (!util::is_nullable_v && std::three_way_comparable_with) constexpr std::compare_three_way_result_t - operator<=>(const optional& lhs, const U& rhs) noexcept + operator<=>(const nullable& lhs, const U& rhs) noexcept { return bool(lhs) ? *lhs <=> rhs : std::strong_ordering::less; } template - constexpr bool operator==(const optional& lhs, const optional& rhs) noexcept + constexpr bool operator==(const nullable& lhs, const nullable& rhs) noexcept { return bool(rhs) ? lhs == *rhs : !bool(lhs); } template U, class UB> constexpr std::compare_three_way_result_t - operator<=>(const optional& lhs, const optional& rhs) noexcept + operator<=>(const nullable& lhs, const nullable& rhs) noexcept { return (bool(lhs) && bool(rhs)) ? *lhs <=> *rhs : bool(lhs) <=> bool(rhs); } template - constexpr optional make_optional(T&& value, B&& flag) + constexpr nullable make_nullable(T&& value, B&& flag) { - return optional(std::forward(value), std::forward(flag)); + return nullable(std::forward(value), std::forward(flag)); } } diff --git a/test/test_optional.cpp b/test/test_nullable.cpp similarity index 71% rename from test/test_optional.cpp rename to test/test_nullable.cpp index ec9e1fcd5..2d065f2e5 100644 --- a/test/test_optional.cpp +++ b/test/test_nullable.cpp @@ -12,34 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "sparrow/optional.hpp" +#include "sparrow/nullable.hpp" #include "doctest/doctest.h" namespace sparrow { - using optional_double = optional; - using optional_int = optional; + using nullable_double = nullable; + using nullable_int = nullable; - TEST_SUITE("optional value") + TEST_SUITE("nullable value") { TEST_CASE("constructors") { SUBCASE("default") { - optional_double d; + nullable_double d; CHECK_FALSE(d.has_value()); } SUBCASE("from nullopt") { - optional_double d(std::nullopt); + nullable_double d(std::nullopt); CHECK_FALSE(d.has_value()); } SUBCASE("from value") { - optional_double d(1.2); + nullable_double d(1.2); REQUIRE(d.has_value()); CHECK_EQ(d.value(), 1.2); } @@ -47,7 +47,7 @@ namespace sparrow SUBCASE("from value with conversion") { int i = 3; - optional_double d(i); + nullable_double d(i); REQUIRE(d.has_value()); CHECK_EQ(d.value(), 3.); } @@ -57,10 +57,10 @@ namespace sparrow double val = 1.2; bool b1 = true; - optional_double td1(val, b1); - optional_double td2(std::move(val), b1); - optional_double td3(val, std::move(b1)); - optional_double td4(std::move(val), std::move(b1)); + nullable_double td1(val, b1); + nullable_double td2(std::move(val), b1); + nullable_double td3(val, std::move(b1)); + nullable_double td4(std::move(val), std::move(b1)); REQUIRE(td1.has_value()); CHECK_EQ(td1.value(), val); @@ -72,10 +72,10 @@ namespace sparrow CHECK_EQ(td4.value(), val); bool b2 = false; - optional_double fd1(val, b2); - optional_double fd2(std::move(val), b2); - optional_double fd3(val, std::move(b2)); - optional_double fd4(std::move(val), std::move(b2)); + nullable_double fd1(val, b2); + nullable_double fd2(std::move(val), b2); + nullable_double fd3(val, std::move(b2)); + nullable_double fd4(std::move(val), std::move(b2)); CHECK_FALSE(fd1.has_value()); CHECK_FALSE(fd2.has_value()); @@ -88,24 +88,24 @@ namespace sparrow { SUBCASE("default") { - optional_double d1(1.2); - optional_double d2(d1); + nullable_double d1(1.2); + nullable_double d2(d1); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } SUBCASE("with conversion") { - optional_int i(2); - optional_double d(i); + nullable_int i(2); + nullable_double d(i); REQUIRE(d.has_value()); CHECK_EQ(i.value(), d.value()); } - SUBCASE("from empty optional") + SUBCASE("from empty nullable") { - optional_double d1(std::nullopt); - optional_double d2(d1); + nullable_double d1(std::nullopt); + nullable_double d2(d1); CHECK_FALSE(d2.has_value()); } } @@ -114,26 +114,26 @@ namespace sparrow { SUBCASE("default") { - optional_double d0(1.2); - optional_double d1(d0); - optional_double d2(std::move(d0)); + nullable_double d0(1.2); + nullable_double d1(d0); + nullable_double d2(std::move(d0)); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } SUBCASE("with conversion") { - optional_int i(2); - optional_int ci(i); - optional_double d(std::move(i)); + nullable_int i(2); + nullable_int ci(i); + nullable_double d(std::move(i)); REQUIRE(d.has_value()); CHECK_EQ(ci.value(), d.value()); } - SUBCASE("from empty optional") + SUBCASE("from empty nullable") { - optional_double d1(std::nullopt); - optional_double d2(std::move(d1)); + nullable_double d1(std::nullopt); + nullable_double d2(std::move(d1)); CHECK_FALSE(d2.has_value()); } } @@ -142,8 +142,8 @@ namespace sparrow { SUBCASE("default") { - optional_double d1(1.2); - optional_double d2(2.5); + nullable_double d1(1.2); + nullable_double d2(2.5); d2 = d1; REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -151,17 +151,17 @@ namespace sparrow SUBCASE("with conversion") { - optional_int d1(1); - optional_double d2(2.5); + nullable_int d1(1); + nullable_double d2(2.5); d2 = d1; REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } - SUBCASE("from empty optional") + SUBCASE("from empty nullable") { - optional_double d1(std::nullopt); - optional_double d2(2.5); + nullable_double d1(std::nullopt); + nullable_double d2(2.5); d2 = d1; CHECK_FALSE(d2.has_value()); } @@ -171,9 +171,9 @@ namespace sparrow { SUBCASE("default") { - optional_double d0(1.2); - optional_double d1(d0); - optional_double d2(2.5); + nullable_double d0(1.2); + nullable_double d1(d0); + nullable_double d2(2.5); d2 = std::move(d0); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -181,18 +181,18 @@ namespace sparrow SUBCASE("with conversion") { - optional_int d0(1); - optional_int d1(d0); - optional_double d2(2.5); + nullable_int d0(1); + nullable_int d1(d0); + nullable_double d2(2.5); d2 = std::move(d0); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } - SUBCASE("from empty optional") + SUBCASE("from empty nullable") { - optional_double d1(std::nullopt); - optional_double d2(2.3); + nullable_double d1(std::nullopt); + nullable_double d2(2.3); d2 = std::move(d1); CHECK_FALSE(d2.has_value()); } @@ -200,10 +200,10 @@ namespace sparrow TEST_CASE("conversion to bool") { - optional_double d1(1.2); + nullable_double d1(1.2); CHECK(d1); - optional_double d2(std::nullopt); + nullable_double d2(std::nullopt); CHECK_FALSE(d2); } @@ -214,8 +214,8 @@ namespace sparrow SUBCASE("& overload") { - optional_double d(initial); - optional_double& d1(d); + nullable_double d(initial); + nullable_double& d1(d); d1.value() = expected; CHECK_EQ(d.value(), expected); CHECK_EQ(*d, expected); @@ -223,16 +223,16 @@ namespace sparrow SUBCASE("const & overload") { - optional_double d(initial); - const optional_double& d2(d); + nullable_double d(initial); + const nullable_double& d2(d); CHECK_EQ(d2.value(), initial); CHECK_EQ(*d2, initial); } SUBCASE("&& overload") { - optional_double d(initial); - optional_double&& d3(std::move(d)); + nullable_double d(initial); + nullable_double&& d3(std::move(d)); d3.value() = expected; CHECK_EQ(d.value(), expected); CHECK_EQ(*d, expected); @@ -240,16 +240,16 @@ namespace sparrow SUBCASE("const && overload") { - optional_double d(initial); - const optional_double&& d4(std::move(d)); + nullable_double d(initial); + const nullable_double&& d4(std::move(d)); CHECK_EQ(d4.value(), initial); CHECK_EQ(*d4, initial); } SUBCASE("empty") { - optional_double empty = std::nullopt; - CHECK_THROWS_AS(empty.value(), std::bad_optional_access); + nullable_double empty = std::nullopt; + CHECK_THROWS_AS(empty.value(), std::bad_nullable_access); CHECK_NOTHROW(*empty); } } @@ -259,13 +259,13 @@ namespace sparrow constexpr double initial = 1.2; constexpr double expected = 2.5; - optional_double d(initial); - optional_double empty(std::nullopt); + nullable_double d(initial); + nullable_double empty(std::nullopt); SUBCASE("const & overload") { - const optional_double& ref(d); - const optional_double& ref_empty(empty); + const nullable_double& ref(d); + const nullable_double& ref_empty(empty); double res = ref.value_or(expected); double res_empty = ref_empty.value_or(expected); @@ -276,8 +276,8 @@ namespace sparrow SUBCASE("&& overload") { - optional_double&& ref(std::move(d)); - optional_double&& ref_empty(std::move(empty)); + nullable_double&& ref(std::move(d)); + nullable_double&& ref_empty(std::move(empty)); double res = ref.value_or(expected); double res_empty = ref_empty.value_or(expected); @@ -292,9 +292,9 @@ namespace sparrow { constexpr double initial = 1.2; constexpr double expected = 2.5; - optional_double d1(initial); - optional_double d2(expected); - optional_double empty(std::nullopt); + nullable_double d1(initial); + nullable_double d2(expected); + nullable_double empty(std::nullopt); swap(d1, d2); CHECK_EQ(d1.value(), expected); @@ -308,7 +308,7 @@ namespace sparrow TEST_CASE("reset") { constexpr double initial = 1.2; - optional_double d(initial); + nullable_double d(initial); d.reset(); CHECK_FALSE(d.has_value()); } @@ -318,9 +318,9 @@ namespace sparrow constexpr double initial = 1.2; constexpr double other = 2.5; - optional_double d1(initial); - optional_double d2(other); - optional_double empty; + nullable_double d1(initial); + nullable_double d2(other); + nullable_double empty; CHECK(d1 == d1); CHECK(d1 == d1.value()); @@ -335,9 +335,9 @@ namespace sparrow constexpr double initial = 1.2; constexpr double other = 2.5; - optional_double d1(initial); - optional_double d2(other); - optional_double empty; + nullable_double d1(initial); + nullable_double d2(other); + nullable_double empty; // opearator <= CHECK(d1 <= d1); @@ -376,31 +376,31 @@ namespace sparrow CHECK_FALSE(empty > d1); } - TEST_CASE("make_optional") + TEST_CASE("make_nullable") { double value = 2.5; - auto opt = make_optional(std::move(value), true); - static_assert(std::same_as, optional_double>); + auto opt = make_nullable(std::move(value), true); + static_assert(std::same_as, nullable_double>); REQUIRE(opt.has_value()); CHECK_EQ(opt.value(), value); } } - using optional_proxy = optional; + using nullable_proxy = nullable; - TEST_SUITE("optional proxy") + TEST_SUITE("nullable proxy") { TEST_CASE("constructors") { double val = 1.2; bool b1 = true; - optional_proxy td(val); + nullable_proxy td(val); REQUIRE(td.has_value()); CHECK_EQ(td.value(), val); - optional_proxy td1(val, b1); - optional_proxy td2(val, std::move(b1)); + nullable_proxy td1(val, b1); + nullable_proxy td2(val, std::move(b1)); REQUIRE(td1.has_value()); CHECK_EQ(td1.value(), val); @@ -408,8 +408,8 @@ namespace sparrow CHECK_EQ(td2.value(), val); bool b2 = false; - optional_proxy fd1(val, b2); - optional_proxy fd2(val, std::move(b2)); + nullable_proxy fd1(val, b2); + nullable_proxy fd2(val, std::move(b2)); CHECK_FALSE(fd1.has_value()); CHECK_FALSE(fd2.has_value()); @@ -418,8 +418,8 @@ namespace sparrow TEST_CASE("copy constructors") { double val = 1.2; - optional_proxy d1(val); - optional_proxy d2(d1); + nullable_proxy d1(val); + nullable_proxy d2(d1); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); } @@ -427,8 +427,8 @@ namespace sparrow TEST_CASE("move constructor") { double val = 1.2; - optional_proxy d1(val); - optional_proxy d2(std::move(d1)); + nullable_proxy d1(val); + nullable_proxy d2(std::move(d1)); REQUIRE(d2.has_value()); CHECK_EQ(d2.value(), val); } @@ -439,8 +439,8 @@ namespace sparrow { double initial = 1.2; double expected = 2.5; - optional_proxy d1(initial); - optional_proxy d2(expected); + nullable_proxy d1(initial); + nullable_proxy d2(expected); d2 = d1; REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -451,18 +451,18 @@ namespace sparrow { double initial = 1.2; double expected = 2.5; - optional_double d1(initial); - optional_proxy d2(expected); + nullable_double d1(initial); + nullable_proxy d2(expected); d2 = d1; REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); CHECK_EQ(initial, expected); } - SUBCASE("from empty optional") + SUBCASE("from empty nullable") { double initial = 1.2; - optional_proxy d2(initial); + nullable_proxy d2(initial); d2 = std::nullopt; CHECK_FALSE(d2.has_value()); } @@ -474,8 +474,8 @@ namespace sparrow { double initial = 1.2; double expected = 2.5; - optional_proxy d1(initial); - optional_proxy d2(expected); + nullable_proxy d1(initial); + nullable_proxy d2(expected); d2 = std::move(d1); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -487,8 +487,8 @@ namespace sparrow { double initial = 1.2; double expected = 2.5; - optional_double d1(initial); - optional_proxy d2(expected); + nullable_double d1(initial); + nullable_proxy d2(expected); d2 = std::move(d1); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -499,7 +499,7 @@ namespace sparrow TEST_CASE("conversion to bool") { double val = 1.2; - optional_proxy d1(val); + nullable_proxy d1(val); CHECK(d1); d1 = std::nullopt; @@ -513,8 +513,8 @@ namespace sparrow SUBCASE("& overload") { - optional_proxy d(initial); - optional_proxy& d1(d); + nullable_proxy d(initial); + nullable_proxy& d1(d); d1.value() = expected; CHECK_EQ(d.value(), expected); CHECK_EQ(*d, expected); @@ -522,16 +522,16 @@ namespace sparrow SUBCASE("const & overload") { - optional_proxy d(initial); - const optional_proxy& d2(d); + nullable_proxy d(initial); + const nullable_proxy& d2(d); CHECK_EQ(d2.value(), initial); CHECK_EQ(*d2, initial); } SUBCASE("&& overload") { - optional_proxy d(initial); - optional_proxy&& d3(std::move(d)); + nullable_proxy d(initial); + nullable_proxy&& d3(std::move(d)); d3.value() = expected; CHECK_EQ(d.value(), expected); CHECK_EQ(*d, expected); @@ -539,17 +539,17 @@ namespace sparrow SUBCASE("const && overload") { - optional_proxy d(initial); - const optional_proxy&& d4(std::move(d)); + nullable_proxy d(initial); + const nullable_proxy&& d4(std::move(d)); CHECK_EQ(d4.value(), initial); CHECK_EQ(*d4, initial); } SUBCASE("empty") { - optional_proxy empty(initial); + nullable_proxy empty(initial); empty = std::nullopt; - CHECK_THROWS_AS(empty.value(), std::bad_optional_access); + CHECK_THROWS_AS(empty.value(), std::bad_nullable_access); CHECK_NOTHROW(*empty); } } @@ -559,14 +559,14 @@ namespace sparrow double initial = 1.2; double expected = 2.5; - optional_proxy d(initial); - optional_proxy empty(initial); + nullable_proxy d(initial); + nullable_proxy empty(initial); empty = std::nullopt; SUBCASE("const & overload") { - const optional_proxy& ref(d); - const optional_proxy& ref_empty(empty); + const nullable_proxy& ref(d); + const nullable_proxy& ref_empty(empty); double res = ref.value_or(expected); double res_empty = ref_empty.value_or(expected); @@ -577,8 +577,8 @@ namespace sparrow SUBCASE("&& overload") { - optional_proxy&& ref(std::move(d)); - optional_proxy&& ref_empty(std::move(empty)); + nullable_proxy&& ref(std::move(d)); + nullable_proxy&& ref_empty(std::move(empty)); double res = ref.value_or(expected); double res_empty = ref_empty.value_or(expected); @@ -595,9 +595,9 @@ namespace sparrow double initial_bu = initial; double expected_bu = expected; double empty_val = 3.7; - optional_proxy d1(initial); - optional_proxy d2(expected); - optional_proxy empty(empty_val); + nullable_proxy d1(initial); + nullable_proxy d2(expected); + nullable_proxy empty(empty_val); empty = std::nullopt; swap(d1, d2); @@ -612,7 +612,7 @@ namespace sparrow TEST_CASE("reset") { double initial = 1.2; - optional_proxy d(initial); + nullable_proxy d(initial); d.reset(); CHECK_FALSE(d.has_value()); } @@ -623,9 +623,9 @@ namespace sparrow double other = 2.5; double empty_val = 3.7; - optional_proxy d1(initial); - optional_proxy d2(other); - optional_proxy empty(empty_val); + nullable_proxy d1(initial); + nullable_proxy d2(other); + nullable_proxy empty(empty_val); empty = std::nullopt; CHECK(d1 == d1); @@ -642,9 +642,9 @@ namespace sparrow double other = 2.5; double empty_val = 3.7; - optional_proxy d1(initial); - optional_proxy d2(other); - optional_proxy empty(empty_val); + nullable_proxy d1(initial); + nullable_proxy d2(other); + nullable_proxy empty(empty_val); empty = std::nullopt; // opearator <= @@ -684,11 +684,11 @@ namespace sparrow CHECK_FALSE(empty > d1); } - TEST_CASE("make_optional") + TEST_CASE("make_nullable") { double value = 2.7; - auto opt = make_optional(value, true); - static_assert(std::same_as, optional_proxy>); + auto opt = make_nullable(value, true); + static_assert(std::same_as, nullable_proxy>); REQUIRE(opt.has_value()); CHECK_EQ(opt.value(), value); } From d849115ee534d9b8377aa12e71594e31d313f1de Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Jul 2024 16:57:02 +0200 Subject: [PATCH 24/34] Fixed renaming --- CMakeLists.txt | 2 +- include/sparrow/nullable.hpp | 4 ++-- test/CMakeLists.txt | 2 +- test/test_nullable.cpp | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f9f7b582..408c00d08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,7 +107,7 @@ set(SPARROW_HEADERS ${SPARROW_INCLUDE_DIR}/sparrow/memory.hpp ${SPARROW_INCLUDE_DIR}/sparrow/mp_utils.hpp ${SPARROW_INCLUDE_DIR}/sparrow/null_layout.hpp - ${SPARROW_INCLUDE_DIR}/sparrow/optional.hpp + ${SPARROW_INCLUDE_DIR}/sparrow/nullable.hpp ${SPARROW_INCLUDE_DIR}/sparrow/sparrow_version.hpp ${SPARROW_INCLUDE_DIR}/sparrow/typed_array.hpp ${SPARROW_INCLUDE_DIR}/sparrow/variable_size_binary_layout.hpp diff --git a/include/sparrow/nullable.hpp b/include/sparrow/nullable.hpp index 40bcf117b..d2e749dd7 100644 --- a/include/sparrow/nullable.hpp +++ b/include/sparrow/nullable.hpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include "sparrow/mp_utils.hpp" @@ -441,7 +441,7 @@ namespace sparrow { if (!m_flag) { - throw std::bad_nullable_access{}; + throw std::bad_optional_access{}; } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 81538a24c..ef6d08751 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -52,7 +52,7 @@ set(SPARROW_TESTS_SOURCES test_memory.cpp test_mpl.cpp test_null_layout.cpp - test_optional.cpp + test_nullable.cpp test_traits.cpp test_typed_array.cpp test_typed_array_timestamp.cpp diff --git a/test/test_nullable.cpp b/test/test_nullable.cpp index 2d065f2e5..a1bc69273 100644 --- a/test/test_nullable.cpp +++ b/test/test_nullable.cpp @@ -249,7 +249,7 @@ namespace sparrow SUBCASE("empty") { nullable_double empty = std::nullopt; - CHECK_THROWS_AS(empty.value(), std::bad_nullable_access); + CHECK_THROWS_AS(empty.value(), std::bad_optional_access); CHECK_NOTHROW(*empty); } } @@ -549,7 +549,7 @@ namespace sparrow { nullable_proxy empty(initial); empty = std::nullopt; - CHECK_THROWS_AS(empty.value(), std::bad_nullable_access); + CHECK_THROWS_AS(empty.value(), std::bad_optional_access); CHECK_NOTHROW(*empty); } } From ac74c3145056b3e8b90624c9bf9ab3f5827a3ee4 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Mon, 8 Jul 2024 18:02:58 +0200 Subject: [PATCH 25/34] Changes according to review --- include/sparrow/nullable.hpp | 166 ++++++++++++++++++++++------------ test/test_nullable.cpp | 170 +++++++++++++++++------------------ 2 files changed, 196 insertions(+), 140 deletions(-) diff --git a/include/sparrow/nullable.hpp b/include/sparrow/nullable.hpp index d2e749dd7..afb2db4c2 100644 --- a/include/sparrow/nullable.hpp +++ b/include/sparrow/nullable.hpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include "sparrow/mp_utils.hpp" @@ -28,7 +28,22 @@ namespace sparrow template class nullable; - + /* + * Default traits for the nullable class. These traits should be specialized + * for proxy classes whose reference and const_reference types are not + * defined as usual. For instance: + * + * @code{.cpp} + * struct nullable_traits + * { + * using value_type = std::string; + * using reference = string_proxy; + * using const_reference = std::string_view; + * using rvalue_reverence = std::string&&; + * using const_rvalue_reference = const std::string&&; + * }; + * @endcode + */ template struct nullable_traits { @@ -50,8 +65,40 @@ namespace sparrow using const_rvalue_reference = std::add_rvalue_reference_t>; }; - namespace util + /* + * Defines a type of object to be thrown by nullable::value when accessing + * a nullable object whose value is null. + */ + class bad_nullable_access : public std::exception + { + public: + + bad_nullable_access() noexcept = default; + bad_nullable_access(const bad_nullable_access&) noexcept = default; + bad_nullable_access& operator=(const bad_nullable_access&) noexcept = default; + + virtual const char* what() const noexcept; + + private: + + static constexpr const char* message = "bad nullable access"; + }; + + /** + * nullval_t is an empty class used to indicate that a nullable is null. + */ + struct nullval_t { + constexpr explicit nullval_t(int) {} + }; + + inline constexpr nullval_t nullval(0); + + namespace impl + { + /** + * Concepts used to disambiguate the nullable class constructors. + */ template concept both_constructible_from_cref = std::constructible_from> and @@ -125,12 +172,7 @@ namespace sparrow } /** - * The nullable class is similar to std::nullable, with two major differences: - * - it can act as a proxy, meaning its template parameter can be lvalue references - * or lvalue const references - * - the semantic for empty nullable: resetting an non empty nullable does not destruct - * the contained value. Allocating an empty nullable construct the contained value. This - * behavior is temporary and will be changed in the near future. + * The nullable class. */ template class nullable @@ -152,7 +194,7 @@ namespace sparrow { } - constexpr nullable(std::nullopt_t) noexcept + constexpr nullable(nullval_t) noexcept : m_value() , m_flag(false) { @@ -174,10 +216,10 @@ namespace sparrow template requires ( - util::both_constructible_from_cref and - not util::initializable_from_refs> + impl::both_constructible_from_cref and + not impl::initializable_from_refs> ) - explicit(not util::both_convertible_from_cref) + explicit(not impl::both_convertible_from_cref) constexpr nullable(const nullable& rhs) : m_value(rhs.m_value) , m_flag(rhs.m_flag) @@ -188,10 +230,10 @@ namespace sparrow template requires ( - util::both_constructible_from_cond_ref and - not util::initializable_from_refs> + impl::both_constructible_from_cond_ref and + not impl::initializable_from_refs> ) - explicit(not util::both_convertible_from_cond_ref) + explicit(not impl::both_convertible_from_cond_ref) constexpr nullable(nullable&& rhs) : m_value(std::move(rhs.m_value)) , m_flag(std::move(rhs.m_flag)) @@ -222,7 +264,7 @@ namespace sparrow { } - constexpr self_type& operator=(std::nullopt_t) + constexpr self_type& operator=(nullval_t) { m_flag = false; return *this; @@ -249,9 +291,9 @@ namespace sparrow template requires( - util::both_assignable_from_cref and - not util::initializable_from_refs> and - not util::assignable_from_refs> + impl::both_assignable_from_cref and + not impl::initializable_from_refs> and + not impl::assignable_from_refs> ) constexpr self_type& operator=(const nullable& rhs) { @@ -269,9 +311,9 @@ namespace sparrow template requires( - util::both_assignable_from_cond_ref and - not util::initializable_from_refs> and - not util::assignable_from_refs> + impl::both_assignable_from_cond_ref and + not impl::initializable_from_refs> and + not impl::assignable_from_refs> ) constexpr self_type& operator=(nullable&& rhs) { @@ -283,10 +325,10 @@ namespace sparrow constexpr bool has_value() const noexcept; constexpr explicit operator bool() const noexcept; - constexpr reference operator*() & noexcept; - constexpr const_reference operator*() const & noexcept; - constexpr rvalue_reference operator*() && noexcept; - constexpr const_rvalue_reference operator*() const && noexcept; + constexpr reference get() & noexcept; + constexpr const_reference get() const & noexcept; + constexpr rvalue_reference get() && noexcept; + constexpr const_rvalue_reference get() const && noexcept; constexpr reference value() &; constexpr const_reference value() const &; @@ -304,7 +346,7 @@ namespace sparrow private: - void throw_if_empty() const; + void throw_if_null() const; T m_value; B m_flag; @@ -317,16 +359,16 @@ namespace sparrow constexpr void swap(nullable& lhs, nullable& rhs) noexcept; template - constexpr bool operator==(const nullable& lhs, std::nullopt_t) noexcept; + constexpr bool operator==(const nullable& lhs, nullval_t) noexcept; template - constexpr std::strong_ordering operator<=>(const nullable& lhs, std::nullopt_t) noexcept; + constexpr std::strong_ordering operator<=>(const nullable& lhs, nullval_t) noexcept; template constexpr bool operator==(const nullable& lhs, const U& rhs) noexcept; template - requires (!util::is_nullable_v && std::three_way_comparable_with) + requires (!impl::is_nullable_v && std::three_way_comparable_with) constexpr std::compare_three_way_result_t operator<=>(const nullable& lhs, const U& rhs) noexcept; @@ -340,6 +382,18 @@ namespace sparrow template constexpr nullable make_nullable(T&& value, B&& flag = true); + /************************************** + * bad_nullable_access implementation * + **************************************/ + + /*bad_nullable_access::bad_nullable_access(const bad_nullable_access&) noexcept; + bad_nullable_access& bad_nullable_access::operator=(const bad_nullable_access&) noexcept;*/ + + const char* bad_nullable_access::what() const noexcept + { + return message; + } + /*************************** * nullable implementation * ***************************/ @@ -357,69 +411,71 @@ namespace sparrow } template - constexpr auto nullable::operator*() & noexcept -> reference + constexpr auto nullable::get() & noexcept -> reference { return m_value; } template - constexpr auto nullable::operator*() const & noexcept -> const_reference + constexpr auto nullable::get() const & noexcept -> const_reference { return m_value; } template - constexpr auto nullable::operator*() && noexcept -> rvalue_reference + constexpr auto nullable::get() && noexcept -> rvalue_reference { + reset(); return std::move(m_value); } template - constexpr auto nullable::operator*() const && noexcept -> const_rvalue_reference + constexpr auto nullable::get() const && noexcept -> const_rvalue_reference { + reset(); return std::move(m_value); } template constexpr auto nullable::value() & -> reference { - throw_if_empty(); - return m_value; + throw_if_null(); + return get(); } template constexpr auto nullable::value() const & -> const_reference { - throw_if_empty(); - return m_value; + throw_if_null(); + return get(); } template constexpr auto nullable::value() && -> rvalue_reference { - throw_if_empty(); - return std::move(m_value); + throw_if_null(); + return get(); } template constexpr auto nullable::value() const && -> const_rvalue_reference { - throw_if_empty(); - return std::move(m_value); + throw_if_null(); + return get(); } template template constexpr auto nullable::value_or(U&& default_value) const & -> value_type { - return bool(*this) ? **this : static_cast(std::forward(default_value)); + return *this ? get() : value_type(std::forward(default_value)); } template template constexpr auto nullable::value_or(U&& default_value) && -> value_type { - return bool(*this) ? std::move(**this) : static_cast(std::forward(default_value)); + return *this ? get() : value_type(std::forward(default_value)); } template @@ -437,11 +493,11 @@ namespace sparrow } template - void nullable::throw_if_empty() const + void nullable::throw_if_null() const { if (!m_flag) { - throw std::bad_optional_access{}; + throw bad_nullable_access{}; } } @@ -452,42 +508,42 @@ namespace sparrow } template - constexpr bool operator==(const nullable& lhs, std::nullopt_t) noexcept + constexpr bool operator==(const nullable& lhs, nullval_t) noexcept { return !lhs; } template - constexpr std::strong_ordering operator<=>(const nullable& lhs, std::nullopt_t) noexcept + constexpr std::strong_ordering operator<=>(const nullable& lhs, nullval_t) noexcept { - return bool(lhs) <=> false; + return lhs <=> false; } template constexpr bool operator==(const nullable& lhs, const U& rhs) noexcept { - return bool(lhs) && *lhs == rhs; + return lhs && (lhs.get() == rhs); } template - requires (!util::is_nullable_v && std::three_way_comparable_with) + requires (!impl::is_nullable_v && std::three_way_comparable_with) constexpr std::compare_three_way_result_t operator<=>(const nullable& lhs, const U& rhs) noexcept { - return bool(lhs) ? *lhs <=> rhs : std::strong_ordering::less; + return lhs ? lhs.get() <=> rhs : std::strong_ordering::less; } template constexpr bool operator==(const nullable& lhs, const nullable& rhs) noexcept { - return bool(rhs) ? lhs == *rhs : !bool(lhs); + return rhs ? lhs == rhs.get() : !lhs; } template U, class UB> constexpr std::compare_three_way_result_t operator<=>(const nullable& lhs, const nullable& rhs) noexcept { - return (bool(lhs) && bool(rhs)) ? *lhs <=> *rhs : bool(lhs) <=> bool(rhs); + return (lhs && rhs) ? lhs.get() <=> rhs.get() : bool(lhs) <=> bool(rhs); } template diff --git a/test/test_nullable.cpp b/test/test_nullable.cpp index a1bc69273..504ded864 100644 --- a/test/test_nullable.cpp +++ b/test/test_nullable.cpp @@ -31,15 +31,15 @@ namespace sparrow CHECK_FALSE(d.has_value()); } - SUBCASE("from nullopt") + SUBCASE("from nullval") { - nullable_double d(std::nullopt); + nullable_double d{nullval}; CHECK_FALSE(d.has_value()); } SUBCASE("from value") { - nullable_double d(1.2); + nullable_double d{1.2}; REQUIRE(d.has_value()); CHECK_EQ(d.value(), 1.2); } @@ -47,7 +47,7 @@ namespace sparrow SUBCASE("from value with conversion") { int i = 3; - nullable_double d(i); + nullable_double d{i}; REQUIRE(d.has_value()); CHECK_EQ(d.value(), 3.); } @@ -88,7 +88,7 @@ namespace sparrow { SUBCASE("default") { - nullable_double d1(1.2); + nullable_double d1{1.2}; nullable_double d2(d1); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -96,7 +96,7 @@ namespace sparrow SUBCASE("with conversion") { - nullable_int i(2); + nullable_int i{2}; nullable_double d(i); REQUIRE(d.has_value()); CHECK_EQ(i.value(), d.value()); @@ -104,7 +104,7 @@ namespace sparrow SUBCASE("from empty nullable") { - nullable_double d1(std::nullopt); + nullable_double d1(nullval); nullable_double d2(d1); CHECK_FALSE(d2.has_value()); } @@ -114,7 +114,7 @@ namespace sparrow { SUBCASE("default") { - nullable_double d0(1.2); + nullable_double d0{1.2}; nullable_double d1(d0); nullable_double d2(std::move(d0)); REQUIRE(d2.has_value()); @@ -123,7 +123,7 @@ namespace sparrow SUBCASE("with conversion") { - nullable_int i(2); + nullable_int i{2}; nullable_int ci(i); nullable_double d(std::move(i)); REQUIRE(d.has_value()); @@ -132,7 +132,7 @@ namespace sparrow SUBCASE("from empty nullable") { - nullable_double d1(std::nullopt); + nullable_double d1(nullval); nullable_double d2(std::move(d1)); CHECK_FALSE(d2.has_value()); } @@ -142,8 +142,8 @@ namespace sparrow { SUBCASE("default") { - nullable_double d1(1.2); - nullable_double d2(2.5); + nullable_double d1{1.2}; + nullable_double d2{2.5}; d2 = d1; REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -151,8 +151,8 @@ namespace sparrow SUBCASE("with conversion") { - nullable_int d1(1); - nullable_double d2(2.5); + nullable_int d1{1}; + nullable_double d2{2.5}; d2 = d1; REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -160,8 +160,8 @@ namespace sparrow SUBCASE("from empty nullable") { - nullable_double d1(std::nullopt); - nullable_double d2(2.5); + nullable_double d1{nullval}; + nullable_double d2{2.5}; d2 = d1; CHECK_FALSE(d2.has_value()); } @@ -171,9 +171,9 @@ namespace sparrow { SUBCASE("default") { - nullable_double d0(1.2); + nullable_double d0{1.2}; nullable_double d1(d0); - nullable_double d2(2.5); + nullable_double d2{2.5}; d2 = std::move(d0); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -181,9 +181,9 @@ namespace sparrow SUBCASE("with conversion") { - nullable_int d0(1); + nullable_int d0{1}; nullable_int d1(d0); - nullable_double d2(2.5); + nullable_double d2{2.5}; d2 = std::move(d0); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -191,8 +191,8 @@ namespace sparrow SUBCASE("from empty nullable") { - nullable_double d1(std::nullopt); - nullable_double d2(2.3); + nullable_double d1{nullval}; + nullable_double d2{2.3}; d2 = std::move(d1); CHECK_FALSE(d2.has_value()); } @@ -200,57 +200,57 @@ namespace sparrow TEST_CASE("conversion to bool") { - nullable_double d1(1.2); + nullable_double d1{1.2}; CHECK(d1); - nullable_double d2(std::nullopt); + nullable_double d2{nullval}; CHECK_FALSE(d2); } - TEST_CASE("value / operator*") + TEST_CASE("value / get") { constexpr double initial = 1.2; constexpr double expected = 2.5; SUBCASE("& overload") { - nullable_double d(initial); - nullable_double& d1(d); + nullable_double d{initial}; + nullable_double& d1{d}; d1.value() = expected; CHECK_EQ(d.value(), expected); - CHECK_EQ(*d, expected); + CHECK_EQ(d.get(), expected); } SUBCASE("const & overload") { - nullable_double d(initial); - const nullable_double& d2(d); + nullable_double d{initial}; + const nullable_double& d2{d}; CHECK_EQ(d2.value(), initial); - CHECK_EQ(*d2, initial); + CHECK_EQ(d2.get(), initial); } SUBCASE("&& overload") { - nullable_double d(initial); + nullable_double d{initial}; nullable_double&& d3(std::move(d)); d3.value() = expected; CHECK_EQ(d.value(), expected); - CHECK_EQ(*d, expected); + CHECK_EQ(d.get(), expected); } SUBCASE("const && overload") { - nullable_double d(initial); + nullable_double d{initial}; const nullable_double&& d4(std::move(d)); CHECK_EQ(d4.value(), initial); - CHECK_EQ(*d4, initial); + CHECK_EQ(d4.get(), initial); } SUBCASE("empty") { - nullable_double empty = std::nullopt; - CHECK_THROWS_AS(empty.value(), std::bad_optional_access); - CHECK_NOTHROW(*empty); + nullable_double empty = nullval; + CHECK_THROWS_AS(empty.value(), bad_nullable_access); + CHECK_NOTHROW(empty.get()); } } @@ -259,8 +259,8 @@ namespace sparrow constexpr double initial = 1.2; constexpr double expected = 2.5; - nullable_double d(initial); - nullable_double empty(std::nullopt); + nullable_double d{initial}; + nullable_double empty{nullval}; SUBCASE("const & overload") { @@ -292,9 +292,9 @@ namespace sparrow { constexpr double initial = 1.2; constexpr double expected = 2.5; - nullable_double d1(initial); - nullable_double d2(expected); - nullable_double empty(std::nullopt); + nullable_double d1{initial}; + nullable_double d2{expected}; + nullable_double empty{nullval}; swap(d1, d2); CHECK_EQ(d1.value(), expected); @@ -308,7 +308,7 @@ namespace sparrow TEST_CASE("reset") { constexpr double initial = 1.2; - nullable_double d(initial); + nullable_double d{initial}; d.reset(); CHECK_FALSE(d.has_value()); } @@ -318,8 +318,8 @@ namespace sparrow constexpr double initial = 1.2; constexpr double other = 2.5; - nullable_double d1(initial); - nullable_double d2(other); + nullable_double d1{initial}; + nullable_double d2{other}; nullable_double empty; CHECK(d1 == d1); @@ -335,8 +335,8 @@ namespace sparrow constexpr double initial = 1.2; constexpr double other = 2.5; - nullable_double d1(initial); - nullable_double d2(other); + nullable_double d1{initial}; + nullable_double d2{other}; nullable_double empty; // opearator <= @@ -439,8 +439,8 @@ namespace sparrow { double initial = 1.2; double expected = 2.5; - nullable_proxy d1(initial); - nullable_proxy d2(expected); + nullable_proxy d1{initial}; + nullable_proxy d2{expected}; d2 = d1; REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -451,8 +451,8 @@ namespace sparrow { double initial = 1.2; double expected = 2.5; - nullable_double d1(initial); - nullable_proxy d2(expected); + nullable_double d1{initial}; + nullable_proxy d2{expected}; d2 = d1; REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -463,7 +463,7 @@ namespace sparrow { double initial = 1.2; nullable_proxy d2(initial); - d2 = std::nullopt; + d2 = nullval; CHECK_FALSE(d2.has_value()); } } @@ -474,8 +474,8 @@ namespace sparrow { double initial = 1.2; double expected = 2.5; - nullable_proxy d1(initial); - nullable_proxy d2(expected); + nullable_proxy d1{initial}; + nullable_proxy d2{expected}; d2 = std::move(d1); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -487,8 +487,8 @@ namespace sparrow { double initial = 1.2; double expected = 2.5; - nullable_double d1(initial); - nullable_proxy d2(expected); + nullable_double d1{initial}; + nullable_proxy d2{expected}; d2 = std::move(d1); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); @@ -502,55 +502,55 @@ namespace sparrow nullable_proxy d1(val); CHECK(d1); - d1 = std::nullopt; + d1 = nullval; CHECK_FALSE(d1); } - TEST_CASE("value / operator*") + TEST_CASE("value / get") { double initial = 1.2; double expected = 2.5; SUBCASE("& overload") { - nullable_proxy d(initial); + nullable_proxy d{initial}; nullable_proxy& d1(d); d1.value() = expected; CHECK_EQ(d.value(), expected); - CHECK_EQ(*d, expected); + CHECK_EQ(d.get(), expected); } SUBCASE("const & overload") { - nullable_proxy d(initial); + nullable_proxy d{initial}; const nullable_proxy& d2(d); CHECK_EQ(d2.value(), initial); - CHECK_EQ(*d2, initial); + CHECK_EQ(d2.get(), initial); } SUBCASE("&& overload") { - nullable_proxy d(initial); + nullable_proxy d{initial}; nullable_proxy&& d3(std::move(d)); d3.value() = expected; CHECK_EQ(d.value(), expected); - CHECK_EQ(*d, expected); + CHECK_EQ(d.get(), expected); } SUBCASE("const && overload") { - nullable_proxy d(initial); + nullable_proxy d{initial}; const nullable_proxy&& d4(std::move(d)); CHECK_EQ(d4.value(), initial); - CHECK_EQ(*d4, initial); + CHECK_EQ(d4.get(), initial); } SUBCASE("empty") { nullable_proxy empty(initial); - empty = std::nullopt; - CHECK_THROWS_AS(empty.value(), std::bad_optional_access); - CHECK_NOTHROW(*empty); + empty = nullval; + CHECK_THROWS_AS(empty.value(), bad_nullable_access); + CHECK_NOTHROW(empty.get()); } } @@ -559,9 +559,9 @@ namespace sparrow double initial = 1.2; double expected = 2.5; - nullable_proxy d(initial); + nullable_proxy d{initial}; nullable_proxy empty(initial); - empty = std::nullopt; + empty = nullval; SUBCASE("const & overload") { @@ -595,10 +595,10 @@ namespace sparrow double initial_bu = initial; double expected_bu = expected; double empty_val = 3.7; - nullable_proxy d1(initial); - nullable_proxy d2(expected); - nullable_proxy empty(empty_val); - empty = std::nullopt; + nullable_proxy d1{initial}; + nullable_proxy d2{expected}; + nullable_proxy empty{empty_val}; + empty = nullval; swap(d1, d2); CHECK_EQ(d1.value(), expected_bu); @@ -612,7 +612,7 @@ namespace sparrow TEST_CASE("reset") { double initial = 1.2; - nullable_proxy d(initial); + nullable_proxy d{initial}; d.reset(); CHECK_FALSE(d.has_value()); } @@ -623,10 +623,10 @@ namespace sparrow double other = 2.5; double empty_val = 3.7; - nullable_proxy d1(initial); - nullable_proxy d2(other); - nullable_proxy empty(empty_val); - empty = std::nullopt; + nullable_proxy d1{initial}; + nullable_proxy d2{other}; + nullable_proxy empty{empty_val}; + empty = nullval; CHECK(d1 == d1); CHECK(d1 == d1.value()); @@ -642,10 +642,10 @@ namespace sparrow double other = 2.5; double empty_val = 3.7; - nullable_proxy d1(initial); - nullable_proxy d2(other); - nullable_proxy empty(empty_val); - empty = std::nullopt; + nullable_proxy d1{initial}; + nullable_proxy d2{other}; + nullable_proxy empty{empty_val}; + empty = nullval; // opearator <= CHECK(d1 <= d1); From dff865e0749c78e8314c831a1599093438e58933 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Mon, 8 Jul 2024 18:04:51 +0200 Subject: [PATCH 26/34] Simplified is_nullable_v implmeentation --- include/sparrow/nullable.hpp | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/include/sparrow/nullable.hpp b/include/sparrow/nullable.hpp index afb2db4c2..9433ddf5b 100644 --- a/include/sparrow/nullable.hpp +++ b/include/sparrow/nullable.hpp @@ -158,17 +158,7 @@ namespace sparrow std::assignable_from, conditional_ref_t>; template - struct is_nullable : std::false_type - { - }; - - template - struct is_nullable> : std::true_type - { - }; - - template - static constexpr bool is_nullable_v = is_nullable::value; + static constexpr bool is_nullable_v = mpl::is_type_instance_of_v; } /** @@ -386,9 +376,6 @@ namespace sparrow * bad_nullable_access implementation * **************************************/ - /*bad_nullable_access::bad_nullable_access(const bad_nullable_access&) noexcept; - bad_nullable_access& bad_nullable_access::operator=(const bad_nullable_access&) noexcept;*/ - const char* bad_nullable_access::what() const noexcept { return message; From 03e1ecf10e7ec6bb50fcfd865dfb36171add5256 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Tue, 9 Jul 2024 09:23:10 +0200 Subject: [PATCH 27/34] Initialize from value / has_value overload to avoid accidental move operations --- include/sparrow/nullable.hpp | 113 +++++++++++++++++++++++++---------- 1 file changed, 83 insertions(+), 30 deletions(-) diff --git a/include/sparrow/nullable.hpp b/include/sparrow/nullable.hpp index 9433ddf5b..7835c47ec 100644 --- a/include/sparrow/nullable.hpp +++ b/include/sparrow/nullable.hpp @@ -61,8 +61,8 @@ namespace sparrow using unref_type = T; using reference = T&; using const_reference = std::add_lvalue_reference_t>; - using rvalue_reference = T&&; - using const_rvalue_reference = std::add_rvalue_reference_t>; + using rvalue_reference = reference; + using const_rvalue_reference = const_reference; }; /* @@ -170,13 +170,18 @@ namespace sparrow public: using self_type = nullable; - using traits = nullable_traits; - using value_type = typename traits::value_type; - using reference = typename traits::reference; - using const_reference = typename traits::const_reference; - using rvalue_reference = typename traits::rvalue_reference; - using const_rvalue_reference = typename traits::const_rvalue_reference; - using flag_type = std::decay_t; + using value_traits = nullable_traits; + using value_type = typename value_traits::value_type; + using reference = typename value_traits::reference; + using const_reference = typename value_traits::const_reference; + using rvalue_reference = typename value_traits::rvalue_reference; + using const_rvalue_reference = typename value_traits::const_rvalue_reference; + using flag_traits = nullable_traits; + using flag_type = typename flag_traits::value_type; + using flag_reference = typename flag_traits::reference; + using flag_const_reference = typename flag_traits::const_reference; + using flag_rvalue_reference = typename flag_traits::rvalue_reference; + using flag_const_rvalue_reference = typename flag_traits::const_rvalue_reference; constexpr nullable() noexcept : m_value() @@ -211,8 +216,8 @@ namespace sparrow ) explicit(not impl::both_convertible_from_cref) constexpr nullable(const nullable& rhs) - : m_value(rhs.m_value) - , m_flag(rhs.m_flag) + : m_value(rhs.get()) + , m_flag(rhs.has_value()) { } @@ -225,8 +230,8 @@ namespace sparrow ) explicit(not impl::both_convertible_from_cond_ref) constexpr nullable(nullable&& rhs) - : m_value(std::move(rhs.m_value)) - , m_flag(std::move(rhs.m_flag)) + : m_value(std::move(rhs).get()) + , m_flag(std::move(rhs).has_value()) { } @@ -274,8 +279,8 @@ namespace sparrow constexpr self_type& operator=(const self_type& rhs) { - m_value = rhs.m_value; - m_flag = rhs.m_flag; + m_value = rhs.get(); + m_flag = rhs.has_value(); return *this; } @@ -287,15 +292,15 @@ namespace sparrow ) constexpr self_type& operator=(const nullable& rhs) { - m_value = rhs.m_value; - m_flag = rhs.m_flag; + m_value = rhs.get(); + m_flag = rhs.has_value(); return *this; } constexpr self_type& operator=(self_type&& rhs) { - m_value = std::move(rhs.m_value); - m_flag = std::move(rhs.m_flag); + m_value = std::move(rhs).get(); + m_flag = std::move(rhs).has_value(); return *this; } @@ -307,14 +312,18 @@ namespace sparrow ) constexpr self_type& operator=(nullable&& rhs) { - m_value = std::move(rhs.m_value); - m_flag = std::move(rhs.m_flag); + m_value = std::move(rhs).get(); + m_flag = std::move(rhs).has_value(); return *this; } - constexpr bool has_value() const noexcept; constexpr explicit operator bool() const noexcept; + constexpr flag_reference has_value() & noexcept; + constexpr flag_const_reference has_value() const & noexcept; + constexpr flag_rvalue_reference has_value() && noexcept; + constexpr flag_const_rvalue_reference has_value() const && noexcept; + constexpr reference get() & noexcept; constexpr const_reference get() const & noexcept; constexpr rvalue_reference get() && noexcept; @@ -386,16 +395,48 @@ namespace sparrow ***************************/ template - constexpr bool nullable::has_value() const noexcept + constexpr nullable::operator bool() const noexcept { return m_flag; } template - constexpr nullable::operator bool() const noexcept + constexpr auto nullable::has_value() & noexcept -> flag_reference { return m_flag; } + + template + constexpr auto nullable::has_value() const & noexcept -> flag_const_reference + { + return m_flag; + } + + template + constexpr auto nullable::has_value() && noexcept -> flag_rvalue_reference + { + if constexpr (std::is_reference_v) + { + return m_flag; + } + else + { + return std::move(m_flag); + } + } + + template + constexpr auto nullable::has_value() const && noexcept -> flag_const_rvalue_reference + { + if constexpr (std::is_reference_v) + { + return m_flag; + } + else + { + return std::move(m_flag); + } + } template constexpr auto nullable::get() & noexcept -> reference @@ -412,15 +453,27 @@ namespace sparrow template constexpr auto nullable::get() && noexcept -> rvalue_reference { - reset(); - return std::move(m_value); + if constexpr (std::is_reference_v) + { + return m_value; + } + else + { + return std::move(m_value); + } } template constexpr auto nullable::get() const && noexcept -> const_rvalue_reference { - reset(); - return std::move(m_value); + if constexpr (std::is_reference_v) + { + return m_value; + } + else + { + return std::move(m_value); + } } template @@ -441,14 +494,14 @@ namespace sparrow constexpr auto nullable::value() && -> rvalue_reference { throw_if_null(); - return get(); + return std::move(*this).get(); } template constexpr auto nullable::value() const && -> const_rvalue_reference { throw_if_null(); - return get(); + return std::move(*this).get(); } template From 0ad626703d157771cd861403c389eddac3e9a327 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Tue, 9 Jul 2024 23:00:03 +0200 Subject: [PATCH 28/34] Minor improvements --- include/sparrow/nullable.hpp | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/include/sparrow/nullable.hpp b/include/sparrow/nullable.hpp index 7835c47ec..032298070 100644 --- a/include/sparrow/nullable.hpp +++ b/include/sparrow/nullable.hpp @@ -47,9 +47,9 @@ namespace sparrow template struct nullable_traits { - using value_type = std::decay_t; - using reference = value_type&; - using const_reference = const value_type&; + using value_type = T; + using reference = std::add_lvalue_reference_t; + using const_reference = std::add_lvalue_reference_t>; using rvalue_reference = value_type&&; using const_rvalue_reference = const value_type&&; }; @@ -57,10 +57,9 @@ namespace sparrow template struct nullable_traits { - using value_type = std::decay_t; - using unref_type = T; - using reference = T&; - using const_reference = std::add_lvalue_reference_t>; + using value_type = T; + using reference = std::add_lvalue_reference_t; + using const_reference = std::add_lvalue_reference_t>; using rvalue_reference = reference; using const_rvalue_reference = const_reference; }; @@ -77,7 +76,10 @@ namespace sparrow bad_nullable_access(const bad_nullable_access&) noexcept = default; bad_nullable_access& operator=(const bad_nullable_access&) noexcept = default; - virtual const char* what() const noexcept; + virtual const char* what() const noexcept + { + return message; + } private: @@ -381,15 +383,6 @@ namespace sparrow template constexpr nullable make_nullable(T&& value, B&& flag = true); - /************************************** - * bad_nullable_access implementation * - **************************************/ - - const char* bad_nullable_access::what() const noexcept - { - return message; - } - /*************************** * nullable implementation * ***************************/ @@ -421,7 +414,7 @@ namespace sparrow } else { - return std::move(m_flag); + return flag_rvalue_reference(m_flag); } } @@ -434,7 +427,7 @@ namespace sparrow } else { - return std::move(m_flag); + return flag_const_rvalue_reference(m_flag); } } @@ -459,7 +452,7 @@ namespace sparrow } else { - return std::move(m_value); + return rvalue_reference(m_value); } } @@ -472,7 +465,7 @@ namespace sparrow } else { - return std::move(m_value); + return const_rvalue_reference(m_value); } } From adf85c25a05be6e2ac5ddc1f09380cb3a8669c92 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Wed, 10 Jul 2024 16:22:41 +0200 Subject: [PATCH 29/34] Changes according to review --- include/sparrow/nullable.hpp | 121 +++---- test/test_nullable.cpp | 606 +++++++++++++++++++++++------------ 2 files changed, 461 insertions(+), 266 deletions(-) diff --git a/include/sparrow/nullable.hpp b/include/sparrow/nullable.hpp index 032298070..364c5ac12 100644 --- a/include/sparrow/nullable.hpp +++ b/include/sparrow/nullable.hpp @@ -83,7 +83,7 @@ namespace sparrow private: - static constexpr const char* message = "bad nullable access"; + static constexpr const char* message = "Invalid access to null value"; }; /** @@ -91,6 +91,11 @@ namespace sparrow */ struct nullval_t { + // This is required to disable the generation of + // the default constructor. Otherwise, a = {} where + // a is a nullable would lead to an ambiguous call + // where both operator=(nullable&&) and operator=(nullval_t) + // are valid. constexpr explicit nullval_t(int) {} }; @@ -128,9 +133,12 @@ namespace sparrow concept initializable_from_refs = initializable_from_one; + // We prefer std::is_assignable_v to std::assignable_from because + // std::assignable_from requires the existence of an implicit conversion + // from From to To template concept assignable_from_one = - (std::assignable_from, Args> && ...); + (std::is_assignable_v, Args> && ...); template concept assignable_from_refs = @@ -151,20 +159,25 @@ namespace sparrow template concept both_assignable_from_cref = - std::assignable_from, mpl::add_const_lvalue_reference_t> and - std::assignable_from, mpl::add_const_lvalue_reference_t&>; + std::is_assignable_v, mpl::add_const_lvalue_reference_t> and + std::is_assignable_v, mpl::add_const_lvalue_reference_t>; template concept both_assignable_from_cond_ref = - std::assignable_from, conditional_ref_t> and - std::assignable_from, conditional_ref_t>; + std::is_assignable_v, conditional_ref_t> and + std::is_assignable_v, conditional_ref_t>; template static constexpr bool is_nullable_v = mpl::is_type_instance_of_v; } /** - * The nullable class. + * The nullable class models a value or a reference that can be "null", or missing. + * The flag indicating whether the element should be considered missing can be a + * boolean-like value or reference. + * + * The value is always available, independently from the value of the flag. The flag + * only indicates whether the value should be considered for computation. */ template class nullable @@ -185,15 +198,17 @@ namespace sparrow using flag_rvalue_reference = typename flag_traits::rvalue_reference; using flag_const_rvalue_reference = typename flag_traits::const_rvalue_reference; + template constexpr nullable() noexcept : m_value() - , m_flag(false) + , m_null_flag(false) { } + template constexpr nullable(nullval_t) noexcept : m_value() - , m_flag(false) + , m_null_flag(false) { } @@ -205,7 +220,7 @@ namespace sparrow explicit (not std::convertible_to) constexpr nullable(U&& value) : m_value(std::forward(value)) - , m_flag(true) + , m_null_flag(true) { } @@ -219,7 +234,7 @@ namespace sparrow explicit(not impl::both_convertible_from_cref) constexpr nullable(const nullable& rhs) : m_value(rhs.get()) - , m_flag(rhs.has_value()) + , m_null_flag(rhs.null_flag()) { } @@ -233,37 +248,37 @@ namespace sparrow explicit(not impl::both_convertible_from_cond_ref) constexpr nullable(nullable&& rhs) : m_value(std::move(rhs).get()) - , m_flag(std::move(rhs).has_value()) + , m_null_flag(std::move(rhs).null_flag()) { } - constexpr nullable(value_type&& value, flag_type&& flag) + constexpr nullable(value_type&& value, flag_type&& null_flag) : m_value(std::move(value)) - , m_flag(std::move(flag)) + , m_null_flag(std::move(null_flag)) { } - constexpr nullable(std::add_lvalue_reference_t value, std::add_lvalue_reference_t flag) + constexpr nullable(std::add_lvalue_reference_t value, std::add_lvalue_reference_t null_flag) : m_value(value) - , m_flag(flag) + , m_null_flag(null_flag) { } - constexpr nullable(value_type&& value, std::add_lvalue_reference_t flag) + constexpr nullable(value_type&& value, std::add_lvalue_reference_t null_flag) : m_value(std::move(value)) - , m_flag(flag) + , m_null_flag(null_flag) { } - constexpr nullable(std::add_lvalue_reference_t value, flag_type&& flag) + constexpr nullable(std::add_lvalue_reference_t value, flag_type&& null_flag) : m_value(value) - , m_flag(std::move(flag)) + , m_null_flag(std::move(null_flag)) { } constexpr self_type& operator=(nullval_t) { - m_flag = false; + m_null_flag = false; return *this; } @@ -275,14 +290,14 @@ namespace sparrow constexpr self_type& operator=(TO&& rhs) { m_value = std::forward(rhs); - m_flag = true; + m_null_flag = true; return *this; } constexpr self_type& operator=(const self_type& rhs) { m_value = rhs.get(); - m_flag = rhs.has_value(); + m_null_flag = rhs.null_flag(); return *this; } @@ -295,14 +310,14 @@ namespace sparrow constexpr self_type& operator=(const nullable& rhs) { m_value = rhs.get(); - m_flag = rhs.has_value(); + m_null_flag = rhs.null_flag(); return *this; } constexpr self_type& operator=(self_type&& rhs) { m_value = std::move(rhs).get(); - m_flag = std::move(rhs).has_value(); + m_null_flag = std::move(rhs).null_flag(); return *this; } @@ -315,16 +330,17 @@ namespace sparrow constexpr self_type& operator=(nullable&& rhs) { m_value = std::move(rhs).get(); - m_flag = std::move(rhs).has_value(); + m_null_flag = std::move(rhs).null_flag(); return *this; } constexpr explicit operator bool() const noexcept; + constexpr bool has_value() const noexcept; - constexpr flag_reference has_value() & noexcept; - constexpr flag_const_reference has_value() const & noexcept; - constexpr flag_rvalue_reference has_value() && noexcept; - constexpr flag_const_rvalue_reference has_value() const && noexcept; + constexpr flag_reference null_flag() & noexcept; + constexpr flag_const_reference null_flag() const & noexcept; + constexpr flag_rvalue_reference null_flag() && noexcept; + constexpr flag_const_rvalue_reference null_flag() const && noexcept; constexpr reference get() & noexcept; constexpr const_reference get() const & noexcept; @@ -350,7 +366,7 @@ namespace sparrow void throw_if_null() const; T m_value; - B m_flag; + B m_null_flag; template friend class nullable; @@ -380,9 +396,6 @@ namespace sparrow constexpr std::compare_three_way_result_t operator<=>(const nullable& lhs, const nullable& rhs) noexcept; - template - constexpr nullable make_nullable(T&& value, B&& flag = true); - /*************************** * nullable implementation * ***************************/ @@ -390,44 +403,50 @@ namespace sparrow template constexpr nullable::operator bool() const noexcept { - return m_flag; + return m_null_flag; } template - constexpr auto nullable::has_value() & noexcept -> flag_reference + constexpr bool nullable::has_value() const noexcept { - return m_flag; + return m_null_flag; + } + + template + constexpr auto nullable::null_flag() & noexcept -> flag_reference + { + return m_null_flag; } template - constexpr auto nullable::has_value() const & noexcept -> flag_const_reference + constexpr auto nullable::null_flag() const & noexcept -> flag_const_reference { - return m_flag; + return m_null_flag; } template - constexpr auto nullable::has_value() && noexcept -> flag_rvalue_reference + constexpr auto nullable::null_flag() && noexcept -> flag_rvalue_reference { if constexpr (std::is_reference_v) { - return m_flag; + return m_null_flag; } else { - return flag_rvalue_reference(m_flag); + return flag_rvalue_reference(m_null_flag); } } template - constexpr auto nullable::has_value() const && noexcept -> flag_const_rvalue_reference + constexpr auto nullable::null_flag() const && noexcept -> flag_const_rvalue_reference { if constexpr (std::is_reference_v) { - return m_flag; + return m_null_flag; } else { - return flag_const_rvalue_reference(m_flag); + return flag_const_rvalue_reference(m_null_flag); } } @@ -516,19 +535,19 @@ namespace sparrow { using std::swap; swap(m_value, other.m_value); - swap(m_flag, other.m_flag); + swap(m_null_flag, other.m_null_flag); } template void nullable::reset() noexcept { - m_flag = false; + m_null_flag = false; } template void nullable::throw_if_null() const { - if (!m_flag) + if (!m_null_flag) { throw bad_nullable_access{}; } @@ -579,11 +598,5 @@ namespace sparrow return (lhs && rhs) ? lhs.get() <=> rhs.get() : bool(lhs) <=> bool(rhs); } - template - constexpr nullable make_nullable(T&& value, B&& flag) - { - return nullable(std::forward(value), std::forward(flag)); - } - } diff --git a/test/test_nullable.cpp b/test/test_nullable.cpp index 504ded864..92c27fbbe 100644 --- a/test/test_nullable.cpp +++ b/test/test_nullable.cpp @@ -12,55 +12,198 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include "sparrow/nullable.hpp" #include "doctest/doctest.h" namespace sparrow { - using nullable_double = nullable; - using nullable_int = nullable; + class Custom + { + public: + + static int counter; + + explicit Custom(const int& i = 0) + : m_value(i) + { + } + + ~Custom() + { + if (m_moved) + { + --counter; + } + } + + Custom(const Custom& rhs) + : m_value(rhs.m_value) + { + } + + Custom& operator=(const Custom& rhs) + { + m_value = rhs.m_value; + return *this; + } + + Custom(Custom&& rhs) + : m_value(rhs.m_value) + { + m_moved = true; + ++counter; + } + + Custom& operator=(Custom&& rhs) + { + m_value = rhs.m_value; + if (!m_moved) + { + m_moved = true; + ++counter; + } + return *this; + } + + Custom& operator=(const int& i) + { + m_value = i; + return *this; + } + + Custom& operator=(int&& i) + { + m_value = i; + if (!m_moved) + { + m_moved = true; + ++counter; + } + return *this; + } + + const int& get_value() const { return m_value; } + + private: + + int m_value; + bool m_moved = false; + }; + int Custom::counter = 0; + + bool operator==(const Custom& lhs, const Custom& rhs) + { + return lhs.get_value() == rhs.get_value(); + } + + bool operator==(const Custom& lhs, const int& rhs) + { + return lhs.get_value() == rhs; + } + + std::strong_ordering operator<=>(const Custom& lhs, const Custom& rhs) + { + return lhs.get_value() <=> rhs.get_value(); + } + + std::strong_ordering operator<=>(const Custom& lhs, const int& rhs) + { + return lhs.get_value() <=> rhs; + } + + using testing_types = std::tuple< + double, + std::string, + Custom>; + + namespace + { + template + struct fixture; + + template <> + struct fixture + { + static double init() { return 1.2; } + static double other() { return 2.5; } + static int convert_init() { return 3; } + + using convert_type = int; + + static bool check_move_count(int) { return true; } + }; + + template <> + struct fixture + { + static std::string init() { return "And now young codebase ..."; } + static std::string other() { return "Darth Codius"; } + static const char* convert_init() { return "Noooooo that's impossible!"; } + + using convert_type = const char*; + + static bool check_move_count(int) { return true; } + }; + + template <> + struct fixture + { + static Custom init() { return Custom(1); } + static Custom other() { return Custom(2); } + static int convert_init() { return 3; } + + using convert_type = int; + + static bool check_move_count(int ref) { return Custom::counter == ref; } + }; + } TEST_SUITE("nullable value") { - TEST_CASE("constructors") + TEST_CASE_TEMPLATE_DEFINE("constructors", T, constructors_id) { SUBCASE("default") { - nullable_double d; + nullable d; CHECK_FALSE(d.has_value()); } SUBCASE("from nullval") { - nullable_double d{nullval}; + nullable d{nullval}; CHECK_FALSE(d.has_value()); } SUBCASE("from value") { - nullable_double d{1.2}; + T dval = fixture::init(); + nullable d{dval}; REQUIRE(d.has_value()); - CHECK_EQ(d.value(), 1.2); + CHECK_EQ(d.value(), dval); } SUBCASE("from value with conversion") { - int i = 3; - nullable_double d{i}; + auto val = fixture::convert_init(); + nullable d{val}; REQUIRE(d.has_value()); - CHECK_EQ(d.value(), 3.); + CHECK_EQ(d.value(), T(val)); } SUBCASE("from value and flag") { - double val = 1.2; + T val = fixture::init(); bool b1 = true; - nullable_double td1(val, b1); - nullable_double td2(std::move(val), b1); - nullable_double td3(val, std::move(b1)); - nullable_double td4(std::move(val), std::move(b1)); + nullable td1(val, b1); + T val2 = val; + nullable td2(std::move(val2), b1); + nullable td3(val, std::move(b1)); + val2 = val; + nullable td4(std::move(val2), std::move(b1)); REQUIRE(td1.has_value()); CHECK_EQ(td1.value(), val); @@ -72,10 +215,12 @@ namespace sparrow CHECK_EQ(td4.value(), val); bool b2 = false; - nullable_double fd1(val, b2); - nullable_double fd2(std::move(val), b2); - nullable_double fd3(val, std::move(b2)); - nullable_double fd4(std::move(val), std::move(b2)); + nullable fd1(val, b2); + val2 = val; + nullable fd2(std::move(val2), b2); + nullable fd3(val, std::move(b2)); + val2 = val; + nullable fd4(std::move(val2), std::move(b2)); CHECK_FALSE(fd1.has_value()); CHECK_FALSE(fd2.has_value()); @@ -83,139 +228,173 @@ namespace sparrow CHECK_FALSE(fd4.has_value()); } } + TEST_CASE_TEMPLATE_APPLY(constructors_id, testing_types); - TEST_CASE("copy constructors") + TEST_CASE_TEMPLATE_DEFINE("copy constructors", T, copy_constructors_id) { SUBCASE("default") { - nullable_double d1{1.2}; - nullable_double d2(d1); + auto val = fixture::init(); + nullable d1{val}; + nullable d2(d1); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); + CHECK(fixture::check_move_count(0)); } SUBCASE("with conversion") { - nullable_int i{2}; - nullable_double d(i); + nullable::convert_type> i{fixture::convert_init()}; + nullable d(i); REQUIRE(d.has_value()); CHECK_EQ(i.value(), d.value()); + CHECK(fixture::check_move_count(0)); } SUBCASE("from empty nullable") { - nullable_double d1(nullval); - nullable_double d2(d1); + nullable d1(nullval); + nullable d2(d1); CHECK_FALSE(d2.has_value()); + CHECK(fixture::check_move_count(0)); } } + TEST_CASE_TEMPLATE_APPLY(copy_constructors_id, testing_types); - TEST_CASE("move constructors") + TEST_CASE_TEMPLATE_DEFINE("move constructors", T, move_constructors_id) { SUBCASE("default") { - nullable_double d0{1.2}; - nullable_double d1(d0); - nullable_double d2(std::move(d0)); + auto val = fixture::init(); + nullable d0{val}; + nullable d1(d0); + nullable d2(std::move(d0)); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); + CHECK(fixture::check_move_count(1)); } SUBCASE("with conversion") { - nullable_int i{2}; - nullable_int ci(i); - nullable_double d(std::move(i)); + using nullable_conv = nullable::convert_type>; + auto val = fixture::convert_init(); + nullable_conv i{val}; + nullable_conv ci(i); + nullable d(std::move(i)); REQUIRE(d.has_value()); CHECK_EQ(ci.value(), d.value()); + // Custom(int) is called + CHECK(fixture::check_move_count(0)); } SUBCASE("from empty nullable") { - nullable_double d1(nullval); - nullable_double d2(std::move(d1)); + nullable d1(nullval); + nullable d2(std::move(d1)); CHECK_FALSE(d2.has_value()); + CHECK(fixture::check_move_count(1)); } } + TEST_CASE_TEMPLATE_APPLY(move_constructors_id, testing_types); - TEST_CASE("copy assign") + TEST_CASE_TEMPLATE_DEFINE("copy assign", T, copy_assign_id) { SUBCASE("default") { - nullable_double d1{1.2}; - nullable_double d2{2.5}; + auto val1 = fixture::init(); + auto val2 = fixture::other(); + nullable d1{val1}; + nullable d2{val2}; d2 = d1; REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); + CHECK(fixture::check_move_count(0)); } SUBCASE("with conversion") { - nullable_int d1{1}; - nullable_double d2{2.5}; + auto val1 = fixture::convert_init(); + auto val2 = fixture::init(); + nullable::convert_type> d1{val1}; + nullable d2{val2}; d2 = d1; REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); + CHECK(fixture::check_move_count(0)); } SUBCASE("from empty nullable") { - nullable_double d1{nullval}; - nullable_double d2{2.5}; + nullable d1{nullval}; + auto val = fixture::init(); + nullable d2{val}; d2 = d1; CHECK_FALSE(d2.has_value()); + CHECK(fixture::check_move_count(0)); } } + TEST_CASE_TEMPLATE_APPLY(copy_assign_id, testing_types); - TEST_CASE("move assign") + TEST_CASE_TEMPLATE_DEFINE("move assign", T, move_assign_id) { SUBCASE("default") { - nullable_double d0{1.2}; - nullable_double d1(d0); - nullable_double d2{2.5}; + auto val0 = fixture::init(); + nullable d0{val0}; + nullable d1(d0); + auto val1 = fixture::other(); + nullable d2{val1}; d2 = std::move(d0); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); + CHECK(fixture::check_move_count(1)); } SUBCASE("with conversion") { - nullable_int d0{1}; - nullable_int d1(d0); - nullable_double d2{2.5}; + using nullable_conv = nullable::convert_type>; + auto val0 = fixture::convert_init(); + nullable_conv d0{val0}; + nullable_conv d1(d0); + auto val1 = fixture::init(); + nullable d2{val1}; d2 = std::move(d0); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); + CHECK(fixture::check_move_count(1)); } SUBCASE("from empty nullable") { - nullable_double d1{nullval}; - nullable_double d2{2.3}; + nullable d1{nullval}; + auto val = fixture::init(); + nullable d2{val}; d2 = std::move(d1); CHECK_FALSE(d2.has_value()); + CHECK(fixture::check_move_count(1)); } } + TEST_CASE_TEMPLATE_APPLY(move_assign_id, testing_types); - TEST_CASE("conversion to bool") + TEST_CASE_TEMPLATE_DEFINE("conversion to bool", T, conversion_id) { - nullable_double d1{1.2}; + nullable d1{fixture::init()}; CHECK(d1); - nullable_double d2{nullval}; + nullable d2{nullval}; CHECK_FALSE(d2); } + TEST_CASE_TEMPLATE_APPLY(conversion_id, testing_types); - TEST_CASE("value / get") + TEST_CASE_TEMPLATE_DEFINE("value / get", T, value_id) { - constexpr double initial = 1.2; - constexpr double expected = 2.5; + const T initial = fixture::init(); + const T expected = fixture::other(); SUBCASE("& overload") { - nullable_double d{initial}; - nullable_double& d1{d}; + nullable d{initial}; + nullable& d1{d}; d1.value() = expected; CHECK_EQ(d.value(), expected); CHECK_EQ(d.get(), expected); @@ -223,16 +402,16 @@ namespace sparrow SUBCASE("const & overload") { - nullable_double d{initial}; - const nullable_double& d2{d}; + nullable d{initial}; + const nullable& d2{d}; CHECK_EQ(d2.value(), initial); CHECK_EQ(d2.get(), initial); } SUBCASE("&& overload") { - nullable_double d{initial}; - nullable_double&& d3(std::move(d)); + nullable d{initial}; + nullable&& d3(std::move(d)); d3.value() = expected; CHECK_EQ(d.value(), expected); CHECK_EQ(d.get(), expected); @@ -240,35 +419,36 @@ namespace sparrow SUBCASE("const && overload") { - nullable_double d{initial}; - const nullable_double&& d4(std::move(d)); + nullable d{initial}; + const nullable&& d4(std::move(d)); CHECK_EQ(d4.value(), initial); CHECK_EQ(d4.get(), initial); } SUBCASE("empty") { - nullable_double empty = nullval; + nullable empty = nullval; CHECK_THROWS_AS(empty.value(), bad_nullable_access); CHECK_NOTHROW(empty.get()); } } + TEST_CASE_TEMPLATE_APPLY(value_id, testing_types); - TEST_CASE("value_or") + TEST_CASE_TEMPLATE_DEFINE("value_or", T, value_or_id) { - constexpr double initial = 1.2; - constexpr double expected = 2.5; + const T initial = fixture::init(); + const T expected = fixture::other(); - nullable_double d{initial}; - nullable_double empty{nullval}; + nullable d{initial}; + nullable empty{nullval}; SUBCASE("const & overload") { - const nullable_double& ref(d); - const nullable_double& ref_empty(empty); + const nullable& ref(d); + const nullable& ref_empty(empty); - double res = ref.value_or(expected); - double res_empty = ref_empty.value_or(expected); + T res = ref.value_or(expected); + T res_empty = ref_empty.value_or(expected); CHECK_EQ(res, initial); CHECK_EQ(res_empty, expected); @@ -276,25 +456,25 @@ namespace sparrow SUBCASE("&& overload") { - nullable_double&& ref(std::move(d)); - nullable_double&& ref_empty(std::move(empty)); + nullable&& ref(std::move(d)); + nullable&& ref_empty(std::move(empty)); - double res = ref.value_or(expected); - double res_empty = ref_empty.value_or(expected); + T res = ref.value_or(expected); + T res_empty = ref_empty.value_or(expected); CHECK_EQ(res, initial); CHECK_EQ(res_empty, expected); } - } + TEST_CASE_TEMPLATE_APPLY(value_or_id, testing_types); - TEST_CASE("swap") + TEST_CASE_TEMPLATE_DEFINE("swap", T, swap_id) { - constexpr double initial = 1.2; - constexpr double expected = 2.5; - nullable_double d1{initial}; - nullable_double d2{expected}; - nullable_double empty{nullval}; + const T initial = fixture::init(); + const T expected = fixture::other(); + nullable d1{initial}; + nullable d2{expected}; + nullable empty{nullval}; swap(d1, d2); CHECK_EQ(d1.value(), expected); @@ -304,23 +484,25 @@ namespace sparrow CHECK_EQ(empty.value(), expected); CHECK_FALSE(d1.has_value()); } + TEST_CASE_TEMPLATE_APPLY(swap_id, testing_types); - TEST_CASE("reset") + TEST_CASE_TEMPLATE_DEFINE("reset", T, reset_id) { - constexpr double initial = 1.2; - nullable_double d{initial}; + const T initial = fixture::init(); + nullable d{initial}; d.reset(); CHECK_FALSE(d.has_value()); } + TEST_CASE_TEMPLATE_APPLY(reset_id, testing_types); - TEST_CASE("equality comparison") + TEST_CASE_TEMPLATE_DEFINE("equality comparison", T, equality_comparison_id) { - constexpr double initial = 1.2; - constexpr double other = 2.5; + const T initial = fixture::init(); + const T other = fixture::other(); - nullable_double d1{initial}; - nullable_double d2{other}; - nullable_double empty; + nullable d1{initial}; + nullable d2{other}; + nullable empty; CHECK(d1 == d1); CHECK(d1 == d1.value()); @@ -329,15 +511,16 @@ namespace sparrow CHECK(d1 != empty); CHECK(empty == empty); } + TEST_CASE_TEMPLATE_APPLY(equality_comparison_id, testing_types); - TEST_CASE("inequality comparison") + TEST_CASE_TEMPLATE_DEFINE("inequality comparison", T, inequality_comparison_id) { - constexpr double initial = 1.2; - constexpr double other = 2.5; + const T initial = fixture::init(); + const T other = fixture::other(); - nullable_double d1{initial}; - nullable_double d2{other}; - nullable_double empty; + nullable d1{initial}; + nullable d2{other}; + nullable empty; // opearator <= CHECK(d1 <= d1); @@ -375,32 +558,22 @@ namespace sparrow CHECK(d1 > empty); CHECK_FALSE(empty > d1); } - - TEST_CASE("make_nullable") - { - double value = 2.5; - auto opt = make_nullable(std::move(value), true); - static_assert(std::same_as, nullable_double>); - REQUIRE(opt.has_value()); - CHECK_EQ(opt.value(), value); - } + TEST_CASE_TEMPLATE_APPLY(inequality_comparison_id, testing_types); } - using nullable_proxy = nullable; - TEST_SUITE("nullable proxy") { - TEST_CASE("constructors") + TEST_CASE_TEMPLATE_DEFINE("constructors", T, constructors_id) { - double val = 1.2; + T val = fixture::init(); bool b1 = true; - nullable_proxy td(val); + nullable td(val); REQUIRE(td.has_value()); CHECK_EQ(td.value(), val); - nullable_proxy td1(val, b1); - nullable_proxy td2(val, std::move(b1)); + nullable td1(val, b1); + nullable td2(val, std::move(b1)); REQUIRE(td1.has_value()); CHECK_EQ(td1.value(), val); @@ -408,113 +581,125 @@ namespace sparrow CHECK_EQ(td2.value(), val); bool b2 = false; - nullable_proxy fd1(val, b2); - nullable_proxy fd2(val, std::move(b2)); + nullable fd1(val, b2); + nullable fd2(val, std::move(b2)); CHECK_FALSE(fd1.has_value()); CHECK_FALSE(fd2.has_value()); } + TEST_CASE_TEMPLATE_APPLY(constructors_id, testing_types); - TEST_CASE("copy constructors") + TEST_CASE_TEMPLATE_DEFINE("copy constructors", T, copy_constructors_id) { - double val = 1.2; - nullable_proxy d1(val); - nullable_proxy d2(d1); + T val = fixture::init(); + nullable d1(val); + nullable d2(d1); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); + CHECK(fixture::check_move_count(0)); } + TEST_CASE_TEMPLATE_APPLY(copy_constructors_id, testing_types); - TEST_CASE("move constructor") + TEST_CASE_TEMPLATE_DEFINE("move constructors", T, move_constructors_id) { - double val = 1.2; - nullable_proxy d1(val); - nullable_proxy d2(std::move(d1)); + T val = fixture::init(); + nullable d1(val); + nullable d2(std::move(d1)); REQUIRE(d2.has_value()); CHECK_EQ(d2.value(), val); + CHECK(fixture::check_move_count(0)); } + TEST_CASE_TEMPLATE_APPLY(move_constructors_id, testing_types); - TEST_CASE("copy assign") + TEST_CASE_TEMPLATE_DEFINE("copy assign", T, copy_assign_id) { SUBCASE("default") { - double initial = 1.2; - double expected = 2.5; - nullable_proxy d1{initial}; - nullable_proxy d2{expected}; + T initial = fixture::init(); + T expected = fixture::other(); + nullable d1{initial}; + nullable d2{expected}; d2 = d1; REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); CHECK_EQ(initial, expected); + CHECK(fixture::check_move_count(0)); } SUBCASE("with conversion") { - double initial = 1.2; - double expected = 2.5; - nullable_double d1{initial}; - nullable_proxy d2{expected}; + T initial = fixture::init(); + T expected = fixture::other(); + nullable d1{initial}; + nullable d2{expected}; d2 = d1; REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); CHECK_EQ(initial, expected); + CHECK(fixture::check_move_count(0)); } SUBCASE("from empty nullable") { - double initial = 1.2; - nullable_proxy d2(initial); + T initial = fixture::init(); + nullable d2(initial); d2 = nullval; CHECK_FALSE(d2.has_value()); + CHECK(fixture::check_move_count(0)); } } + TEST_CASE_TEMPLATE_APPLY(copy_assign_id, testing_types); - TEST_CASE("move assign") + TEST_CASE_TEMPLATE_DEFINE("move assign", T, move_assign_id) { SUBCASE("default") { - double initial = 1.2; - double expected = 2.5; - nullable_proxy d1{initial}; - nullable_proxy d2{expected}; + T initial = fixture::init(); + T expected = fixture::other(); + nullable d1{initial}; + nullable d2{expected}; d2 = std::move(d1); REQUIRE(d2.has_value()); CHECK_EQ(d1.value(), d2.value()); CHECK_EQ(initial, expected); - + CHECK(fixture::check_move_count(0)); } SUBCASE("with conversion") { - double initial = 1.2; - double expected = 2.5; - nullable_double d1{initial}; - nullable_proxy d2{expected}; + T initial = fixture::init(); + T expected = fixture::other(); + nullable d1{initial}; + nullable d2{expected}; d2 = std::move(d1); REQUIRE(d2.has_value()); - CHECK_EQ(d1.value(), d2.value()); CHECK_EQ(initial, expected); + // d1 is not a proxy, therefore it is moved + CHECK(fixture::check_move_count(1)); } } + TEST_CASE_TEMPLATE_APPLY(move_assign_id, testing_types); - TEST_CASE("conversion to bool") + TEST_CASE_TEMPLATE_DEFINE("conversion to bool", T, conversion_id) { - double val = 1.2; - nullable_proxy d1(val); + T val = fixture::init(); + nullable d1(val); CHECK(d1); d1 = nullval; CHECK_FALSE(d1); } + TEST_CASE_TEMPLATE_APPLY(conversion_id, testing_types); - TEST_CASE("value / get") + TEST_CASE_TEMPLATE_DEFINE("value / get", T, value_id) { - double initial = 1.2; - double expected = 2.5; + T initial = fixture::init(); + T expected = fixture::other(); SUBCASE("& overload") { - nullable_proxy d{initial}; - nullable_proxy& d1(d); + nullable d{initial}; + nullable& d1(d); d1.value() = expected; CHECK_EQ(d.value(), expected); CHECK_EQ(d.get(), expected); @@ -522,16 +707,16 @@ namespace sparrow SUBCASE("const & overload") { - nullable_proxy d{initial}; - const nullable_proxy& d2(d); + nullable d{initial}; + const nullable& d2(d); CHECK_EQ(d2.value(), initial); CHECK_EQ(d2.get(), initial); } SUBCASE("&& overload") { - nullable_proxy d{initial}; - nullable_proxy&& d3(std::move(d)); + nullable d{initial}; + nullable&& d3(std::move(d)); d3.value() = expected; CHECK_EQ(d.value(), expected); CHECK_EQ(d.get(), expected); @@ -539,37 +724,38 @@ namespace sparrow SUBCASE("const && overload") { - nullable_proxy d{initial}; - const nullable_proxy&& d4(std::move(d)); + nullable d{initial}; + const nullable&& d4(std::move(d)); CHECK_EQ(d4.value(), initial); CHECK_EQ(d4.get(), initial); } SUBCASE("empty") { - nullable_proxy empty(initial); + nullable empty(initial); empty = nullval; CHECK_THROWS_AS(empty.value(), bad_nullable_access); CHECK_NOTHROW(empty.get()); } } + TEST_CASE_TEMPLATE_APPLY(value_id, testing_types); - TEST_CASE("value_or") + TEST_CASE_TEMPLATE_DEFINE("value_or", T, value_or_id) { - double initial = 1.2; - double expected = 2.5; + T initial = fixture::init(); + T expected = fixture::other(); - nullable_proxy d{initial}; - nullable_proxy empty(initial); + nullable d{initial}; + nullable empty(initial); empty = nullval; SUBCASE("const & overload") { - const nullable_proxy& ref(d); - const nullable_proxy& ref_empty(empty); + const nullable& ref(d); + const nullable& ref_empty(empty); - double res = ref.value_or(expected); - double res_empty = ref_empty.value_or(expected); + T res = ref.value_or(expected); + T res_empty = ref_empty.value_or(expected); CHECK_EQ(res, initial); CHECK_EQ(res_empty, expected); @@ -577,27 +763,28 @@ namespace sparrow SUBCASE("&& overload") { - nullable_proxy&& ref(std::move(d)); - nullable_proxy&& ref_empty(std::move(empty)); + nullable&& ref(std::move(d)); + nullable&& ref_empty(std::move(empty)); - double res = ref.value_or(expected); - double res_empty = ref_empty.value_or(expected); + T res = ref.value_or(expected); + T res_empty = ref_empty.value_or(expected); CHECK_EQ(res, initial); CHECK_EQ(res_empty, expected); } } + TEST_CASE_TEMPLATE_APPLY(value_or_id, testing_types); - TEST_CASE("swap") + TEST_CASE_TEMPLATE_DEFINE("swap", T, swap_id) { - double initial = 1.2; - double expected = 2.5; - double initial_bu = initial; - double expected_bu = expected; - double empty_val = 3.7; - nullable_proxy d1{initial}; - nullable_proxy d2{expected}; - nullable_proxy empty{empty_val}; + T initial = fixture::init(); + T expected = fixture::other(); + T initial_bu = initial; + T expected_bu = expected; + T empty_val = T(fixture::convert_init()); + nullable d1{initial}; + nullable d2{expected}; + nullable empty{empty_val}; empty = nullval; swap(d1, d2); @@ -608,24 +795,26 @@ namespace sparrow CHECK_EQ(empty.value(), expected_bu); CHECK_FALSE(d1.has_value()); } + TEST_CASE_TEMPLATE_APPLY(swap_id, testing_types); - TEST_CASE("reset") + TEST_CASE_TEMPLATE_DEFINE("reset", T, reset_id) { - double initial = 1.2; - nullable_proxy d{initial}; + T initial = fixture::init(); + nullable d{initial}; d.reset(); CHECK_FALSE(d.has_value()); } + TEST_CASE_TEMPLATE_APPLY(reset_id, testing_types); - TEST_CASE("equality comparison") + TEST_CASE_TEMPLATE_DEFINE("equality comparison", T, equality_comparison_id) { - double initial = 1.2; - double other = 2.5; - double empty_val = 3.7; + T initial = fixture::init(); + T other = fixture::other(); + T empty_val = T(fixture::convert_init()); - nullable_proxy d1{initial}; - nullable_proxy d2{other}; - nullable_proxy empty{empty_val}; + nullable d1{initial}; + nullable d2{other}; + nullable empty{empty_val}; empty = nullval; CHECK(d1 == d1); @@ -635,16 +824,17 @@ namespace sparrow CHECK(d1 != empty); CHECK(empty == empty); } + TEST_CASE_TEMPLATE_APPLY(equality_comparison_id, testing_types); - TEST_CASE("inequality comparison") + TEST_CASE_TEMPLATE_DEFINE("inequality comparison", T, inequality_comparison_id) { - double initial = 1.2; - double other = 2.5; - double empty_val = 3.7; + T initial = fixture::init(); + T other = fixture::other(); + T empty_val = T(fixture::convert_init()); - nullable_proxy d1{initial}; - nullable_proxy d2{other}; - nullable_proxy empty{empty_val}; + nullable d1{initial}; + nullable d2{other}; + nullable empty{empty_val}; empty = nullval; // opearator <= @@ -683,15 +873,7 @@ namespace sparrow CHECK(d1 > empty); CHECK_FALSE(empty > d1); } - - TEST_CASE("make_nullable") - { - double value = 2.7; - auto opt = make_nullable(value, true); - static_assert(std::same_as, nullable_proxy>); - REQUIRE(opt.has_value()); - CHECK_EQ(opt.value(), value); - } + TEST_CASE_TEMPLATE_APPLY(inequality_comparison_id, testing_types); } } From 4f2e1ccd6b94a314f1b7021bf2274bb2261eb1f2 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 12 Jul 2024 08:46:08 +0200 Subject: [PATCH 30/34] Added more documentation --- include/sparrow/mp_utils.hpp | 4 +++ include/sparrow/nullable.hpp | 51 ++++++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/include/sparrow/mp_utils.hpp b/include/sparrow/mp_utils.hpp index 804ae2fb3..a2b10082f 100644 --- a/include/sparrow/mp_utils.hpp +++ b/include/sparrow/mp_utils.hpp @@ -419,4 +419,8 @@ namespace sparrow::mpl #endif } + /// The boolean_like concept specifies that a type can be convertible to and assignable from + /// bool. + template + concept boolean_like = std::constructible_from and std::convertible_to; } diff --git a/include/sparrow/nullable.hpp b/include/sparrow/nullable.hpp index 364c5ac12..aa4453692 100644 --- a/include/sparrow/nullable.hpp +++ b/include/sparrow/nullable.hpp @@ -23,8 +23,7 @@ namespace sparrow { - template - concept boolean_like = std::constructible_from and std::convertible_to; + using mpl::boolean_like; template class nullable; @@ -178,6 +177,54 @@ namespace sparrow * * The value is always available, independently from the value of the flag. The flag * only indicates whether the value should be considered for computation. + * + * When it holds a value, the nullable class has a regular value semantics: copying + * or moving it will copy or move the underlygin value and flag. When it holds a + * reference, the nullable class has a view semantics: copying it or moving it will + * copy the underlying value and flag instead of reassigining the references. This + * allows to create nullable views over two distinct arrays (one for the values, one + * for the flags) used to implement a stl-like contianer of nullable. For instance, + * if you have the following class: + * + * @code{.cpp} + * template + * class nullable_array + * { + * private: + * std::vector m_values; + * std::vector m_flags; + * + * public: + * + * using reference = nullable; + * using cons_reference = nullable; + * + * reference operator[](size_type i) + * { + * return reference(m_values[i], m_flags[i]); + * } + * + * const_reference operator[](size_type i) const + * { + * return const_reference(m_values[i], m_flags[i]); + * } + * // ... + * }; + * @endcode + * + * Then you want the same semantic for accessing elements of nullable_array + * as that of std::vector, meaning that the following: + * + * @code{.cpp} + * nullable_array my_array = { ... }; + * my_array[1] = my_array[0]; + * @endcode + * + * should copy the underlying value and flag of the first element of my_array + * to the underlying value and flag of the second element of the array. + * + * @tparam T the type of the value + * @tparam B the type of the flag. This type must be convertible to and assignable from bool */ template class nullable From 0c64d3c7bd2e03a98eb6c5dbbd03fa66f1080d0a Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 12 Jul 2024 09:32:17 +0200 Subject: [PATCH 31/34] GNiiiiiiaaaaaarrrrrgh --- include/sparrow/mp_utils.hpp | 2 +- include/sparrow/nullable.hpp | 10 ++++++++++ test/test_mpl.cpp | 20 ++++++++++++++++++++ test/test_nullable.cpp | 12 ++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/include/sparrow/mp_utils.hpp b/include/sparrow/mp_utils.hpp index a2b10082f..f1bcf8112 100644 --- a/include/sparrow/mp_utils.hpp +++ b/include/sparrow/mp_utils.hpp @@ -422,5 +422,5 @@ namespace sparrow::mpl /// The boolean_like concept specifies that a type can be convertible to and assignable from /// bool. template - concept boolean_like = std::constructible_from and std::convertible_to; + concept boolean_like = std::is_assignable_v, bool> and std::convertible_to; } diff --git a/include/sparrow/nullable.hpp b/include/sparrow/nullable.hpp index aa4453692..db51ded10 100644 --- a/include/sparrow/nullable.hpp +++ b/include/sparrow/nullable.hpp @@ -443,6 +443,11 @@ namespace sparrow constexpr std::compare_three_way_result_t operator<=>(const nullable& lhs, const nullable& rhs) noexcept; + // Even if we have CTAD in C++20, some constructors add lvalue reference + // to their argument, making the deduction impossible. + template + constexpr nullable make_nullable(T&& value, B&& flag = true); + /*************************** * nullable implementation * ***************************/ @@ -645,5 +650,10 @@ namespace sparrow return (lhs && rhs) ? lhs.get() <=> rhs.get() : bool(lhs) <=> bool(rhs); } + template + constexpr nullable make_nullable(T&& value, B&& flag) + { + return nullable(std::forward(value), std::forward(flag)); + } } diff --git a/test/test_mpl.cpp b/test/test_mpl.cpp index e58797d27..56acce6b8 100644 --- a/test/test_mpl.cpp +++ b/test/test_mpl.cpp @@ -117,4 +117,24 @@ namespace sparrow // transfrom static_assert(std::same_as, mpl::transform>); + + ////////////////////////////// + // concepts and other stuff + + // add_const_lvalue_reference + static_assert(std::same_as, const int&>); + static_assert(std::same_as, const int&>); + static_assert(not std::same_as, const int&>); + + // boolean like + static_assert(mpl::boolean_like); + + class like_a_bool + { + public: + like_a_bool& operator=(const bool&) { return *this; } + operator bool() const { return true; } + }; + + static_assert(mpl::boolean_like); } diff --git a/test/test_nullable.cpp b/test/test_nullable.cpp index 92c27fbbe..ef6c92893 100644 --- a/test/test_nullable.cpp +++ b/test/test_nullable.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include "sparrow/nullable.hpp" @@ -559,6 +560,17 @@ namespace sparrow CHECK_FALSE(empty > d1); } TEST_CASE_TEMPLATE_APPLY(inequality_comparison_id, testing_types); + + TEST_CASE_TEMPLATE_DEFINE("make_nullable", T, make_nullable_id) + { + T value = fixture::init(); + T value_copy = value; + auto opt = make_nullable(std::move(value), true); + static_assert(std::same_as, nullable>); + REQUIRE(opt.has_value()); + CHECK_EQ(opt.value(), value_copy); + } + TEST_CASE_TEMPLATE_APPLY(make_nullable_id, testing_types); } TEST_SUITE("nullable proxy") From 3830cbcc0e72a084cb965f0c730fc86a2c634d80 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 12 Jul 2024 11:03:43 +0200 Subject: [PATCH 32/34] Conditional noexcept --- include/sparrow/nullable.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/sparrow/nullable.hpp b/include/sparrow/nullable.hpp index db51ded10..57ca9f918 100644 --- a/include/sparrow/nullable.hpp +++ b/include/sparrow/nullable.hpp @@ -265,7 +265,7 @@ namespace sparrow std::constructible_from ) explicit (not std::convertible_to) - constexpr nullable(U&& value) + constexpr nullable(U&& value) noexcept(noexcept(T(std::declval()))) : m_value(std::forward(value)) , m_null_flag(true) { From 760c3ff817ca49a1e3e51eb83de42eb97f4c18e9 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 12 Jul 2024 12:35:50 +0200 Subject: [PATCH 33/34] Please merge me! I caaaan't wait! --- include/sparrow/mp_utils.hpp | 7 ++++--- include/sparrow/nullable.hpp | 25 ++++++++++++++----------- test/test_mpl.cpp | 2 +- test/test_nullable.cpp | 9 ++++++++- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/include/sparrow/mp_utils.hpp b/include/sparrow/mp_utils.hpp index f1bcf8112..611140165 100644 --- a/include/sparrow/mp_utils.hpp +++ b/include/sparrow/mp_utils.hpp @@ -419,8 +419,9 @@ namespace sparrow::mpl #endif } - /// The boolean_like concept specifies that a type can be convertible to and assignable from - /// bool. + /// Matches types that can be convertible to and assignable from bool. We do not use + /// `std::convertible_to` because we don't want to impose an implicit conversion. template - concept boolean_like = std::is_assignable_v, bool> and std::convertible_to; + concept boolean_like = std::is_assignable_v, bool> and + requires { static_cast(std::declval()); }; } diff --git a/include/sparrow/nullable.hpp b/include/sparrow/nullable.hpp index 57ca9f918..c3993e4ae 100644 --- a/include/sparrow/nullable.hpp +++ b/include/sparrow/nullable.hpp @@ -82,7 +82,7 @@ namespace sparrow private: - static constexpr const char* message = "Invalid access to null value"; + static constexpr const char* message = "Invalid access to nullable underlying value"; }; /** @@ -171,20 +171,23 @@ namespace sparrow } /** - * The nullable class models a value or a reference that can be "null", or missing. + * The nullable class models a value or a reference that can be "null", or missing, + * like values traditionally used in data science libraries. * The flag indicating whether the element should be considered missing can be a * boolean-like value or reference. * - * The value is always available, independently from the value of the flag. The flag - * only indicates whether the value should be considered for computation. + * The value is always valid, independently from the value of the flag. The flag + * only indicates whether the value should be considered as specified (flag is true) + * or null (flag is false). Assigning nullval to a nullable or setting its flag to + * flase does not trigger the destruction of the underlying value. * - * When it holds a value, the nullable class has a regular value semantics: copying - * or moving it will copy or move the underlygin value and flag. When it holds a - * reference, the nullable class has a view semantics: copying it or moving it will - * copy the underlying value and flag instead of reassigining the references. This - * allows to create nullable views over two distinct arrays (one for the values, one - * for the flags) used to implement a stl-like contianer of nullable. For instance, - * if you have the following class: + * When the stored object is not a reference, the nullable class has a regular value + * semantics: copying or moving it will copy or move the underlying value and flag. + * When the stored object is a reference, the nullable class has a view semantics: + * copying it or moving it will copy the underlying value and flag instead of reassigining + * the references. This allows to create nullable views over two distinct arrays (one + * for the values, one for the flags) used to implement a stl-like contianer of nullable. + * For instance, if you have the following class: * * @code{.cpp} * template diff --git a/test/test_mpl.cpp b/test/test_mpl.cpp index 56acce6b8..3377c037e 100644 --- a/test/test_mpl.cpp +++ b/test/test_mpl.cpp @@ -133,7 +133,7 @@ namespace sparrow { public: like_a_bool& operator=(const bool&) { return *this; } - operator bool() const { return true; } + explicit operator bool() const { return true; } }; static_assert(mpl::boolean_like); diff --git a/test/test_nullable.cpp b/test/test_nullable.cpp index ef6c92893..c61fc3b06 100644 --- a/test/test_nullable.cpp +++ b/test/test_nullable.cpp @@ -14,6 +14,7 @@ #include #include +#include #include "sparrow/nullable.hpp" @@ -21,13 +22,19 @@ namespace sparrow { + // Custom type that increments a counter + // when an instane is move constructed or + // move assigned (and decrements it upon + // deletion). This allows to test that + // nullable views do not move their underlying + // data upon assignment. class Custom { public: static int counter; - explicit Custom(const int& i = 0) + explicit Custom(int i = 0) : m_value(i) { } From fcf0fe22a67ccfc47d6047a57d5d86dce92afb8c Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 12 Jul 2024 14:56:59 +0200 Subject: [PATCH 34/34] Do not import boolean_like concept in sparrow namespace --- include/sparrow/nullable.hpp | 60 +++++++++++++++++------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/include/sparrow/nullable.hpp b/include/sparrow/nullable.hpp index c3993e4ae..86cc571ba 100644 --- a/include/sparrow/nullable.hpp +++ b/include/sparrow/nullable.hpp @@ -23,9 +23,7 @@ namespace sparrow { - using mpl::boolean_like; - - template + template class nullable; /* * Default traits for the nullable class. These traits should be specialized @@ -229,7 +227,7 @@ namespace sparrow * @tparam T the type of the value * @tparam B the type of the flag. This type must be convertible to and assignable from bool */ - template + template class nullable { public: @@ -276,7 +274,7 @@ namespace sparrow constexpr nullable(const self_type&) = default; - template + template requires ( impl::both_constructible_from_cref and not impl::initializable_from_refs> @@ -290,7 +288,7 @@ namespace sparrow constexpr nullable(self_type&&) noexcept = default; - template + template requires ( impl::both_constructible_from_cond_ref and not impl::initializable_from_refs> @@ -351,7 +349,7 @@ namespace sparrow return *this; } - template + template requires( impl::both_assignable_from_cref and not impl::initializable_from_refs> and @@ -371,7 +369,7 @@ namespace sparrow return *this; } - template + template requires( impl::both_assignable_from_cond_ref and not impl::initializable_from_refs> and @@ -418,7 +416,7 @@ namespace sparrow T m_value; B m_null_flag; - template + template friend class nullable; }; @@ -428,7 +426,7 @@ namespace sparrow template constexpr bool operator==(const nullable& lhs, nullval_t) noexcept; - template + template constexpr std::strong_ordering operator<=>(const nullable& lhs, nullval_t) noexcept; template @@ -448,38 +446,38 @@ namespace sparrow // Even if we have CTAD in C++20, some constructors add lvalue reference // to their argument, making the deduction impossible. - template + template constexpr nullable make_nullable(T&& value, B&& flag = true); /*************************** * nullable implementation * ***************************/ - template + template constexpr nullable::operator bool() const noexcept { return m_null_flag; } - template + template constexpr bool nullable::has_value() const noexcept { return m_null_flag; } - template + template constexpr auto nullable::null_flag() & noexcept -> flag_reference { return m_null_flag; } - template + template constexpr auto nullable::null_flag() const & noexcept -> flag_const_reference { return m_null_flag; } - template + template constexpr auto nullable::null_flag() && noexcept -> flag_rvalue_reference { if constexpr (std::is_reference_v) @@ -492,7 +490,7 @@ namespace sparrow } } - template + template constexpr auto nullable::null_flag() const && noexcept -> flag_const_rvalue_reference { if constexpr (std::is_reference_v) @@ -505,19 +503,19 @@ namespace sparrow } } - template + template constexpr auto nullable::get() & noexcept -> reference { return m_value; } - template + template constexpr auto nullable::get() const & noexcept -> const_reference { return m_value; } - template + template constexpr auto nullable::get() && noexcept -> rvalue_reference { if constexpr (std::is_reference_v) @@ -530,7 +528,7 @@ namespace sparrow } } - template + template constexpr auto nullable::get() const && noexcept -> const_rvalue_reference { if constexpr (std::is_reference_v) @@ -543,49 +541,49 @@ namespace sparrow } } - template + template constexpr auto nullable::value() & -> reference { throw_if_null(); return get(); } - template + template constexpr auto nullable::value() const & -> const_reference { throw_if_null(); return get(); } - template + template constexpr auto nullable::value() && -> rvalue_reference { throw_if_null(); return std::move(*this).get(); } - template + template constexpr auto nullable::value() const && -> const_rvalue_reference { throw_if_null(); return std::move(*this).get(); } - template + template template constexpr auto nullable::value_or(U&& default_value) const & -> value_type { return *this ? get() : value_type(std::forward(default_value)); } - template + template template constexpr auto nullable::value_or(U&& default_value) && -> value_type { return *this ? get() : value_type(std::forward(default_value)); } - template + template void nullable::swap(self_type& other) noexcept { using std::swap; @@ -593,13 +591,13 @@ namespace sparrow swap(m_null_flag, other.m_null_flag); } - template + template void nullable::reset() noexcept { m_null_flag = false; } - template + template void nullable::throw_if_null() const { if (!m_null_flag) @@ -653,7 +651,7 @@ namespace sparrow return (lhs && rhs) ? lhs.get() <=> rhs.get() : bool(lhs) <=> bool(rhs); } - template + template constexpr nullable make_nullable(T&& value, B&& flag) { return nullable(std::forward(value), std::forward(flag));