From f6f145c5e5ecdd43789df510d4fe8b5c7c9ac3e1 Mon Sep 17 00:00:00 2001 From: Sergei Date: Fri, 8 May 2026 11:33:21 +0300 Subject: [PATCH] Apply the rule of zero for `etl::optional` type. (#1426) * Apply the rule of zero for `etl::optional` type. Correct move behavior of `TestDataM` - it should preserve `valid` value. Implemented overloads of `etl::make_optional` free function. Extend optional moveable tests - fundamental vs non-fundamental - move construct/assign from valueless - Verify nothrow of `etl::swap` for `etl::optional`. * coderabbitai review fixes. --------- Co-authored-by: John Wellbelove --- include/etl/optional.h | 192 ++++++++--------- test/data.h | 13 +- test/test_optional.cpp | 467 ++++++++++++++++++++++++++++++----------- 3 files changed, 438 insertions(+), 234 deletions(-) diff --git a/include/etl/optional.h b/include/etl/optional.h index c7958dfb..244aa3cb 100644 --- a/include/etl/optional.h +++ b/include/etl/optional.h @@ -142,11 +142,12 @@ namespace etl } #include "private/diagnostic_uninitialized_push.h" + //*************************************************************************** /// Copy constructor. //*************************************************************************** ETL_CONSTEXPR20_STL - optional_impl(const optional_impl& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible::value) + optional_impl(const optional_impl& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible::value) { if (other.has_value()) { @@ -160,7 +161,7 @@ namespace etl /// Move constructor. //*************************************************************************** ETL_CONSTEXPR20_STL - optional_impl(optional_impl&& other) ETL_NOEXCEPT_IF(etl::is_nothrow_move_constructible::value) + optional_impl(optional_impl&& other) ETL_NOEXCEPT_IF(etl::is_nothrow_move_constructible::value) { if (other.has_value()) { @@ -231,7 +232,7 @@ namespace etl /// Assignment operator from optional_impl. //*************************************************************************** ETL_CONSTEXPR20_STL - optional_impl& operator=(const optional_impl& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible::value) + optional_impl& operator=(const optional_impl& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible::value) { if (this != &other) { @@ -478,11 +479,11 @@ namespace etl /// Swaps this value with another. //*************************************************************************** ETL_CONSTEXPR20_STL - void swap(optional_impl& other) + void swap(optional_impl& other) ETL_NOEXCEPT_IF(etl::is_nothrow_move_constructible::value) { - optional_impl temp(*this); - *this = other; - other = temp; + optional_impl temp(ETL_MOVE(*this)); + *this = ETL_MOVE(other); + other = ETL_MOVE(temp); } //*************************************************************************** @@ -498,7 +499,7 @@ namespace etl /// //************************************************************************* ETL_CONSTEXPR20_STL - T& emplace(const optional_impl& other) + T& emplace(const optional_impl& other) { #if ETL_IS_DEBUG_BUILD ETL_ASSERT(other.has_value(), ETL_ERROR(optional_invalid)); @@ -750,10 +751,11 @@ namespace etl } #include "private/diagnostic_uninitialized_push.h" + //*************************************************************************** /// Copy constructor. //*************************************************************************** - ETL_CONSTEXPR14 optional_impl(const optional_impl& other) ETL_NOEXCEPT + ETL_CONSTEXPR14 optional_impl(const optional_impl& other) ETL_NOEXCEPT { if (other.has_value()) { @@ -827,7 +829,7 @@ namespace etl //*************************************************************************** /// Assignment operator from optional_impl. //*************************************************************************** - ETL_CONSTEXPR14 optional_impl& operator=(const optional_impl& other) ETL_NOEXCEPT + ETL_CONSTEXPR14 optional_impl& operator=(const optional_impl& other) ETL_NOEXCEPT { if (this != &other) { @@ -1078,7 +1080,7 @@ namespace etl /// //************************************************************************* ETL_CONSTEXPR20_STL - T& emplace(const optional_impl& other) + T& emplace(const optional_impl& other) { #if ETL_IS_DEBUG_BUILD ETL_ASSERT(other.has_value(), ETL_ERROR(optional_invalid)); @@ -1336,24 +1338,8 @@ namespace etl #endif #include "private/diagnostic_uninitialized_push.h" -#if ETL_USING_CPP11 - //*************************************************************************** - /// Copy constructor. - //*************************************************************************** - template - ETL_CONSTEXPR14 optional(const optional& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible::value) - : impl_t(other) - { - } - //*************************************************************************** - /// Copy constructor. - //*************************************************************************** - template - ETL_CONSTEXPR20_STL optional(const optional& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible::value) - : impl_t(other) - { - } +#if ETL_USING_CPP11 #else //*************************************************************************** /// Copy constructor. @@ -1365,26 +1351,6 @@ namespace etl #endif #include "private/diagnostic_pop.h" -#if ETL_USING_CPP11 - //*************************************************************************** - /// Move constructor. - //*************************************************************************** - template - ETL_CONSTEXPR14 optional(optional&& other) ETL_NOEXCEPT_IF(etl::is_nothrow_move_constructible::value) - : impl_t(etl::move(other)) - { - } - - //*************************************************************************** - /// Move constructor. - //*************************************************************************** - template - ETL_CONSTEXPR20_STL optional(optional&& other) ETL_NOEXCEPT_IF(etl::is_nothrow_move_constructible::value) - : impl_t(etl::move(other)) - { - } -#endif - #if ETL_USING_CPP11 //*************************************************************************** /// Converting constructor from value type. @@ -1502,27 +1468,6 @@ namespace etl #endif #if ETL_USING_CPP11 - //*************************************************************************** - /// Assignment operator from optional. - //*************************************************************************** - template - ETL_CONSTEXPR14 optional& operator=(const optional& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible::value) - { - impl_t::operator=(other); - - return *this; - } - - //*************************************************************************** - /// Assignment operator from optional. - //*************************************************************************** - template - ETL_CONSTEXPR20_STL optional& operator=(const optional& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible::value) - { - impl_t::operator=(other); - - return *this; - } #else //*************************************************************************** /// Assignment operator from optional. @@ -1535,30 +1480,6 @@ namespace etl } #endif -#if ETL_USING_CPP11 - //*************************************************************************** - /// Move assignment operator from optional. - //*************************************************************************** - template - ETL_CONSTEXPR14 optional& operator=(optional&& other) ETL_NOEXCEPT_IF(etl::is_nothrow_move_constructible::value) - { - impl_t::operator=(etl::move(other)); - - return *this; - } - - //*************************************************************************** - /// Move assignment operator from optional. - //*************************************************************************** - template - ETL_CONSTEXPR20_STL optional& operator=(optional&& other) ETL_NOEXCEPT_IF(etl::is_nothrow_move_constructible::value) - { - impl_t::operator=(etl::move(other)); - - return *this; - } -#endif - #if ETL_USING_CPP11 //*************************************************************************** /// Converting assignment operator from value type. @@ -2226,6 +2147,71 @@ namespace etl #include "private/diagnostic_pop.h" +#if ETL_CPP11_SUPPORTED + //*************************************************************************** + /// Creates an optional object from `value`. + //*************************************************************************** + template + ETL_CONSTEXPR14 etl::optional::type> make_optional(T&& value) // + ETL_NOEXCEPT_IF((etl::is_nothrow_constructible::type, T&&>::value)) + { + return etl::optional::type>(etl::forward(value)); + } + template + ETL_CONSTEXPR20_STL etl::optional::type> make_optional(T&& value) // + ETL_NOEXCEPT_IF((etl::is_nothrow_constructible::type, T&&>::value)) + { + return etl::optional::type>(etl::forward(value)); + } + + //*************************************************************************** + /// Creates an optional object constructed in-place from `args...` . + /// Equivalent to `return etl::optional(etl::in_place, etl::forward(args)...);`. + /// This overload participates in overload resolution only if + /// `etl::is_constructible_v` is true. + //*************************************************************************** + template ::value, int>::type = 0, // + typename U = T, ETL_OPTIONAL_ENABLE_CPP14> + ETL_CONSTEXPR14 etl::optional make_optional(Args&&... args) // + ETL_NOEXCEPT_IF((etl::is_nothrow_constructible::value)) + { + return etl::optional(etl::in_place_t{}, etl::forward(args)...); + } + template ::value, int>::type = 0, // + typename U = T, ETL_OPTIONAL_ENABLE_CPP20_STL> + ETL_CONSTEXPR20_STL etl::optional make_optional(Args&&... args) // + ETL_NOEXCEPT_IF((etl::is_nothrow_constructible::value)) + { + return etl::optional(etl::in_place_t{}, etl::forward(args)...); + } + + #if ETL_HAS_INITIALIZER_LIST + //*************************************************************************** + /// Creates an optional object constructed in-place from `ilist` and `args...`. + /// Equivalent to `return etl::optional(std::in_place, ilist, std::forward(args)...);`. + /// This overload participates in overload resolution only if + /// `etl::is_constructible_v&, Args...>` is true. + //*************************************************************************** + template &, Args...>::value, int>::type = 0, // + typename U = T, ETL_OPTIONAL_ENABLE_CPP14> + ETL_CONSTEXPR14 etl::optional make_optional(std::initializer_list ilist, Args&&... args) + ETL_NOEXCEPT_IF((etl::is_nothrow_constructible&, Args...>::value)) + { + return etl::optional(etl::in_place_t{}, ilist, etl::forward(args)...); + } + template &, Args...>::value, int>::type = 0, // + typename U = T, ETL_OPTIONAL_ENABLE_CPP20_STL> + ETL_CONSTEXPR20_STL etl::optional make_optional(std::initializer_list ilist, Args&&... args) + ETL_NOEXCEPT_IF((etl::is_nothrow_constructible&, Args...>::value)) + { + return etl::optional(etl::in_place_t{}, ilist, etl::forward(args)...); + } + #endif +#else //*************************************************************************** /// Make an optional. //*************************************************************************** @@ -2234,6 +2220,7 @@ namespace etl { return etl::optional::type>(value); } +#endif //*************************************************************************** /// Template deduction guides. @@ -2242,16 +2229,17 @@ namespace etl template optional(T) -> optional; #endif -} // namespace etl -//************************************************************************* -/// Swaps the values. -//************************************************************************* -template -ETL_CONSTEXPR20_STL void swap(etl::optional& lhs, etl::optional& rhs) -{ - lhs.swap(rhs); -} + //************************************************************************* + /// Swaps the values. + //************************************************************************* + template + ETL_CONSTEXPR20_STL void swap(etl::optional& lhs, etl::optional& rhs) ETL_NOEXCEPT_FROM(lhs.swap(rhs)) + { + lhs.swap(rhs); + } + +} // namespace etl #undef ETL_OPTIONAL_ENABLE_CPP14 #undef ETL_OPTIONAL_ENABLE_CPP20_STL diff --git a/test/data.h b/test/data.h index 782ef92a..4aadbf67 100644 --- a/test/data.h +++ b/test/data.h @@ -201,14 +201,14 @@ public: TestDataM(TestDataM&& other) noexcept : value(std::move(other.value)) - , valid(true) + , valid(other.valid) { other.valid = false; } TestDataM(const TestDataM&& other) noexcept : value(std::move(other.value)) - , valid(true) + , valid(other.valid) { other.valid = false; } @@ -220,10 +220,13 @@ public: TestDataM& operator=(TestDataM&& other) noexcept { - value = std::move(other.value); - valid = true; + if (this != &other) + { + value = std::move(other.value); + valid = other.valid; - other.valid = false; + other.valid = false; + } return *this; } diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 88242053..04ad9574 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -28,13 +28,15 @@ SOFTWARE. #include "unit_test_framework.h" +#include #include #include #include #include -#include +#include #include "data.h" +#include "etl/algorithm.h" #include "etl/optional.h" #include "etl/vector.h" @@ -83,6 +85,29 @@ namespace }; #include "etl/private/diagnostic_pop.h" + struct TestIL + { + constexpr TestIL() + : a(0) + , b(0) + , c(0) + { + } + + ETL_CONSTEXPR20 TestIL(std::initializer_list il, int a_, int b_, int c_) + : a(a_) + , b(b_) + , c(c_) + { + etl::copy_n(il.begin(), std::min(il.size(), arr.size()), arr.begin()); + } + + std::array arr{}; + int a; + int b; + int c; + }; + SUITE(test_optional) { //************************************************************************* @@ -91,38 +116,38 @@ namespace etl::optional data1; etl::optional data2; - CHECK(!bool(data1)); - CHECK(!bool(data2)); + CHECK(!static_cast(data1)); + CHECK(!static_cast(data2)); CHECK(!data1.has_value()); CHECK(!data2.has_value()); data1 = Data("Hello"); - CHECK(bool(data1)); + CHECK(static_cast(data1)); CHECK(data1.has_value()); CHECK_EQUAL(Data("Hello"), data1); data1 = data2; - CHECK(!bool(data1)); - CHECK(!bool(data2)); + CHECK(!static_cast(data1)); + CHECK(!static_cast(data2)); CHECK(!data1.has_value()); CHECK(!data2.has_value()); data1 = Data("World"); data2 = data1; - CHECK(bool(data1)); - CHECK(bool(data2)); + CHECK(static_cast(data1)); + CHECK(static_cast(data2)); CHECK(data1.has_value()); CHECK(data2.has_value()); - etl::optional data3(data1); - CHECK(bool(data3)); + const etl::optional data3(data1); + CHECK(static_cast(data3)); CHECK(data3.has_value()); CHECK_EQUAL(data1, data3); etl::optional data4; data4 = Data("Hello"); data4 = etl::nullopt; - CHECK(!bool(data4)); + CHECK(!static_cast(data4)); CHECK(!data4.has_value()); } @@ -133,7 +158,7 @@ namespace constexpr etl::optional opt(etl::in_place_t{}, 1); CHECK_TRUE(opt.has_value()); - CHECK(bool(opt)); + CHECK(static_cast(opt)); CHECK_EQUAL(1, opt.value()); } #endif @@ -157,7 +182,7 @@ namespace constexpr etl::optional opt(etl::in_place_t{}, 1, 2); CHECK_TRUE(opt.has_value()); - CHECK(bool(opt)); + CHECK(static_cast(opt)); CHECK_EQUAL(1, opt.value().a); CHECK_EQUAL(2, opt.value().b); } @@ -166,34 +191,14 @@ namespace //************************************************************************* TEST(test_construct_from_initializer_list_and_arguments) { - struct S - { - S() - : vi() - , a(0) - , b(0) - { - } + etl::optional opt(etl::in_place_t{}, {10, 11, 12}, 1, 2, 3); - S(std::initializer_list il, int a_, int b_) - : vi(il) - , a(a_) - , b(b_) - { - } - - std::vector vi; - int a; - int b; - }; - - etl::optional opt(etl::in_place_t{}, {10, 11, 12}, 1, 2); - - CHECK_EQUAL(10, opt.value().vi[0]); - CHECK_EQUAL(11, opt.value().vi[1]); - CHECK_EQUAL(12, opt.value().vi[2]); + CHECK_EQUAL(10, opt.value().arr[0]); + CHECK_EQUAL(11, opt.value().arr[1]); + CHECK_EQUAL(12, opt.value().arr[2]); CHECK_EQUAL(1, opt.value().a); CHECK_EQUAL(2, opt.value().b); + CHECK_EQUAL(3, opt.value().c); } //************************************************************************* @@ -201,10 +206,10 @@ namespace { Data data("Hello"); - etl::optional opt{data}; + const etl::optional opt{data}; CHECK(opt.has_value()); - CHECK(bool(opt)); + CHECK(static_cast(opt)); CHECK_EQUAL(data, opt); } @@ -258,20 +263,89 @@ namespace } //************************************************************************* - TEST(test_moveable) + TEST(test_moveable_not_fundamental) { #include "etl/private/diagnostic_pessimizing_move_push.h" + + // Construct by moving value. etl::optional data(std::move(DataM(1))); + CHECK(data.has_value()); + CHECK(data->valid); CHECK_EQUAL(1U, data.value().value); - CHECK(bool(data)); + CHECK(static_cast(data)); - data = std::move(etl::optional(std::move(DataM(2)))); - CHECK_EQUAL(2U, data.value().value); - CHECK(bool(data)); + // Assign by moving optional. + { + etl::optional temp(DataM(2)); + data = std::move(temp); + CHECK(temp.has_value() && !temp->valid); // NOLINT "Note that a moved-from optional still contains a value (although invalid one)." + CHECK(data.has_value()); + CHECK(data->valid); + CHECK(static_cast(data)); + CHECK_EQUAL(2U, data.value().value); + } - etl::optional data2(etl::move(data)); - CHECK_EQUAL(2U, data2.value().value); - CHECK(bool(data2)); + // Construct by moving optional. + { + etl::optional data2(etl::move(data)); + CHECK(data.has_value() && !data->valid); // NOLINT "Note that a moved-from optional still contains a value (although invalid one)." + CHECK(data2.has_value()); + CHECK(data2->valid); + CHECK(static_cast(data2)); + CHECK_EQUAL(2U, data2.value().value); + } + + // Try to move construct/assign from valueless. + { + etl::optional temp; + etl::optional data2(etl::move(temp)); + CHECK(!data2.has_value()); + + data2 = etl::move(etl::optional()); + CHECK(!data2.has_value()); + } +#include "etl/private/diagnostic_pop.h" + } + + //************************************************************************* + TEST(test_moveable_fundamental) + { +#include "etl/private/diagnostic_pessimizing_move_push.h" + + // Construct by moving value. + etl::optional data(1U); + CHECK(data.has_value()); + CHECK_EQUAL(1U, data.value()); + CHECK(static_cast(data)); + + // Assign by moving optional. + { + etl::optional temp(2U); + data = std::move(temp); + CHECK(temp.has_value()); // NOLINT "Note that a moved-from optional still contains a value." + CHECK(data.has_value()); + CHECK(static_cast(data)); + CHECK_EQUAL(2U, data.value()); + } + + // Construct by moving optional. + { + etl::optional data2(etl::move(data)); + CHECK(data.has_value()); // NOLINT "Note that a moved-from optional still contains a value." + CHECK(data2.has_value()); + CHECK(static_cast(data2)); + CHECK_EQUAL(2U, data2.value()); + } + + // Try to move construct/assign from valueless. + { + etl::optional temp; + etl::optional data2(etl::move(temp)); + CHECK(!data2.has_value()); + + data2 = etl::move(etl::optional()); + CHECK(!data2.has_value()); + } #include "etl/private/diagnostic_pop.h" } @@ -281,7 +355,7 @@ namespace etl::optional data(etl::nullopt); data = 1; data = etl::nullopt; - CHECK(!bool(data)); + CHECK(!static_cast(data)); } //************************************************************************* @@ -290,7 +364,7 @@ namespace etl::optional data(etl::nullopt); data = Data("Hello"); data = etl::nullopt; - CHECK(!bool(data)); + CHECK(!static_cast(data)); } //************************************************************************* @@ -317,7 +391,7 @@ namespace CHECK_EQUAL(5, resultFT); const NonFundamentalType constNFT{"Default"}; - NonFundamentalType resultNFT = etl::optional{}.value_or(constNFT); + const NonFundamentalType resultNFT = etl::optional{}.value_or(constNFT); CHECK_EQUAL("Default", resultNFT); } @@ -339,12 +413,12 @@ namespace TEST(test_chained_value_or_github_bug_720) { - github_bug_720_bug_helper helper{}; + const github_bug_720_bug_helper helper{}; - int value1 = helper.get_valid().value_or(1); + const int value1 = helper.get_valid().value_or(1); CHECK_EQUAL(5, value1); - int value2 = helper.get_invalid().value_or(1); + const int value2 = helper.get_invalid().value_or(1); CHECK_EQUAL(1, value2); } @@ -783,11 +857,11 @@ namespace container.resize(5, Data("1")); - CHECK(bool(container[0])); - CHECK(bool(container[1])); - CHECK(bool(container[2])); - CHECK(bool(container[3])); - CHECK(bool(container[4])); + CHECK(static_cast(container[0])); + CHECK(static_cast(container[1])); + CHECK(static_cast(container[2])); + CHECK(static_cast(container[3])); + CHECK(static_cast(container[4])); } //************************************************************************* @@ -796,10 +870,10 @@ namespace // The indexed access doesn't work in Linux for some reason!!! #ifndef ETL_PLATFORM_LINUX etl::optional> container; - CHECK(!bool(container)); // + CHECK(!static_cast(container)); // container = etl::vector(); - CHECK(bool(container)); + CHECK(static_cast(container)); container.value().resize(5, Data("1")); CHECK_EQUAL(5U, container.value().size()); @@ -823,51 +897,61 @@ namespace //************************************************************************* TEST(test_swap) { - etl::optional original1(Data("1")); - etl::optional original2(Data("2")); + const etl::optional original1(Data("1")); + const etl::optional original2(Data("2")); etl::optional data1; etl::optional data2; // Both invalid. swap(data1, data2); - CHECK(!bool(data1)); - CHECK(!bool(data2)); + CHECK(!static_cast(data1)); + CHECK(!static_cast(data2)); - // Data1 valid; + // data1 is valid data1 = original1; data2 = etl::nullopt; swap(data1, data2); - CHECK(!bool(data1)); - CHECK(bool(data2)); + CHECK(!static_cast(data1)); + CHECK(static_cast(data2)); CHECK_EQUAL(data2, original1); - // Data2 valid; + // data2 is valid data1 = etl::nullopt; data2 = original2; swap(data1, data2); - CHECK(bool(data1)); - CHECK(!bool(data2)); + CHECK(static_cast(data1)); + CHECK(!static_cast(data2)); CHECK_EQUAL(data1, original2); - // Both valid; + // both are valid data1 = original1; data2 = original2; swap(data1, data2); - CHECK(bool(data1)); - CHECK(bool(data2)); + CHECK(static_cast(data1)); + CHECK(static_cast(data2)); CHECK_EQUAL(data1, original2); CHECK_EQUAL(data2, original1); } + //************************************************************************* + TEST(test_swap_moveable) + { + etl::optional data1(1U); + etl::optional data2(2U); + swap(data1, data2); + CHECK_EQUAL(2U, data1.value().value); + CHECK_EQUAL(1U, data2.value().value); + } + //************************************************************************* TEST(test_reset) { etl::optional data(Data("1")); - CHECK(bool(data)); + CHECK(static_cast(data)); data.reset(); - CHECK(!bool(data)); + CHECK(!static_cast(data)); } //************************************************************************* @@ -908,10 +992,10 @@ namespace TEST(test_optional_pod_emplace_bug_712) { - etl::optional optionalObject; // The Test: Does this compile for an object with a - // deleted default constructor? + const etl::optional optionalObject; // The Test: Does this compile for an object with a + // deleted default constructor? - // Make sure it isn't optimised away. + // Make sure it isn't optimized away. CHECK_FALSE(optionalObject.has_value()); } @@ -1080,10 +1164,10 @@ namespace TEST(range_based_for_loop_with_value) { - etl::optional opt = 4; + const etl::optional opt = 4; int sum = 0; - for (int value : opt) + for (const int value : opt) { sum += value; } @@ -1093,10 +1177,10 @@ namespace TEST(range_based_for_loop_empty) { - etl::optional opt; + const etl::optional opt; int sum = 0; - for (int value : opt) + for (const int value : opt) { sum += value; } @@ -1106,8 +1190,8 @@ namespace TEST(test_range_based_for_loop_non_trivial) { - etl::optional opt = Data("TEST"); - int count = 0; + const etl::optional opt = Data("TEST"); + int count = 0; for (const Data& value : opt) { @@ -1163,8 +1247,8 @@ namespace { // etl::optional should compile when T has deleted copy/move // constructors, as long as T is constructible from the given arguments. - Issue146_Container with_value(42); - Issue146_Container without_value; + const Issue146_Container with_value(42); + const Issue146_Container without_value; CHECK_TRUE(with_value.a.has_value()); CHECK_EQUAL(42, with_value.a->_some); @@ -1172,12 +1256,103 @@ namespace CHECK_FALSE(without_value.a.has_value()); // in_place construction should also work - etl::optional opt(etl::in_place_t{}, 99); + const etl::optional opt(etl::in_place_t{}, 99); CHECK_TRUE(opt.has_value()); CHECK_EQUAL(99, opt->_some); } #endif + TEST(test_make_optional_1_lvalue) + { + const std::string test_value("TEST"); + Data test_data(test_value); + const etl::optional opt = etl::make_optional(test_data); + CHECK_TRUE(opt.has_value()); + CHECK_EQUAL(test_value, opt.value().value); + } + + TEST(test_make_optional_1_const_value) + { + const std::string test_value("TEST"); + const Data test_data(test_value); + const etl::optional opt = etl::make_optional(test_data); + CHECK_TRUE(opt.has_value()); + CHECK_EQUAL(test_data.value, opt.value().value); + } + +#if ETL_USING_CPP11 + TEST(test_make_optional_1_rvalue) + { + constexpr uint32_t test_value = 42; + DataM test_data(test_value); + const etl::optional opt = etl::make_optional(std::move(test_data)); + CHECK_TRUE(opt.has_value()); + CHECK_FALSE(test_data.valid); + CHECK_EQUAL(test_value, opt.value().value); + } +#endif + +#if ETL_USING_CPP14 + TEST(test_make_optional_1_constexpr) + { + constexpr etl::optional opt = etl::make_optional(42); + CHECK_TRUE(opt.has_value()); + CHECK_EQUAL(42, opt.value()); + } +#endif + + TEST(test_make_optional_2_lvalue) + { + std::string test_value("TEST"); + const auto opt = etl::make_optional(test_value); + CHECK_TRUE(opt.has_value()); + CHECK_EQUAL(test_value, opt.value().value); + } + + TEST(test_make_optional_2_rvalue) + { + const etl::optional opt = etl::make_optional(42u); + CHECK_TRUE(opt.has_value()); + CHECK_EQUAL(42, opt.value().value); + } + +#if ETL_USING_CPP14 + TEST(test_make_optional_2_constexpr) + { + constexpr etl::optional opt = etl::make_optional(42); + CHECK_TRUE(opt.has_value()); + CHECK_EQUAL(42, opt.value()); + } +#endif + + TEST(test_make_optional_3) + { + int test_value1(1); + const int test_value2(2); + const auto opt = etl::make_optional({10, 11, 12}, test_value1, test_value2, 3); + CHECK_TRUE(opt.has_value()); + CHECK_EQUAL(10, opt->arr[0]); + CHECK_EQUAL(11, opt->arr[1]); + CHECK_EQUAL(12, opt->arr[2]); + CHECK_EQUAL(test_value1, opt->a); + CHECK_EQUAL(test_value2, opt->b); + CHECK_EQUAL(3, opt->c); + } + +#if ETL_USING_CPP20 && ETL_USING_STL + TEST(test_make_optional_3_constexpr) + { + constexpr etl::optional opt = etl::make_optional({1, 2}, 10, 20, 30); + CHECK_TRUE(opt.has_value()); + CHECK_EQUAL(1, opt->arr[0]); + CHECK_EQUAL(2, opt->arr[1]); + CHECK_EQUAL(0, opt->arr[2]); + CHECK_EQUAL(10, opt->a); + CHECK_EQUAL(20, opt->b); + CHECK_EQUAL(30, opt->c); + } +#endif + //************************************************************************* // Tests for noexcept properties of etl::optional // The noexcept specs only take effect when ETL_USING_EXCEPTIONS is enabled, @@ -1185,16 +1360,17 @@ namespace // The etl::is_nothrow_* traits only work with STL or builtins. //************************************************************************* #if ETL_USING_CPP11 && ETL_USING_EXCEPTIONS && (defined(ETL_USE_TYPE_TRAITS_BUILTINS) || (ETL_USING_STL && !defined(ETL_USER_DEFINED_TYPE_TRAITS))) - struct NothrowCopyMove + struct NothrowAtAll { - NothrowCopyMove() noexcept {} - NothrowCopyMove(const NothrowCopyMove&) noexcept {} - NothrowCopyMove(NothrowCopyMove&&) noexcept {} - NothrowCopyMove& operator=(const NothrowCopyMove&) noexcept + NothrowAtAll() noexcept {} + NothrowAtAll(const NothrowAtAll&) noexcept {} + NothrowAtAll(NothrowAtAll&&) noexcept {} + NothrowAtAll(std::initializer_list) noexcept {} + NothrowAtAll& operator=(const NothrowAtAll&) noexcept { return *this; } - NothrowCopyMove& operator=(NothrowCopyMove&&) noexcept + NothrowAtAll& operator=(NothrowAtAll&&) noexcept { return *this; } @@ -1205,10 +1381,10 @@ namespace ThrowingCopy() noexcept {} ThrowingCopy(const ThrowingCopy&) {} // may throw ThrowingCopy(ThrowingCopy&&) noexcept {} - ThrowingCopy& operator=(const ThrowingCopy&) + ThrowingCopy& operator=(const ThrowingCopy&) // may throw { return *this; - } // may throw + } ThrowingCopy& operator=(ThrowingCopy&&) noexcept { return *this; @@ -1224,22 +1400,23 @@ namespace { return *this; } - ThrowingMove& operator=(ThrowingMove&&) - { - return *this; - } // may throw - }; - - struct ThrowingBoth - { - ThrowingBoth() noexcept {} - ThrowingBoth(const ThrowingBoth&) {} // may throw - ThrowingBoth(ThrowingBoth&&) {} // may throw - ThrowingBoth& operator=(const ThrowingBoth&) + ThrowingMove& operator=(ThrowingMove&&) // may throw { return *this; } - ThrowingBoth& operator=(ThrowingBoth&&) + }; + + struct ThrowingAll + { + ThrowingAll() {} // may throw + ThrowingAll(const ThrowingAll&) {} // may throw + ThrowingAll(ThrowingAll&&) {} // may throw + ThrowingAll(std::initializer_list) {} // may throw + ThrowingAll& operator=(const ThrowingAll&) // may throw + { + return *this; + } + ThrowingAll& operator=(ThrowingAll&&) // may throw { return *this; } @@ -1249,14 +1426,14 @@ namespace { // When T is nothrow copy constructible, optional should be too static_assert(etl::is_nothrow_copy_constructible>::value, "optional should be nothrow copy constructible"); - static_assert(etl::is_nothrow_copy_constructible>::value, - "optional should be nothrow copy constructible"); + static_assert(etl::is_nothrow_copy_constructible>::value, + "optional should be nothrow copy constructible"); // When T is NOT nothrow copy constructible, optional should not be either static_assert(!etl::is_nothrow_copy_constructible>::value, "optional should NOT be nothrow copy constructible"); - static_assert(!etl::is_nothrow_copy_constructible>::value, - "optional should NOT be nothrow copy constructible"); + static_assert(!etl::is_nothrow_copy_constructible>::value, + "optional should NOT be nothrow copy constructible"); // ThrowingMove has nothrow copy but throwing move static_assert(etl::is_nothrow_copy_constructible>::value, @@ -1267,20 +1444,29 @@ namespace TEST(test_optional_nothrow_move_constructible) { - // When T is nothrow move constructible, optional should be too + // When T is nothrow move constructible, optional (and swap) should be too static_assert(etl::is_nothrow_move_constructible>::value, "optional should be nothrow move constructible"); - static_assert(etl::is_nothrow_move_constructible>::value, - "optional should be nothrow move constructible"); + static_assert(etl::is_nothrow_move_constructible>::value, + "optional should be nothrow move constructible"); + static_assert(noexcept(swap(std::declval&>(), std::declval&>())), "swap() should be nothrow"); + static_assert(noexcept(swap(std::declval&>(), std::declval&>())), + "swap() should be nothrow"); - // When T is NOT nothrow move constructible, optional should not be either + // When T is NOT nothrow move constructible, optional (and swap) should not be either static_assert(!etl::is_nothrow_move_constructible>::value, "optional should NOT be nothrow move constructible"); - static_assert(!etl::is_nothrow_move_constructible>::value, - "optional should NOT be nothrow move constructible"); + static_assert(!etl::is_nothrow_move_constructible>::value, + "optional should NOT be nothrow move constructible"); + static_assert(!noexcept(swap(std::declval&>(), std::declval&>())), + "swap() should NOT be nothrow"); + static_assert(!noexcept(swap(std::declval&>(), std::declval&>())), + "swap() should NOT be nothrow"); // ThrowingCopy has nothrow move but throwing copy static_assert(etl::is_nothrow_move_constructible>::value, "optional should be nothrow move constructible"); + static_assert(noexcept(swap(std::declval&>(), std::declval&>())), + "swap() should be nothrow"); CHECK(true); // Placeholder for the static_asserts above } @@ -1289,12 +1475,12 @@ namespace { // Default construction of optional should always be noexcept static_assert(etl::is_nothrow_default_constructible>::value, "optional should be nothrow default constructible"); - static_assert(etl::is_nothrow_default_constructible>::value, - "optional should be nothrow default constructible"); + static_assert(etl::is_nothrow_default_constructible>::value, + "optional should be nothrow default constructible"); static_assert(etl::is_nothrow_default_constructible>::value, "optional should be nothrow default constructible"); - static_assert(etl::is_nothrow_default_constructible>::value, - "optional should be nothrow default constructible"); + static_assert(etl::is_nothrow_default_constructible>::value, + "optional should be nothrow default constructible"); CHECK(true); } @@ -1312,8 +1498,7 @@ namespace { // Copy assignment should propagate noexcept from T static_assert(etl::is_nothrow_copy_assignable>::value, "optional should be nothrow copy assignable"); - static_assert(etl::is_nothrow_copy_assignable>::value, - "optional should be nothrow copy assignable"); + static_assert(etl::is_nothrow_copy_assignable>::value, "optional should be nothrow copy assignable"); // ThrowingCopy has a throwing copy constructor, so copy assignment should not be noexcept static_assert(!etl::is_nothrow_copy_assignable>::value, @@ -1326,8 +1511,7 @@ namespace { // Move assignment should propagate noexcept from T static_assert(etl::is_nothrow_move_assignable>::value, "optional should be nothrow move assignable"); - static_assert(etl::is_nothrow_move_assignable>::value, - "optional should be nothrow move assignable"); + static_assert(etl::is_nothrow_move_assignable>::value, "optional should be nothrow move assignable"); // ThrowingMove has a throwing move constructor, so move assignment should not be noexcept static_assert(!etl::is_nothrow_move_assignable>::value, @@ -1335,6 +1519,35 @@ namespace CHECK(true); } + + TEST(test_make_optional_nothrow) + { + // make_optional #1 + { + NothrowAtAll nothrowAtAll{}; + static_assert(noexcept(etl::make_optional(nothrowAtAll)), "make_optional(NothrowAtAll&) should be nothrow"); + static_assert(noexcept(etl::make_optional(std::move(nothrowAtAll))), "make_optional(NothrowAtAll&&) should be nothrow"); + ThrowingAll throwingAll{}; + static_assert(!noexcept(etl::make_optional(throwingAll)), "make_optional(ThrowingAll&) should NOT be nothrow"); + static_assert(!noexcept(etl::make_optional(std::move(throwingAll))), "make_optional(ThrowingAll&&) should NOT be nothrow"); + } + + // make_optional #2 + { + static_assert(noexcept(etl::make_optional()), "make_optional() should be nothrow"); + static_assert(noexcept(etl::make_optional()), "make_optional() should be nothrow"); + static_assert(!noexcept(etl::make_optional()), "make_optional() should NOT be nothrow"); + static_assert(!noexcept(etl::make_optional()), "make_optional() should NOT be nothrow"); + } + + // make_optional #3 + { + static_assert(noexcept(etl::make_optional({1, 2, 3})), "make_optional(1,2,3) should be nothrow"); + static_assert(noexcept(etl::make_optional({1, 2, 3})), "make_optional(1,2,3) should be nothrow"); + static_assert(!noexcept(etl::make_optional({1, 2, 3})), "make_optional({1,2,3}) should NOT be nothrow"); + static_assert(!noexcept(etl::make_optional({1, 2, 3})), "make_optional({1,2,3}) should NOT be nothrow"); + } + } #endif } } // namespace