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`.
This commit is contained in:
Sergei Shirokov 2026-04-23 02:10:08 +03:00
parent b9b36d8155
commit e36b08cff5
3 changed files with 424 additions and 234 deletions

View File

@ -142,11 +142,12 @@ namespace etl
}
#include "private/diagnostic_uninitialized_push.h"
//***************************************************************************
/// Copy constructor.
//***************************************************************************
ETL_CONSTEXPR20_STL
optional_impl(const optional_impl<T>& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible<T>::value)
optional_impl(const optional_impl& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible<T>::value)
{
if (other.has_value())
{
@ -160,7 +161,7 @@ namespace etl
/// Move constructor.
//***************************************************************************
ETL_CONSTEXPR20_STL
optional_impl(optional_impl<T>&& other) ETL_NOEXCEPT_IF(etl::is_nothrow_move_constructible<T>::value)
optional_impl(optional_impl&& other) ETL_NOEXCEPT_IF(etl::is_nothrow_move_constructible<T>::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<T>& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible<T>::value)
optional_impl& operator=(const optional_impl& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible<T>::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<T>::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<T>& 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<T>& 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<T>& 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<T>& 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 <typename U = T, ETL_OPTIONAL_ENABLE_CPP14>
ETL_CONSTEXPR14 optional(const optional& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible<T>::value)
: impl_t(other)
{
}
//***************************************************************************
/// Copy constructor.
//***************************************************************************
template <typename U = T, ETL_OPTIONAL_ENABLE_CPP20_STL>
ETL_CONSTEXPR20_STL optional(const optional& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible<T>::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 <typename U = T, ETL_OPTIONAL_ENABLE_CPP14>
ETL_CONSTEXPR14 optional(optional&& other) ETL_NOEXCEPT_IF(etl::is_nothrow_move_constructible<T>::value)
: impl_t(etl::move(other))
{
}
//***************************************************************************
/// Move constructor.
//***************************************************************************
template <typename U = T, ETL_OPTIONAL_ENABLE_CPP20_STL>
ETL_CONSTEXPR20_STL optional(optional&& other) ETL_NOEXCEPT_IF(etl::is_nothrow_move_constructible<T>::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 <typename U = T, ETL_OPTIONAL_ENABLE_CPP14>
ETL_CONSTEXPR14 optional& operator=(const optional& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible<T>::value)
{
impl_t::operator=(other);
return *this;
}
//***************************************************************************
/// Assignment operator from optional.
//***************************************************************************
template <typename U = T, ETL_OPTIONAL_ENABLE_CPP20_STL>
ETL_CONSTEXPR20_STL optional& operator=(const optional& other) ETL_NOEXCEPT_IF(etl::is_nothrow_copy_constructible<T>::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 <typename U = T, ETL_OPTIONAL_ENABLE_CPP14>
ETL_CONSTEXPR14 optional& operator=(optional&& other) ETL_NOEXCEPT_IF(etl::is_nothrow_move_constructible<T>::value)
{
impl_t::operator=(etl::move(other));
return *this;
}
//***************************************************************************
/// Move assignment operator from optional.
//***************************************************************************
template <typename U = T, ETL_OPTIONAL_ENABLE_CPP20_STL>
ETL_CONSTEXPR20_STL optional& operator=(optional&& other) ETL_NOEXCEPT_IF(etl::is_nothrow_move_constructible<T>::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 <typename T, typename U = T, ETL_OPTIONAL_ENABLE_CPP14>
ETL_CONSTEXPR14 etl::optional<typename etl::decay<T>::type> make_optional(T&& value) //
ETL_NOEXCEPT_IF((etl::is_nothrow_constructible<T, T&&>::value))
{
return etl::optional<typename etl::decay<T>::type>(etl::forward<T>(value));
}
template <typename T, typename U = T, ETL_OPTIONAL_ENABLE_CPP20_STL>
ETL_CONSTEXPR20_STL etl::optional<typename etl::decay<T>::type> make_optional(T&& value) //
ETL_NOEXCEPT_IF((etl::is_nothrow_constructible<T, T&&>::value))
{
return etl::optional<typename etl::decay<T>::type>(etl::forward<T>(value));
}
//***************************************************************************
/// Creates an optional object constructed in-place from `args...` .
/// Equivalent to `return etl::optional<T>(etl::in_place, etl::forward<Args>(args)...);`.
/// This overload participates in overload resolution only if
/// `etl::is_constructible_v<T, Args...>` is true.
//***************************************************************************
template <typename T, typename... Args, //
typename etl::enable_if< etl::is_constructible<T, Args...>::value, int>::type = 0, //
typename U = T, ETL_OPTIONAL_ENABLE_CPP14>
ETL_CONSTEXPR14 etl::optional<T> make_optional(Args&&... args) //
ETL_NOEXCEPT_IF((etl::is_nothrow_constructible<T, Args...>::value))
{
return etl::optional<typename etl::decay<T>::type>(etl::in_place_t{}, etl::forward<Args>(args)...);
}
template <typename T, typename... Args, //
typename etl::enable_if< etl::is_constructible<T, Args...>::value, int>::type = 0, //
typename U = T, ETL_OPTIONAL_ENABLE_CPP20_STL>
ETL_CONSTEXPR20_STL etl::optional<T> make_optional(Args&&... args) //
ETL_NOEXCEPT_IF((etl::is_nothrow_constructible<T, Args...>::value))
{
return etl::optional<typename etl::decay<T>::type>(etl::in_place_t{}, etl::forward<Args>(args)...);
}
#if ETL_HAS_INITIALIZER_LIST
//***************************************************************************
/// Creates an optional object constructed in-place from `ilist` and `args...`.
/// Equivalent to `return etl::optional<T>(std::in_place, ilist, std::forward<Args>(args)...);`.
/// This overload participates in overload resolution only if
/// `etl::is_constructible_v<T, std::initializer_list<U>&, Args...>` is true.
//***************************************************************************
template <typename T, typename TL, typename... Args, //
typename etl::enable_if< etl::is_constructible<T, std::initializer_list<TL>&, Args...>::value, int>::type = 0, //
typename U = T, ETL_OPTIONAL_ENABLE_CPP14>
ETL_CONSTEXPR14 etl::optional<T> make_optional(std::initializer_list<TL> ilist, Args&&... args)
ETL_NOEXCEPT_IF((etl::is_nothrow_constructible<T, std::initializer_list<TL>&, Args...>::value))
{
return etl::optional<typename etl::decay<T>::type>(etl::in_place_t{}, ilist, etl::forward<Args>(args)...);
}
template <typename T, typename TL, typename... Args, //
typename etl::enable_if< etl::is_constructible<T, std::initializer_list<TL>&, Args...>::value, int>::type = 0, //
typename U = T, ETL_OPTIONAL_ENABLE_CPP20_STL>
ETL_CONSTEXPR20_STL etl::optional<T> make_optional(std::initializer_list<TL> ilist, Args&&... args)
ETL_NOEXCEPT_IF((etl::is_nothrow_constructible<T, std::initializer_list<TL>&, Args...>::value))
{
return etl::optional<typename etl::decay<T>::type>(etl::in_place_t{}, ilist, etl::forward<Args>(args)...);
}
#endif
#else
//***************************************************************************
/// Make an optional.
//***************************************************************************
@ -2234,6 +2220,7 @@ namespace etl
{
return etl::optional<typename etl::decay<T>::type>(value);
}
#endif
//***************************************************************************
/// Template deduction guides.
@ -2242,16 +2229,17 @@ namespace etl
template <typename T>
optional(T) -> optional<T>;
#endif
} // namespace etl
//*************************************************************************
/// Swaps the values.
//*************************************************************************
template <typename T>
ETL_CONSTEXPR20_STL void swap(etl::optional<T>& lhs, etl::optional<T>& rhs)
{
lhs.swap(rhs);
}
//*************************************************************************
/// Swaps the values.
//*************************************************************************
template <typename T>
ETL_CONSTEXPR20_STL void swap(etl::optional<T>& lhs, etl::optional<T>& rhs) ETL_NOEXCEPT_FROM(lhs.swap(rhs))
{
lhs.swap(rhs);
}
} // namespace etl
#undef ETL_OPTIONAL_ENABLE_CPP14
#undef ETL_OPTIONAL_ENABLE_CPP20_STL

View File

@ -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;
}

View File

@ -28,13 +28,15 @@ SOFTWARE.
#include "unit_test_framework.h"
#include <array>
#include <cstdint>
#include <ostream>
#include <string>
#include <type_traits>
#include <vector>
#include <utility>
#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<int> 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<int, 3> arr{};
int a;
int b;
int c;
};
SUITE(test_optional)
{
//*************************************************************************
@ -91,38 +116,38 @@ namespace
etl::optional<Data> data1;
etl::optional<Data> data2;
CHECK(!bool(data1));
CHECK(!bool(data2));
CHECK(!static_cast<bool>(data1));
CHECK(!static_cast<bool>(data2));
CHECK(!data1.has_value());
CHECK(!data2.has_value());
data1 = Data("Hello");
CHECK(bool(data1));
CHECK(static_cast<bool>(data1));
CHECK(data1.has_value());
CHECK_EQUAL(Data("Hello"), data1);
data1 = data2;
CHECK(!bool(data1));
CHECK(!bool(data2));
CHECK(!static_cast<bool>(data1));
CHECK(!static_cast<bool>(data2));
CHECK(!data1.has_value());
CHECK(!data2.has_value());
data1 = Data("World");
data2 = data1;
CHECK(bool(data1));
CHECK(bool(data2));
CHECK(static_cast<bool>(data1));
CHECK(static_cast<bool>(data2));
CHECK(data1.has_value());
CHECK(data2.has_value());
etl::optional<Data> data3(data1);
CHECK(bool(data3));
const etl::optional<Data> data3(data1);
CHECK(static_cast<bool>(data3));
CHECK(data3.has_value());
CHECK_EQUAL(data1, data3);
etl::optional<Data> data4;
data4 = Data("Hello");
data4 = etl::nullopt;
CHECK(!bool(data4));
CHECK(!static_cast<bool>(data4));
CHECK(!data4.has_value());
}
@ -133,7 +158,7 @@ namespace
constexpr etl::optional<int> opt(etl::in_place_t{}, 1);
CHECK_TRUE(opt.has_value());
CHECK(bool(opt));
CHECK(static_cast<bool>(opt));
CHECK_EQUAL(1, opt.value());
}
#endif
@ -157,7 +182,7 @@ namespace
constexpr etl::optional<TestData> opt(etl::in_place_t{}, 1, 2);
CHECK_TRUE(opt.has_value());
CHECK(bool(opt));
CHECK(static_cast<bool>(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<TestIL> opt(etl::in_place_t{}, {10, 11, 12}, 1, 2, 3);
S(std::initializer_list<int> il, int a_, int b_)
: vi(il)
, a(a_)
, b(b_)
{
}
std::vector<int> vi;
int a;
int b;
};
etl::optional<S> 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<Data> opt{data};
const etl::optional<Data> opt{data};
CHECK(opt.has_value());
CHECK(bool(opt));
CHECK(static_cast<bool>(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<DataM> data(std::move(DataM(1)));
CHECK(data.has_value());
CHECK(data->valid);
CHECK_EQUAL(1U, data.value().value);
CHECK(bool(data));
CHECK(static_cast<bool>(data));
data = std::move(etl::optional<DataM>(std::move(DataM(2))));
CHECK_EQUAL(2U, data.value().value);
CHECK(bool(data));
// Assign by moving optional.
{
etl::optional<DataM> 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<bool>(data));
CHECK_EQUAL(2U, data.value().value);
}
etl::optional<DataM> data2(etl::move(data));
CHECK_EQUAL(2U, data2.value().value);
CHECK(bool(data2));
// Construct by moving optional.
{
etl::optional<DataM> 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<bool>(data2));
CHECK_EQUAL(2U, data2.value().value);
}
// Try to move construct/assign from valueless.
{
etl::optional<DataM> temp;
etl::optional<DataM> data2(etl::move(temp));
CHECK(!data2.has_value());
data2 = etl::move(etl::optional<DataM>());
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<std::uint8_t> data(1U);
CHECK(data.has_value());
CHECK_EQUAL(1U, data.value());
CHECK(static_cast<bool>(data));
// Assign by moving optional.
{
etl::optional<std::uint8_t> 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<bool>(data));
CHECK_EQUAL(2U, data.value());
}
// Construct by moving optional.
{
etl::optional<std::uint8_t> 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<bool>(data2));
CHECK_EQUAL(2U, data2.value());
}
// Try to move construct/assign from valueless.
{
etl::optional<std::uint8_t> temp;
etl::optional<std::uint8_t> data2(etl::move(temp));
CHECK(!data2.has_value());
data2 = etl::move(etl::optional<std::uint8_t>());
CHECK(!data2.has_value());
}
#include "etl/private/diagnostic_pop.h"
}
@ -281,7 +355,7 @@ namespace
etl::optional<int> data(etl::nullopt);
data = 1;
data = etl::nullopt;
CHECK(!bool(data));
CHECK(!static_cast<bool>(data));
}
//*************************************************************************
@ -290,7 +364,7 @@ namespace
etl::optional<Data> data(etl::nullopt);
data = Data("Hello");
data = etl::nullopt;
CHECK(!bool(data));
CHECK(!static_cast<bool>(data));
}
//*************************************************************************
@ -317,7 +391,7 @@ namespace
CHECK_EQUAL(5, resultFT);
const NonFundamentalType constNFT{"Default"};
NonFundamentalType resultNFT = etl::optional<NonFundamentalType>{}.value_or(constNFT);
const NonFundamentalType resultNFT = etl::optional<NonFundamentalType>{}.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<bool>(container[0]));
CHECK(static_cast<bool>(container[1]));
CHECK(static_cast<bool>(container[2]));
CHECK(static_cast<bool>(container[3]));
CHECK(static_cast<bool>(container[4]));
}
//*************************************************************************
@ -796,10 +870,10 @@ namespace
// The indexed access doesn't work in Linux for some reason!!!
#ifndef ETL_PLATFORM_LINUX
etl::optional<etl::vector<Data, 10>> container;
CHECK(!bool(container)); //
CHECK(!static_cast<bool>(container)); //
container = etl::vector<Data, 10>();
CHECK(bool(container));
CHECK(static_cast<bool>(container));
container.value().resize(5, Data("1"));
CHECK_EQUAL(5U, container.value().size());
@ -823,51 +897,61 @@ namespace
//*************************************************************************
TEST(test_swap)
{
etl::optional<Data> original1(Data("1"));
etl::optional<Data> original2(Data("2"));
const etl::optional<Data> original1(Data("1"));
const etl::optional<Data> original2(Data("2"));
etl::optional<Data> data1;
etl::optional<Data> data2;
// Both invalid.
swap(data1, data2);
CHECK(!bool(data1));
CHECK(!bool(data2));
CHECK(!static_cast<bool>(data1));
CHECK(!static_cast<bool>(data2));
// Data1 valid;
// data1 is valid
data1 = original1;
data2 = etl::nullopt;
swap(data1, data2);
CHECK(!bool(data1));
CHECK(bool(data2));
CHECK(!static_cast<bool>(data1));
CHECK(static_cast<bool>(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<bool>(data1));
CHECK(!static_cast<bool>(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<bool>(data1));
CHECK(static_cast<bool>(data2));
CHECK_EQUAL(data1, original2);
CHECK_EQUAL(data2, original1);
}
//*************************************************************************
TEST(test_swap_moveable)
{
etl::optional<DataM> data1(1U);
etl::optional<DataM> data2(2U);
swap(data1, data2);
CHECK_EQUAL(2U, data1.value().value);
CHECK_EQUAL(1U, data2.value().value);
}
//*************************************************************************
TEST(test_reset)
{
etl::optional<Data> data(Data("1"));
CHECK(bool(data));
CHECK(static_cast<bool>(data));
data.reset();
CHECK(!bool(data));
CHECK(!static_cast<bool>(data));
}
//*************************************************************************
@ -908,10 +992,10 @@ namespace
TEST(test_optional_pod_emplace_bug_712)
{
etl::optional<MyPODObject> optionalObject; // The Test: Does this compile for an object with a
// deleted default constructor?
const etl::optional<MyPODObject> 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<int> opt = 4;
const etl::optional<int> 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<int> opt;
const etl::optional<int> 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<Data> opt = Data("TEST");
int count = 0;
const etl::optional<Data> opt = Data("TEST");
int count = 0;
for (const Data& value : opt)
{
@ -1163,8 +1247,8 @@ namespace
{
// etl::optional<T> 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<Issue146_NonCopyable> opt(etl::in_place_t{}, 99);
const etl::optional<Issue146_NonCopyable> 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<Data> 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<Data> 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<DataM> 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<int> 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 etl::optional<Data> opt = etl::make_optional<Data>(test_value);
CHECK_TRUE(opt.has_value());
CHECK_EQUAL(test_value, opt.value().value);
}
TEST(test_make_optional_2_rvalue)
{
const etl::optional<DataM> opt = etl::make_optional<DataM>(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<uint32_t> opt = etl::make_optional<uint32_t>(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 etl::optional<TestIL> opt = etl::make_optional<TestIL>({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<TestIL> opt = etl::make_optional<TestIL>({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<int>) 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<int>) {} // 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<T> should be too
static_assert(etl::is_nothrow_copy_constructible<etl::optional<int>>::value, "optional<int> should be nothrow copy constructible");
static_assert(etl::is_nothrow_copy_constructible<etl::optional<NothrowCopyMove>>::value,
"optional<NothrowCopyMove> should be nothrow copy constructible");
static_assert(etl::is_nothrow_copy_constructible<etl::optional<NothrowAtAll>>::value,
"optional<NothrowAtAll> should be nothrow copy constructible");
// When T is NOT nothrow copy constructible, optional<T> should not be either
static_assert(!etl::is_nothrow_copy_constructible<etl::optional<ThrowingCopy>>::value,
"optional<ThrowingCopy> should NOT be nothrow copy constructible");
static_assert(!etl::is_nothrow_copy_constructible<etl::optional<ThrowingBoth>>::value,
"optional<ThrowingBoth> should NOT be nothrow copy constructible");
static_assert(!etl::is_nothrow_copy_constructible<etl::optional<ThrowingAll>>::value,
"optional<ThrowingAll> should NOT be nothrow copy constructible");
// ThrowingMove has nothrow copy but throwing move
static_assert(etl::is_nothrow_copy_constructible<etl::optional<ThrowingMove>>::value,
@ -1267,20 +1444,29 @@ namespace
TEST(test_optional_nothrow_move_constructible)
{
// When T is nothrow move constructible, optional<T> should be too
// When T is nothrow move constructible, optional<T> (and swap) should be too
static_assert(etl::is_nothrow_move_constructible<etl::optional<int>>::value, "optional<int> should be nothrow move constructible");
static_assert(etl::is_nothrow_move_constructible<etl::optional<NothrowCopyMove>>::value,
"optional<NothrowCopyMove> should be nothrow move constructible");
static_assert(etl::is_nothrow_move_constructible<etl::optional<NothrowAtAll>>::value,
"optional<NothrowAtAll> should be nothrow move constructible");
static_assert(noexcept(swap(std::declval<etl::optional<int>&>(), std::declval<etl::optional<int>&>())), "swap<int>() should be nothrow");
static_assert(noexcept(swap(std::declval<etl::optional<NothrowAtAll>&>(), std::declval<etl::optional<NothrowAtAll>&>())),
"swap<NothrowAtAll>() should be nothrow");
// When T is NOT nothrow move constructible, optional<T> should not be either
// When T is NOT nothrow move constructible, optional<T> (and swap) should not be either
static_assert(!etl::is_nothrow_move_constructible<etl::optional<ThrowingMove>>::value,
"optional<ThrowingMove> should NOT be nothrow move constructible");
static_assert(!etl::is_nothrow_move_constructible<etl::optional<ThrowingBoth>>::value,
"optional<ThrowingBoth> should NOT be nothrow move constructible");
static_assert(!etl::is_nothrow_move_constructible<etl::optional<ThrowingAll>>::value,
"optional<ThrowingAll> should NOT be nothrow move constructible");
static_assert(!noexcept(swap(std::declval<etl::optional<ThrowingMove>&>(), std::declval<etl::optional<ThrowingMove>&>())),
"swap<ThrowingMove>() should NOT be nothrow");
static_assert(!noexcept(swap(std::declval<etl::optional<ThrowingAll>&>(), std::declval<etl::optional<ThrowingAll>&>())),
"swap<ThrowingAll>() should NOT be nothrow");
// ThrowingCopy has nothrow move but throwing copy
static_assert(etl::is_nothrow_move_constructible<etl::optional<ThrowingCopy>>::value,
"optional<ThrowingCopy> should be nothrow move constructible");
static_assert(noexcept(swap(std::declval<etl::optional<ThrowingCopy>&>(), std::declval<etl::optional<ThrowingCopy>&>())),
"swap<ThrowingCopy>() 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<etl::optional<int>>::value, "optional<int> should be nothrow default constructible");
static_assert(etl::is_nothrow_default_constructible<etl::optional<NothrowCopyMove>>::value,
"optional<NothrowCopyMove> should be nothrow default constructible");
static_assert(etl::is_nothrow_default_constructible<etl::optional<NothrowAtAll>>::value,
"optional<NothrowAtAll> should be nothrow default constructible");
static_assert(etl::is_nothrow_default_constructible<etl::optional<ThrowingCopy>>::value,
"optional<ThrowingCopy> should be nothrow default constructible");
static_assert(etl::is_nothrow_default_constructible<etl::optional<ThrowingBoth>>::value,
"optional<ThrowingBoth> should be nothrow default constructible");
static_assert(etl::is_nothrow_default_constructible<etl::optional<ThrowingAll>>::value,
"optional<ThrowingAll> 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<etl::optional<int>>::value, "optional<int> should be nothrow copy assignable");
static_assert(etl::is_nothrow_copy_assignable<etl::optional<NothrowCopyMove>>::value,
"optional<NothrowCopyMove> should be nothrow copy assignable");
static_assert(etl::is_nothrow_copy_assignable<etl::optional<NothrowAtAll>>::value, "optional<NothrowAtAll> 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<etl::optional<ThrowingCopy>>::value,
@ -1326,8 +1511,7 @@ namespace
{
// Move assignment should propagate noexcept from T
static_assert(etl::is_nothrow_move_assignable<etl::optional<int>>::value, "optional<int> should be nothrow move assignable");
static_assert(etl::is_nothrow_move_assignable<etl::optional<NothrowCopyMove>>::value,
"optional<NothrowCopyMove> should be nothrow move assignable");
static_assert(etl::is_nothrow_move_assignable<etl::optional<NothrowAtAll>>::value, "optional<NothrowAtAll> 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<etl::optional<ThrowingMove>>::value,
@ -1335,6 +1519,21 @@ namespace
CHECK(true);
}
TEST(test_make_optional_nothrow)
{
// make_optional #1
static_assert(noexcept(etl::make_optional(NothrowAtAll{})), "make_optional(NothrowAtAll) should be nothrow");
static_assert(!noexcept(etl::make_optional(ThrowingAll{})), "make_optional(ThrowingAll) should NOT be nothrow");
// make_optional #2
static_assert(noexcept(etl::make_optional<NothrowAtAll>()), "make_optional<NothrowAtAll>() should be nothrow");
static_assert(!noexcept(etl::make_optional<ThrowingAll>()), "make_optional<ThrowingAll>() should NOT be nothrow");
// make_optional #3
static_assert(noexcept(etl::make_optional<NothrowAtAll>({1, 2, 3})), "make_optional<NothrowAtAll>(1,2,3) should be nothrow");
static_assert(!noexcept(etl::make_optional<ThrowingAll>({1, 2, 3})), "make_optional<ThrowingAll>({1,2,3}) should NOT be nothrow");
}
#endif
}
} // namespace