diff --git a/cetlvast/suites/compile/test_any_footprint_get_const.cpp b/cetlvast/suites/compile/test_unbounded_variant_footprint_get_const.cpp similarity index 72% rename from cetlvast/suites/compile/test_any_footprint_get_const.cpp rename to cetlvast/suites/compile/test_unbounded_variant_footprint_get_const.cpp index 7ce257b..7a7de4f 100644 --- a/cetlvast/suites/compile/test_any_footprint_get_const.cpp +++ b/cetlvast/suites/compile/test_unbounded_variant_footprint_get_const.cpp @@ -1,5 +1,5 @@ /// @file -/// Compile test that ensures it's impossible get "bigger" value than `Footprint` of const `any`. +/// Compile test that ensures it's impossible get "bigger" value than `Footprint` of const `unbounded_variant`. /// /// @copyright /// Copyright (C) OpenCyphal Development Team @@ -7,7 +7,7 @@ /// SPDX-License-Identifier: MIT /// -#include "cetl/any.hpp" +#include "cetl/unbounded_variant.hpp" #include @@ -23,9 +23,9 @@ constexpr type_id type_id_value{}; int main() { - using any = cetl::any; + using ub_var = cetl::unbounded_variant; - const any test{static_cast(0)}; + const ub_var test{static_cast(0)}; #ifndef CETLVAST_COMPILETEST_PRECHECK @@ -34,11 +34,11 @@ int main() // static_assert(sizeof(ValueType) <= Footprint, // "Cannot contain the requested type since the footprint is too small"); // ``` - return cetl::any_cast(test); + return cetl::get(test); #else - return cetl::any_cast(test); + return cetl::get(test); #endif } diff --git a/cetlvast/suites/compile/test_any_footprint_get_non_const.cpp b/cetlvast/suites/compile/test_unbounded_variant_footprint_get_non_const.cpp similarity index 73% rename from cetlvast/suites/compile/test_any_footprint_get_non_const.cpp rename to cetlvast/suites/compile/test_unbounded_variant_footprint_get_non_const.cpp index a049e39..c8f6487 100644 --- a/cetlvast/suites/compile/test_any_footprint_get_non_const.cpp +++ b/cetlvast/suites/compile/test_unbounded_variant_footprint_get_non_const.cpp @@ -1,5 +1,5 @@ /// @file -/// Compile test that ensures it's impossible set "bigger" value than `Footprint` of `any`. +/// Compile test that ensures it's impossible set "bigger" value than `Footprint` of `unbounded_variant`. /// /// @copyright /// Copyright (C) OpenCyphal Development Team @@ -7,7 +7,7 @@ /// SPDX-License-Identifier: MIT /// -#include "cetl/any.hpp" +#include "cetl/unbounded_variant.hpp" #include @@ -23,9 +23,9 @@ constexpr type_id type_id_value{}; int main() { - using any = cetl::any; + using ub_var = cetl::unbounded_variant; - any test{static_cast(0)}; + ub_var test{static_cast(0)}; #ifndef CETLVAST_COMPILETEST_PRECHECK @@ -34,11 +34,11 @@ int main() // static_assert(sizeof(ValueType) <= Footprint, // "Cannot contain the requested type since the footprint is too small"); // ``` - return cetl::any_cast(test); + return cetl::get(test); #else - return cetl::any_cast(test); + return cetl::get(test); #endif } diff --git a/cetlvast/suites/compile/test_any_footprint_set.cpp b/cetlvast/suites/compile/test_unbounded_variant_footprint_set.cpp similarity index 82% rename from cetlvast/suites/compile/test_any_footprint_set.cpp rename to cetlvast/suites/compile/test_unbounded_variant_footprint_set.cpp index 7348299..e4d7f51 100644 --- a/cetlvast/suites/compile/test_any_footprint_set.cpp +++ b/cetlvast/suites/compile/test_unbounded_variant_footprint_set.cpp @@ -1,5 +1,5 @@ /// @file -/// Compile test that ensures it's impossible set "bigger" value than `Footprint` of `any`. +/// Compile test that ensures it's impossible set "bigger" value than `Footprint` of `unbounded_variant`. /// /// @copyright /// Copyright (C) OpenCyphal Development Team @@ -7,7 +7,7 @@ /// SPDX-License-Identifier: MIT /// -#include "cetl/any.hpp" +#include "cetl/unbounded_variant.hpp" #include @@ -23,9 +23,9 @@ constexpr type_id type_id_value{}; int main() { - using any = cetl::any; + using ub_var = cetl::unbounded_variant; - any test{}; + ub_var test{}; #ifndef CETLVAST_COMPILETEST_PRECHECK diff --git a/cetlvast/suites/docs/examples/example_10_any.cpp b/cetlvast/suites/docs/examples/example_10_unbounded_variant.cpp similarity index 55% rename from cetlvast/suites/docs/examples/example_10_any.cpp rename to cetlvast/suites/docs/examples/example_10_unbounded_variant.cpp index fb34414..00ca124 100644 --- a/cetlvast/suites/docs/examples/example_10_any.cpp +++ b/cetlvast/suites/docs/examples/example_10_unbounded_variant.cpp @@ -1,16 +1,16 @@ /// @file -/// Example of using cetl::any. +/// Example of using cetl::unbounded_variant. /// /// @copyright /// Copyright (C) OpenCyphal Development Team /// Copyright Amazon.com Inc. or its affiliates. /// SPDX-License-Identifier: MIT /// -#include "cetl/any.hpp" +#include "cetl/unbounded_variant.hpp" -#include +#include -//! [example_10_any_type_id] +//! [example_10_unbounded_variant_type_id] namespace cetl { template <> @@ -23,23 +23,23 @@ template <> constexpr type_id type_id_value = {4}; } // namespace cetl -//! [example_10_any_type_id] +//! [example_10_unbounded_variant_type_id] -TEST(example_10_any, basic_usage) +TEST(example_10_unbounded_variant, basic_usage) { - //! [example_10_any_basic_usage] + //! [example_10_unbounded_variant_basic_usage] /// This example is inspired by the [cppreference.com](https://en.cppreference.com/w/cpp/utility/any) - /// documentation. - using any = cetl::any; + /// documentation, but instead of `any` it's called `unbounded_variant`. + using ub_var = cetl::unbounded_variant; - any a = 1; - EXPECT_EQ(1, cetl::any_cast(a)); + ub_var a = 1; + EXPECT_THAT(cetl::get(a), 1); a = 3.14; - EXPECT_EQ(3.14, cetl::any_cast(a)); + EXPECT_THAT(cetl::get(a), 3.14); a = true; - EXPECT_TRUE(cetl::any_cast(a)); + EXPECT_TRUE(cetl::get(a)); // bad any cast // @@ -49,9 +49,9 @@ TEST(example_10_any, basic_usage) // Should be used in the tests where exceptions are expected (see `EXPECT_THROW`). const auto sink = [](auto&&) {}; - EXPECT_THROW(sink(cetl::any_cast(a)), cetl::bad_any_cast); + EXPECT_THROW(sink(cetl::get(a)), cetl::bad_unbounded_variant_access); #else - EXPECT_EQ(nullptr, cetl::any_cast(&a)); + EXPECT_THAT(cetl::get_if(&a), testing::IsNull()); #endif a = 2; @@ -65,6 +65,6 @@ TEST(example_10_any, basic_usage) // pointer to contained data // a = 3; - EXPECT_EQ(3, *cetl::any_cast(&a)); - //! [example_10_any_basic_usage] + EXPECT_THAT(*cetl::get_if(&a), 3); + //! [example_10_unbounded_variant_basic_usage] } diff --git a/cetlvast/suites/unittest/test_any.cpp b/cetlvast/suites/unittest/test_any.cpp deleted file mode 100644 index 048c48c..0000000 --- a/cetlvast/suites/unittest/test_any.cpp +++ /dev/null @@ -1,1092 +0,0 @@ -/// @file -/// Unit tests for cetl/any.hpp -/// -/// @copyright -/// Copyright (C) OpenCyphal Development Team -/// Copyright Amazon.com Inc. or its affiliates. -/// SPDX-License-Identifier: MIT - -#include - -#include -#include -#include -#include - -// NOLINTBEGIN(*-use-after-move) - -namespace -{ - -using cetl::any; -using cetl::any_cast; -using cetl::make_any; -using cetl::in_place_type_t; -using cetl::type_id; -using cetl::type_id_type; -using cetl::rtti_helper; - -using namespace std::string_literals; - -/// HELPERS --------------------------------------------------------------------------------------------------------- - -#if defined(__cpp_exceptions) - -// Workaround for GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425 -// Should be used in the tests where exceptions are expected (see `EXPECT_THROW`). -const auto sink = [](auto&&) {}; - -#endif - -enum class side_effect_op : char -{ - Construct = '@', - CopyConstruct = 'C', - MoveConstruct = 'M', - CopyAssign = '=', - MoveAssign = '<', - Destruct = '~', - DestructMoved = '_', -}; -using side_effect_fn = std::function; - -struct side_effect_stats -{ - std::string ops; - int assignments = 0; - int constructs = 0; - int destructs = 0; - - auto make_side_effect_fn() - { - return [this](side_effect_op op) { - ops += static_cast(op); - - constructs += (op == side_effect_op::Construct) ? 1 : 0; - constructs += (op == side_effect_op::CopyConstruct) ? 1 : 0; - constructs += (op == side_effect_op::MoveConstruct) ? 1 : 0; - - assignments += (op == side_effect_op::CopyAssign) ? 1 : 0; - assignments += (op == side_effect_op::MoveAssign) ? 1 : 0; - - destructs += (op == side_effect_op::Destruct) ? 1 : 0; - destructs += (op == side_effect_op::DestructMoved) ? 1 : 0; - }; - } -}; - -struct TestBase : rtti_helper> -{ - char payload_; - int value_ = 0; - bool moved_ = false; - - TestBase(const char payload, side_effect_fn side_effect) - : payload_(payload) - , side_effect_(std::move(side_effect)) - { - side_effect_(side_effect_op::Construct); - } - TestBase(const TestBase& other) - { - copy_from(other, side_effect_op::CopyConstruct); - } - TestBase(TestBase&& other) noexcept - { - move_from(other, side_effect_op::MoveConstruct); - } - - ~TestBase() override - { - side_effect_(moved_ ? side_effect_op::DestructMoved : side_effect_op::Destruct); - } - - TestBase& operator=(const TestBase& other) - { - copy_from(other, side_effect_op::CopyAssign); - return *this; - } - - TestBase& operator=(TestBase&& other) noexcept - { - move_from(other, side_effect_op::MoveAssign); - return *this; - } - - CETL_NODISCARD virtual const char* what() const noexcept - { - return "TestBase"; - } - -private: - side_effect_fn side_effect_; - - void copy_from(const TestBase& other, const side_effect_op op) - { - payload_ = other.payload_; - side_effect_ = other.side_effect_; - value_ = other.value_ + 10; - - side_effect_(op); - } - - void move_from(TestBase& other, const side_effect_op op) - { - payload_ = other.payload_; - side_effect_ = other.side_effect_; - value_ = other.value_ + 1; - - other.moved_ = true; - other.payload_ = '\0'; - - side_effect_(op); - } - -}; // TestBase - -struct TestCopyableOnly final : TestBase -{ - explicit TestCopyableOnly( - const char payload = '?', - side_effect_fn side_effect = [](auto) {}) - : TestBase(payload, std::move(side_effect)) - { - } - TestCopyableOnly(const TestCopyableOnly& other) = default; - TestCopyableOnly(TestCopyableOnly&& other) noexcept = delete; - - TestCopyableOnly& operator=(const TestCopyableOnly& other) = default; - TestCopyableOnly& operator=(TestCopyableOnly&& other) noexcept = delete; - - CETL_NODISCARD const char* what() const noexcept override - { - return "TestCopyableOnly"; - } - - // rtti - - static constexpr type_id _get_type_id_() noexcept - { - return {0x0, 0b01}; - } - - CETL_NODISCARD void* _cast_(const type_id& id) & noexcept override - { - return (id == _get_type_id_()) ? static_cast(this) : base::_cast_(id); - } - CETL_NODISCARD const void* _cast_(const type_id& id) const& noexcept override - { - return (id == _get_type_id_()) ? static_cast(this) : base::_cast_(id); - } - -private: - using base = TestBase; -}; - -struct TestMovableOnly final : TestBase -{ - explicit TestMovableOnly( - const char payload = '?', - side_effect_fn side_effect = [](auto) {}) - : TestBase(payload, std::move(side_effect)) - { - } - TestMovableOnly(const TestMovableOnly& other) = delete; - TestMovableOnly(TestMovableOnly&& other) noexcept = default; - - TestMovableOnly& operator=(const TestMovableOnly& other) = delete; - TestMovableOnly& operator=(TestMovableOnly&& other) noexcept = default; - - CETL_NODISCARD const char* what() const noexcept override - { - return "TestMovableOnly"; - } - - // rtti - - static constexpr type_id _get_type_id_() noexcept - { - return {0x0, 0b10}; - } - - CETL_NODISCARD void* _cast_(const type_id& id) & noexcept override - { - return (id == _get_type_id_()) ? static_cast(this) : base::_cast_(id); - } - CETL_NODISCARD const void* _cast_(const type_id& id) const& noexcept override - { - return (id == _get_type_id_()) ? static_cast(this) : base::_cast_(id); - } - -private: - using base = TestBase; -}; - -struct TestCopyableAndMovable final : TestBase -{ - // Just to make this class a bit bigger than base. - char place_holder_; - - explicit TestCopyableAndMovable( - const char payload = '?', - side_effect_fn side_effect = [](auto) {}) - : TestBase(payload, std::move(side_effect)) - , place_holder_{payload} - { - } - - CETL_NODISCARD const char* what() const noexcept override - { - return "TestCopyableAndMovable"; - } - - // rtti - - static constexpr type_id _get_type_id_() noexcept - { - return {0x0, 0b11}; - } - - CETL_NODISCARD void* _cast_(const type_id& id) & noexcept override - { - return (id == _get_type_id_()) ? static_cast(this) : base::_cast_(id); - } - CETL_NODISCARD const void* _cast_(const type_id& id) const& noexcept override - { - return (id == _get_type_id_()) ? static_cast(this) : base::_cast_(id); - } - -private: - using base = TestBase; -}; - -/// TESTS ----------------------------------------------------------------------------------------------------------- - -TEST(test_any, cppref_example) -{ - using any = any; - - any a = 1; - EXPECT_EQ(1, any_cast(a)); - - a = 3.14; - EXPECT_EQ(3.14, any_cast(a)); - - a = true; - EXPECT_TRUE(any_cast(a)); - - // bad cast - a = 1; -#if defined(__cpp_exceptions) - EXPECT_THROW(sink(any_cast(a)), cetl::bad_any_cast); -#else - EXPECT_EQ(nullptr, any_cast(&a)); -#endif - - a = 2; - EXPECT_TRUE(a.has_value()); - - // reset - a.reset(); - EXPECT_FALSE(a.has_value()); - - // pointer to contained data - a = 3; - EXPECT_EQ(3, *any_cast(&a)); -} - -TEST(test_any, ctor_1_default) -{ - EXPECT_FALSE((any<0>{}.has_value())); - EXPECT_FALSE((any<0, false>{}.has_value())); - EXPECT_FALSE((any<0, false, true>{}.has_value())); - EXPECT_FALSE((any<0, true, false>{}.has_value())); - EXPECT_FALSE((any<0, true, true, 1>{}.has_value())); - - EXPECT_FALSE((any<1>{}.has_value())); - EXPECT_FALSE((any<1, false>{}.has_value())); - EXPECT_FALSE((any<1, false, true>{}.has_value())); - EXPECT_FALSE((any<1, true, false>{}.has_value())); - EXPECT_FALSE((any<1, true, true, 1>{}.has_value())); - - EXPECT_FALSE((any<13>{}.has_value())); - EXPECT_FALSE((any<13, false>{}.has_value())); - EXPECT_FALSE((any<13, false, true>{}.has_value())); - EXPECT_FALSE((any<13, true, false>{}.has_value())); - EXPECT_FALSE((any<13, true, true, 1>{}.has_value())); -} - -TEST(test_any, ctor_2_copy) -{ - // Primitive `int` - { - using any = any; - - const any src{42}; - any dst{src}; - - EXPECT_EQ(42, any_cast(src)); - EXPECT_EQ(42, any_cast(dst)); - } - - // Copyable and Movable `any` - { - using test = TestCopyableAndMovable; - using any = any; - - const any src{test{}}; - any dst{src}; - - EXPECT_EQ(1 + 10, any_cast(src).value_); - EXPECT_EQ(1, any_cast(src).value_); - - EXPECT_EQ(1 + 10 + 10, any_cast(dst).value_); - EXPECT_EQ(1 + 10, any_cast(dst).value_); - EXPECT_EQ(1 + 10, any_cast(dst).value_); - - EXPECT_FALSE(any_cast(dst).moved_); - EXPECT_EQ(1 + 10 + 1, any_cast(std::move(dst)).value_); - EXPECT_TRUE(any_cast(dst).moved_); - } - - // Copyable only `any` - { - using test = TestCopyableOnly; - using any = any; - - const test value{}; - any src{value}; - const any dst{src}; - - EXPECT_EQ(10, any_cast(src).value_); - EXPECT_EQ(10, any_cast(src).value_); - - EXPECT_EQ(10 + 10, any_cast(dst).value_); - } - - // Movable only `any` - { - using test = TestMovableOnly; - using any = any; - - test value{'X'}; - EXPECT_FALSE(value.moved_); - EXPECT_EQ('X', value.payload_); - - test value2{std::move(value)}; - EXPECT_TRUE(value.moved_); - EXPECT_EQ('\0', value.payload_); - EXPECT_FALSE(value2.moved_); - EXPECT_EQ(1, value2.value_); - EXPECT_EQ('X', value2.payload_); - // any src{value}; //< expectedly won't compile (due to !copyable `test`) - any src{std::move(value2)}; - EXPECT_EQ('X', any_cast(src).payload_); - // const any dst{src}; //< expectedly won't compile (due to !copyable `any`) - } - - // Non-Copyable and non-movable `any` - { - using test = TestCopyableAndMovable; - using any = any; - - any src{test{}}; - EXPECT_EQ(1 + 10, any_cast(src).value_); - EXPECT_EQ(1, any_cast(src).value_); - EXPECT_EQ(1 + 1, any_cast(std::move(src)).value_); - // const any dst{src}; //< expectedly won't compile (due to !copyable `any`) - // any dst{std::move(src)}; //< expectedly won't compile (due to !movable `any`) - } -} - -TEST(test_any, ctor_3_move) -{ - // Primitive `int` - { - using any = any; - - any src{42}; - const any dst{std::move(src)}; - - EXPECT_FALSE(src.has_value()); - EXPECT_EQ(42, any_cast(dst)); - } - - // Copyable and Movable `any` - { - using test = TestCopyableAndMovable; - using any = any; - - any src{test{}}; - EXPECT_TRUE(src.has_value()); - - const any dst{std::move(src)}; - EXPECT_TRUE(dst.has_value()); - EXPECT_FALSE(src.has_value()); - EXPECT_EQ(2, any_cast(dst).value_); - } - - // Movable only `any` - { - using test = TestMovableOnly; - using any = any; - - any src{test{'X'}}; - const any dst{std::move(src)}; - - EXPECT_EQ(nullptr, any_cast(&src)); - EXPECT_EQ(2, any_cast(dst).value_); - EXPECT_EQ('X', any_cast(dst).payload_); - // EXPECT_EQ(2, any_cast(dst).value_); //< expectedly won't compile (due to !copyable) - // EXPECT_EQ(2, any_cast(dst).value_); //< expectedly won't compile (due to const) - } - - // Copyable only `any`, movable only `unique_ptr` - { - using test = std::unique_ptr; - using any = any; - - any src{std::make_unique()}; - any dst{std::move(src)}; - EXPECT_FALSE(src.has_value()); - - auto ptr = any_cast(std::move(dst)); - EXPECT_TRUE(ptr); - EXPECT_EQ(0, ptr->value_); - } -} - -TEST(test_any, ctor_4_move_value) -{ - using test = TestCopyableAndMovable; - using any = any; - - test value{'Y'}; - const any dst{std::move(value)}; - EXPECT_TRUE(value.moved_); - EXPECT_TRUE(dst.has_value()); - EXPECT_EQ(1, any_cast(dst).value_); - EXPECT_EQ('Y', any_cast(dst).payload_); -} - -TEST(test_any, ctor_5_in_place) -{ - struct TestType : rtti_helper> - { - char ch_; - int number_; - - TestType(const char ch, const int number) - { - ch_ = ch; - number_ = number; - } - }; - using any = any; - - const any src{in_place_type_t{}, 'Y', 42}; - - const auto test = any_cast(src); - EXPECT_EQ('Y', test.ch_); - EXPECT_EQ(42, test.number_); -} - -TEST(test_any, ctor_6_in_place_initializer_list) -{ - struct TestType : rtti_helper> - { - std::size_t size_; - int number_; - - TestType(const std::initializer_list chars, const int number) - { - size_ = chars.size(); - number_ = number; - } - }; - using any = any; - - const any src{in_place_type_t{}, {'A', 'B', 'C'}, 42}; - - auto& test = any_cast(src); - EXPECT_EQ(3, test.size_); - EXPECT_EQ(42, test.number_); -} - -TEST(test_any, assign_1_copy) -{ - // Primitive `int` - { - using any = any; - - const any src{42}; - EXPECT_TRUE(src.has_value()); - - any dst{}; - EXPECT_FALSE(dst.has_value()); - - dst = src; - EXPECT_TRUE(src.has_value()); - EXPECT_TRUE(dst.has_value()); - EXPECT_EQ(42, any_cast(dst)); - - const any src2{147}; - dst = src2; - EXPECT_EQ(147, any_cast(dst)); - - const any empty{}; - dst = empty; - EXPECT_FALSE(dst.has_value()); - } - - // Copyable only `any` - // - side_effect_stats stats; - { - using test = TestCopyableOnly; - using any = any; - - auto side_effects = stats.make_side_effect_fn(); - - const test value1{'X', side_effects}; - EXPECT_STREQ("@", stats.ops.c_str()); - - const any src1{value1}; - EXPECT_STREQ("@C", stats.ops.c_str()); - - any dst{}; - dst = src1; - EXPECT_STREQ("@CCC~", stats.ops.c_str()); - - EXPECT_EQ(10, any_cast(src1).value_); - EXPECT_EQ('X', any_cast(src1).payload_); - EXPECT_EQ(30, any_cast(dst).value_); - EXPECT_EQ('X', any_cast(dst).payload_); - - const test value2{'Z', side_effects}; - EXPECT_STREQ("@CCC~@", stats.ops.c_str()); - - const any src2{value2}; - EXPECT_STREQ("@CCC~@C", stats.ops.c_str()); - - dst = src2; - EXPECT_STREQ("@CCC~@CCC~C~C~~", stats.ops.c_str()); - - auto dst_ptr = &dst; - dst = *dst_ptr; - EXPECT_STREQ("@CCC~@CCC~C~C~~", stats.ops.c_str()); - - EXPECT_EQ(10, any_cast(src2).value_); - EXPECT_EQ('Z', any_cast(src2).payload_); - EXPECT_EQ(30, any_cast(dst).value_); - EXPECT_EQ('Z', any_cast(dst).payload_); - } - EXPECT_EQ(stats.constructs, stats.destructs); - EXPECT_STREQ("@CCC~@CCC~C~C~~~~~~~", stats.ops.c_str()); -} - -TEST(test_any, assign_2_move) -{ - // Primitive `int` - { - using any = any; - - any src{42}; - EXPECT_TRUE(src.has_value()); - - any dst{}; - EXPECT_FALSE(dst.has_value()); - - dst = std::move(src); - EXPECT_TRUE(dst.has_value()); - EXPECT_FALSE(src.has_value()); - EXPECT_EQ(42, any_cast(dst)); - - dst = any{147}; - EXPECT_EQ(147, any_cast(dst)); - - auto dst_ptr = &dst; - dst = std::move(*dst_ptr); - EXPECT_EQ(147, any_cast(dst)); - - dst = any{}; - EXPECT_FALSE(dst.has_value()); - } - - // Movable only `any` - // - side_effect_stats stats; - { - using test = TestMovableOnly; - using any = any; - - auto side_effects = stats.make_side_effect_fn(); - - any src1{test{'X', side_effects}}; - EXPECT_STREQ("@M_", stats.ops.c_str()); - - any dst{}; - dst = std::move(src1); - EXPECT_STREQ("@M_M_M_", stats.ops.c_str()); - - EXPECT_EQ(nullptr, any_cast(&src1)); - EXPECT_EQ(3, any_cast(dst).value_); - EXPECT_EQ('X', any_cast(dst).payload_); - - any src2{test{'Z', side_effects}}; - EXPECT_STREQ("@M_M_M_@M_", stats.ops.c_str()); - - dst = std::move(src2); - EXPECT_STREQ("@M_M_M_@M_M_M_M_M_~", stats.ops.c_str()); - - EXPECT_EQ(nullptr, any_cast(&src2)); - EXPECT_EQ(3, any_cast(dst).value_); - EXPECT_EQ('Z', any_cast(dst).payload_); - } - EXPECT_EQ(stats.constructs, stats.destructs); - EXPECT_STREQ("@M_M_M_@M_M_M_M_M_~~", stats.ops.c_str()); -} - -TEST(test_any, assign_3_move_value) -{ - // Primitive `int` - { - using any = any; - - any dst{}; - EXPECT_FALSE(dst.has_value()); - - dst = 147; - EXPECT_EQ(147, any_cast(dst)); - } -} - -TEST(test_any, make_any_cppref_example) -{ - using any = any))>; - - auto a0 = make_any("Hello, cetl::any!\n"); - auto a1 = make_any, any>(0.1, 2.3); - - EXPECT_STREQ("Hello, cetl::any!\n", any_cast(a0).c_str()); - EXPECT_EQ(std::complex(0.1, 2.3), any_cast>(a1)); - - using lambda = std::function; - using any_lambda = cetl::any; - - auto a3 = make_any([] { return "Lambda #3.\n"; }); - EXPECT_TRUE(a3.has_value()); - EXPECT_STREQ("Lambda #3.\n", any_cast(a3)()); -} - -TEST(test_any, make_any_1) -{ - using any = any; - - auto src = make_any(42); - EXPECT_EQ(42, any_cast(src)); - static_assert(std::is_same::value, ""); -} - -TEST(test_any, make_any_1_like) -{ - auto src = make_any(static_cast(42)); - EXPECT_EQ(42, any_cast(src)); - static_assert(std::is_same>::value, ""); - static_assert(std::is_same>::value, ""); -} - -TEST(test_any, make_any_2_list) -{ - struct TestType : rtti_helper> - { - std::size_t size_; - int number_; - - TestType(const std::initializer_list chars, const int number) - { - size_ = chars.size(); - number_ = number; - } - }; - using any = any; - - const auto src = make_any({'A', 'C'}, 42); - const auto& test = any_cast(src); - EXPECT_EQ(2, test.size_); - EXPECT_EQ(42, test.number_); - - // `cetl::any_like` version - // - const auto dst = make_any({'B', 'D', 'E'}, 147); - static_assert(std::is_same>::value, ""); - EXPECT_EQ(3, any_cast(&dst)->size_); - EXPECT_EQ(147, any_cast(dst).number_); -} - -TEST(test_any, any_cast_cppref_example) -{ - using any = any; - - auto a1 = any{12}; - EXPECT_EQ(12, any_cast(a1)); - -#if defined(__cpp_exceptions) - - EXPECT_THROW(sink(any_cast(a1)), cetl::bad_any_cast); - -#endif - - // Pointer example - EXPECT_EQ(12, *any_cast(&a1)); - EXPECT_EQ(nullptr, any_cast(&a1)); - - // Advanced example - a1 = std::string("hello"); - auto& ra = any_cast(a1); //< reference - ra[1] = 'o'; - EXPECT_STREQ("hollo", any_cast(a1).c_str()); //< const reference - - auto s1 = any_cast(std::move(a1)); //< rvalue reference - // Note: `s1` is a move-constructed std::string, `a1` is empty - static_assert(std::is_same::value, ""); - EXPECT_STREQ("hollo", s1.c_str()); -} - -TEST(test_any, any_cast_1_const) -{ - using any = any; - - const any src{42}; - -#if defined(__cpp_exceptions) - - EXPECT_THROW(sink(any_cast(src)), cetl::bad_any_cast); - - const any empty{}; - EXPECT_THROW(sink(any_cast(empty)), cetl::bad_any_cast); - -#endif - - EXPECT_EQ(42, any_cast(src)); - // EXPECT_EQ(42, any_cast(src)); //< won't compile expectedly - EXPECT_EQ(42, any_cast(src)); - EXPECT_EQ(42, any_cast(src)); -} - -TEST(test_any, any_cast_2_non_const) -{ - using any = any; - - any src{42}; - -#if defined(__cpp_exceptions) - - EXPECT_THROW(sink(any_cast(src)), cetl::bad_any_cast); - - any empty{}; - EXPECT_THROW(sink(any_cast(empty)), cetl::bad_any_cast); - -#endif - - EXPECT_EQ(42, any_cast(src)); - EXPECT_EQ(42, any_cast(src)); - EXPECT_EQ(42, any_cast(src)); - EXPECT_EQ(42, any_cast(src)); - - const auto test_str{"0123456789012345678901234567890123456789"s}; - - src = test_str; - EXPECT_STREQ(test_str.c_str(), any_cast(src).c_str()); - -#if defined(__cpp_exceptions) - - EXPECT_THROW(sink(any_cast(src)), cetl::bad_any_cast); - - src.reset(); - EXPECT_THROW(sink(any_cast(src)), cetl::bad_any_cast); - EXPECT_THROW(sink(any_cast(src)), cetl::bad_any_cast); - -#endif -} - -TEST(test_any, any_cast_3_move_primitive_int) -{ - using any = any; - - any src{147}; - EXPECT_EQ(147, any_cast(std::move(src))); - EXPECT_TRUE(src.has_value()); //< technically still "has" the value, but moved out. - - EXPECT_EQ(42, any_cast(any{42})); - // EXPECT_EQ(42, any_cast(any{42})); //< won't compile expectedly - EXPECT_EQ(42, any_cast(any{42})); - EXPECT_EQ(42, any_cast(any{42})); -} - -TEST(test_any, any_cast_3_move_empty_bad_cast) -{ -#if defined(__cpp_exceptions) - - using any = any; - - EXPECT_THROW(sink(any_cast(any{})), cetl::bad_any_cast); - - const auto test_str{"0123456789012345678901234567890123456789"s}; - - any src{test_str}; - - // Try move out but with wrong type - // - EXPECT_THROW(sink(any_cast(std::move(src))), cetl::bad_any_cast); - // - EXPECT_TRUE(src.has_value()); //< expectedly still has value b/c there was exception - EXPECT_STREQ(test_str.c_str(), any_cast(src).c_str()); - - // Retry to move out but now with correct type - // - EXPECT_STREQ(test_str.c_str(), any_cast(std::move(src)).c_str()); - // - EXPECT_TRUE(src.has_value()); //< technically still "has" the value, but moved out (hence empty). - EXPECT_TRUE(any_cast(src).empty()); - -#endif -} - -TEST(test_any, any_cast_4_const_ptr) -{ - using any = any; - - const any src{147}; - - auto int_ptr = any_cast(&src); - static_assert(std::is_same::value, ""); - - EXPECT_TRUE(int_ptr); - EXPECT_EQ(147, *int_ptr); - - auto const_int_ptr = any_cast(&src); - static_assert(std::is_same::value, ""); - EXPECT_EQ(int_ptr, const_int_ptr); - - EXPECT_EQ(nullptr, any_cast(static_cast(nullptr))); -} - -TEST(test_any, any_cast_5_non_const_ptr_with_custom_alignment) -{ - constexpr std::size_t alignment = 4096; - - using any = any; - - any src{'Y'}; - - auto char_ptr = any_cast(&src); - static_assert(std::is_same::value, ""); - EXPECT_TRUE(char_ptr); - EXPECT_EQ('Y', *char_ptr); - EXPECT_EQ(0, reinterpret_cast(char_ptr) & static_cast(alignment - 1)); - - auto const_char_ptr = any_cast(&src); - static_assert(std::is_same::value, ""); - EXPECT_EQ(char_ptr, const_char_ptr); - - EXPECT_EQ(nullptr, any_cast(static_cast(nullptr))); -} - -TEST(test_any, any_cast_polymorphic) -{ - side_effect_stats stats; - { - using any = any; - - auto side_effects = stats.make_side_effect_fn(); - - any test_any = TestCopyableAndMovable{'Y', side_effects}; - - auto& test_base1 = any_cast(test_any); - EXPECT_EQ('Y', test_base1.payload_); - EXPECT_STREQ("TestCopyableAndMovable", test_base1.what()); - EXPECT_NE(nullptr, any_cast(&test_any)); - EXPECT_EQ(nullptr, any_cast(&test_any)); - EXPECT_EQ(nullptr, any_cast(&test_any)); - - test_any = TestBase{'X', side_effects}; - - auto& test_base2 = any_cast(test_any); - EXPECT_EQ('X', test_base2.payload_); - EXPECT_STREQ("TestBase", test_base2.what()); - EXPECT_EQ(nullptr, any_cast(&test_any)); - EXPECT_EQ(nullptr, any_cast(&test_any)); - EXPECT_EQ(nullptr, any_cast(&test_any)); - } - EXPECT_EQ(stats.constructs, stats.destructs); - EXPECT_STREQ("@M_@MM_M_M_~_~", stats.ops.c_str()); -} - -TEST(test_any, swap_copyable) -{ - using test = TestCopyableOnly; - using any = any; - - any empty{}; - any a{in_place_type_t{}, 'A'}; - any b{in_place_type_t{}, 'B'}; - - // Self swap - a.swap(a); - EXPECT_EQ('A', any_cast(a).payload_); - // EXPECT_EQ(nullptr, any_cast(&a)); //< won't compile expectedly b/c footprint is smaller - - a.swap(b); - EXPECT_EQ('B', any_cast(a).payload_); - EXPECT_EQ('A', any_cast(b).payload_); - - empty.swap(a); - EXPECT_FALSE(a.has_value()); - EXPECT_EQ('B', any_cast(empty).payload_); - - empty.swap(a); - EXPECT_FALSE(empty.has_value()); - EXPECT_EQ('B', any_cast(a).payload_); - - any another_empty{}; - empty.swap(another_empty); - EXPECT_FALSE(empty.has_value()); - EXPECT_FALSE(another_empty.has_value()); -} - -TEST(test_any, swap_movable) -{ - using test = TestMovableOnly; - using any = any; - - any empty{}; - any a{in_place_type_t{}, 'A'}; - any b{in_place_type_t{}, 'B'}; - - // Self swap - a.swap(a); - EXPECT_TRUE(a.has_value()); - EXPECT_FALSE(any_cast(a).moved_); - EXPECT_EQ('A', any_cast(a).payload_); - - a.swap(b); - EXPECT_TRUE(a.has_value()); - EXPECT_TRUE(b.has_value()); - EXPECT_FALSE(any_cast(a).moved_); - EXPECT_FALSE(any_cast(b).moved_); - EXPECT_EQ('B', any_cast(a).payload_); - EXPECT_EQ('A', any_cast(b).payload_); - - empty.swap(a); - EXPECT_FALSE(a.has_value()); - EXPECT_TRUE(empty.has_value()); - EXPECT_FALSE(any_cast(empty).moved_); - EXPECT_EQ('B', any_cast(empty).payload_); - - empty.swap(a); - EXPECT_TRUE(a.has_value()); - EXPECT_FALSE(empty.has_value()); - EXPECT_FALSE(any_cast(a).moved_); - EXPECT_EQ('B', any_cast(a).payload_); - - any another_empty{}; - empty.swap(another_empty); - EXPECT_FALSE(empty.has_value()); - EXPECT_FALSE(another_empty.has_value()); -} - -TEST(test_any, emplace_1) -{ - // Primitive `char` - { - using any = any; - - any src; - src.emplace('Y'); - EXPECT_EQ('Y', any_cast(src)); - } - - // `TestType` with two params ctor. - { - struct TestType : rtti_helper> - { - char ch_; - int number_; - - TestType(char ch, int number) - { - ch_ = ch; - number_ = number; - } - }; - using any = any; - - any t; - t.emplace('Y', 147); - EXPECT_EQ('Y', any_cast(t).ch_); - EXPECT_EQ(147, any_cast(t).number_); - } -} - -TEST(test_any, emplace_2_initializer_list) -{ - struct TestType : rtti_helper> - { - std::size_t size_; - int number_; - - TestType(const std::initializer_list chars, const int number) - { - size_ = chars.size(); - number_ = number; - } - }; - using any = any; - - any src; - src.emplace({'A', 'B', 'C'}, 42); - - const auto test = any_cast(src); - EXPECT_EQ(3, test.size_); - EXPECT_EQ(42, test.number_); -} - -} // namespace - -namespace cetl -{ -template <> -constexpr type_id type_id_value = {1}; - -template <> -constexpr type_id type_id_value = {2}; - -template <> -constexpr type_id type_id_value = {3}; - -template <> -constexpr type_id type_id_value = {4}; - -template <> -constexpr type_id type_id_value = {5}; - -template <> -constexpr type_id type_id_value = {6}; - -template <> -constexpr type_id type_id_value = {7}; - -template <> -constexpr type_id type_id_value> = - {0xB3, 0xB8, 0x4E, 0xC1, 0x1F, 0xE4, 0x49, 0x35, 0x9E, 0xC9, 0x1A, 0x77, 0x7B, 0x82, 0x53, 0x25}; - -template <> -constexpr type_id type_id_value> = {8}; - -template <> -constexpr type_id type_id_value> = {9}; - -} // namespace cetl - -// NOLINTEND(*-use-after-move) diff --git a/cetlvast/suites/unittest/test_pf17_any.cpp b/cetlvast/suites/unittest/test_pf17_any.cpp deleted file mode 100644 index 6b6237d..0000000 --- a/cetlvast/suites/unittest/test_pf17_any.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/// @file -/// Unit tests for cetl/pf17/any.hpp -/// -/// @copyright -/// Copyright (C) OpenCyphal Development Team -/// Copyright Amazon.com Inc. or its affiliates. -/// SPDX-License-Identifier: MIT - -#include - -#include - -namespace -{ - -/// TESTS ----------------------------------------------------------------------------------------------------------- - -TEST(test_bad_any_cast, ctor) -{ -#if defined(__cpp_exceptions) - - // Test the default constructor. - cetl::bad_any_cast test_exception1; - - // Test the copy constructor. - cetl::bad_any_cast test_exception2{test_exception1}; - - // Test the move constructor. - cetl::bad_any_cast test_exception3{std::move(test_exception2)}; - EXPECT_STRNE("", test_exception3.what()); - -#else - GTEST_SKIP() << "Not applicable when exceptions are disabled."; -#endif -} - -TEST(test_bad_any_cast, assignment) -{ -#if defined(__cpp_exceptions) - - // Test the copy assignment operator. - cetl::bad_any_cast test_exception1; - cetl::bad_any_cast test_exception2; - test_exception2 = test_exception1; - - // Test the move assignment operator. - cetl::bad_any_cast test_exception3; - test_exception3 = std::move(test_exception2); - EXPECT_STRNE("", test_exception3.what()); - -#else - GTEST_SKIP() << "Not applicable when exceptions are disabled."; -#endif -} - -} // namespace diff --git a/cetlvast/suites/unittest/test_unbounded_variant.cpp b/cetlvast/suites/unittest/test_unbounded_variant.cpp new file mode 100644 index 0000000..02a8b25 --- /dev/null +++ b/cetlvast/suites/unittest/test_unbounded_variant.cpp @@ -0,0 +1,1136 @@ +/// @file +/// Unit tests for cetl/unbounded_variant.hpp +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#include + +#include +#include +#include +#include + +// NOLINTBEGIN(*-use-after-move) + +namespace +{ + +using cetl::unbounded_variant; +using cetl::get; +using cetl::get_if; +using cetl::make_unbounded_variant; +using cetl::in_place_type_t; +using cetl::type_id; +using cetl::type_id_type; +using cetl::rtti_helper; + +using testing::IsNull; +using testing::NotNull; + +using namespace std::string_literals; + +/// HELPERS --------------------------------------------------------------------------------------------------------- + +#if defined(__cpp_exceptions) + +// Workaround for GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425 +// Should be used in the tests where exceptions are expected (see `EXPECT_THROW`). +const auto sink = [](auto&&) {}; + +#endif + +enum class side_effect_op : char +{ + Construct = '@', + CopyConstruct = 'C', + MoveConstruct = 'M', + CopyAssign = '=', + MoveAssign = '<', + Destruct = '~', + DestructMoved = '_', +}; +using side_effect_fn = std::function; + +struct side_effect_stats +{ + std::string ops; + int assignments = 0; + int constructs = 0; + int destructs = 0; + + auto make_side_effect_fn() + { + return [this](side_effect_op op) { + ops += static_cast(op); + + constructs += (op == side_effect_op::Construct) ? 1 : 0; + constructs += (op == side_effect_op::CopyConstruct) ? 1 : 0; + constructs += (op == side_effect_op::MoveConstruct) ? 1 : 0; + + assignments += (op == side_effect_op::CopyAssign) ? 1 : 0; + assignments += (op == side_effect_op::MoveAssign) ? 1 : 0; + + destructs += (op == side_effect_op::Destruct) ? 1 : 0; + destructs += (op == side_effect_op::DestructMoved) ? 1 : 0; + }; + } +}; + +struct TestBase : rtti_helper> +{ + char payload_; + int value_ = 0; + bool moved_ = false; + + TestBase(const char payload, side_effect_fn side_effect) + : payload_(payload) + , side_effect_(std::move(side_effect)) + { + side_effect_(side_effect_op::Construct); + } + TestBase(const TestBase& other) + { + copy_from(other, side_effect_op::CopyConstruct); + } + TestBase(TestBase&& other) noexcept + { + move_from(other, side_effect_op::MoveConstruct); + } + + ~TestBase() override + { + side_effect_(moved_ ? side_effect_op::DestructMoved : side_effect_op::Destruct); + } + + TestBase& operator=(const TestBase& other) + { + copy_from(other, side_effect_op::CopyAssign); + return *this; + } + + TestBase& operator=(TestBase&& other) noexcept + { + move_from(other, side_effect_op::MoveAssign); + return *this; + } + + CETL_NODISCARD virtual const char* what() const noexcept + { + return "TestBase"; + } + +private: + side_effect_fn side_effect_; + + void copy_from(const TestBase& other, const side_effect_op op) + { + payload_ = other.payload_; + side_effect_ = other.side_effect_; + value_ = other.value_ + 10; + + side_effect_(op); + } + + void move_from(TestBase& other, const side_effect_op op) + { + payload_ = other.payload_; + side_effect_ = other.side_effect_; + value_ = other.value_ + 1; + + other.moved_ = true; + other.payload_ = '\0'; + + side_effect_(op); + } + +}; // TestBase + +struct TestCopyableOnly final : TestBase +{ + explicit TestCopyableOnly( + const char payload = '?', + side_effect_fn side_effect = [](auto) {}) + : TestBase(payload, std::move(side_effect)) + { + } + TestCopyableOnly(const TestCopyableOnly& other) = default; + TestCopyableOnly(TestCopyableOnly&& other) noexcept = delete; + + TestCopyableOnly& operator=(const TestCopyableOnly& other) = default; + TestCopyableOnly& operator=(TestCopyableOnly&& other) noexcept = delete; + + CETL_NODISCARD const char* what() const noexcept override + { + return "TestCopyableOnly"; + } + + // rtti + + static constexpr type_id _get_type_id_() noexcept + { + return {0x0, 0b01}; + } + + CETL_NODISCARD void* _cast_(const type_id& id) & noexcept override + { + return (id == _get_type_id_()) ? static_cast(this) : base::_cast_(id); + } + CETL_NODISCARD const void* _cast_(const type_id& id) const& noexcept override + { + return (id == _get_type_id_()) ? static_cast(this) : base::_cast_(id); + } + +private: + using base = TestBase; +}; + +struct TestMovableOnly final : TestBase +{ + explicit TestMovableOnly( + const char payload = '?', + side_effect_fn side_effect = [](auto) {}) + : TestBase(payload, std::move(side_effect)) + { + } + TestMovableOnly(const TestMovableOnly& other) = delete; + TestMovableOnly(TestMovableOnly&& other) noexcept = default; + + TestMovableOnly& operator=(const TestMovableOnly& other) = delete; + TestMovableOnly& operator=(TestMovableOnly&& other) noexcept = default; + + CETL_NODISCARD const char* what() const noexcept override + { + return "TestMovableOnly"; + } + + // rtti + + static constexpr type_id _get_type_id_() noexcept + { + return {0x0, 0b10}; + } + + CETL_NODISCARD void* _cast_(const type_id& id) & noexcept override + { + return (id == _get_type_id_()) ? static_cast(this) : base::_cast_(id); + } + CETL_NODISCARD const void* _cast_(const type_id& id) const& noexcept override + { + return (id == _get_type_id_()) ? static_cast(this) : base::_cast_(id); + } + +private: + using base = TestBase; +}; + +struct TestCopyableAndMovable final : TestBase +{ + // Just to make this class a bit bigger than base. + char place_holder_; + + explicit TestCopyableAndMovable( + const char payload = '?', + side_effect_fn side_effect = [](auto) {}) + : TestBase(payload, std::move(side_effect)) + , place_holder_{payload} + { + } + + CETL_NODISCARD const char* what() const noexcept override + { + return "TestCopyableAndMovable"; + } + + // rtti + + static constexpr type_id _get_type_id_() noexcept + { + return {0x0, 0b11}; + } + + CETL_NODISCARD void* _cast_(const type_id& id) & noexcept override + { + return (id == _get_type_id_()) ? static_cast(this) : base::_cast_(id); + } + CETL_NODISCARD const void* _cast_(const type_id& id) const& noexcept override + { + return (id == _get_type_id_()) ? static_cast(this) : base::_cast_(id); + } + +private: + using base = TestBase; +}; + +/// TESTS ----------------------------------------------------------------------------------------------------------- + +TEST(test_unbounded_variant, bad_unbounded_variant_access_ctor) +{ +#if defined(__cpp_exceptions) + + // Test the default constructor. + cetl::bad_unbounded_variant_access test_exception1; + + // Test the copy constructor. + cetl::bad_unbounded_variant_access test_exception2{test_exception1}; + + // Test the move constructor. + cetl::bad_unbounded_variant_access test_exception3{std::move(test_exception2)}; + EXPECT_THAT(test_exception3.what(), "bad unbounded variant access"); + +#else + GTEST_SKIP() << "Not applicable when exceptions are disabled."; +#endif +} + +TEST(test_unbounded_variant, bad_unbounded_variant_access_assignment) +{ +#if defined(__cpp_exceptions) + + // Test the copy assignment operator. + cetl::bad_unbounded_variant_access test_exception1; + cetl::bad_unbounded_variant_access test_exception2; + test_exception2 = test_exception1; + + // Test the move assignment operator. + cetl::bad_unbounded_variant_access test_exception3; + test_exception3 = std::move(test_exception2); + EXPECT_THAT(test_exception3.what(), "bad unbounded variant access"); + +#else + GTEST_SKIP() << "Not applicable when exceptions are disabled."; +#endif +} + +TEST(test_unbounded_variant, cppref_example) +{ + using ub_var = unbounded_variant; + + ub_var a = 1; + EXPECT_THAT(get(a), 1); + + a = 3.14; + EXPECT_THAT(get(a), 3.14); + + a = true; + EXPECT_TRUE(get(a)); + + // bad cast + a = 1; +#if defined(__cpp_exceptions) + EXPECT_THROW(sink(get(a)), cetl::bad_unbounded_variant_access); +#else + EXPECT_THAT(get_if(&a), IsNull()); +#endif + + a = 2; + EXPECT_TRUE(a.has_value()); + + // reset + a.reset(); + ASSERT_FALSE(a.has_value()); + + // pointer to contained data + a = 3; + EXPECT_THAT(*get_if(&a), 3); +} + +TEST(test_unbounded_variant, ctor_1_default) +{ + EXPECT_FALSE((unbounded_variant<0>{}.has_value())); + EXPECT_FALSE((unbounded_variant<0, false>{}.has_value())); + EXPECT_FALSE((unbounded_variant<0, false, true>{}.has_value())); + EXPECT_FALSE((unbounded_variant<0, true, false>{}.has_value())); + EXPECT_FALSE((unbounded_variant<0, true, true, 1>{}.has_value())); + + EXPECT_FALSE((unbounded_variant<1>{}.has_value())); + EXPECT_FALSE((unbounded_variant<1, false>{}.has_value())); + EXPECT_FALSE((unbounded_variant<1, false, true>{}.has_value())); + EXPECT_FALSE((unbounded_variant<1, true, false>{}.has_value())); + EXPECT_FALSE((unbounded_variant<1, true, true, 1>{}.has_value())); + + EXPECT_FALSE((unbounded_variant<13>{}.has_value())); + EXPECT_FALSE((unbounded_variant<13, false>{}.has_value())); + EXPECT_FALSE((unbounded_variant<13, false, true>{}.has_value())); + EXPECT_FALSE((unbounded_variant<13, true, false>{}.has_value())); + EXPECT_FALSE((unbounded_variant<13, true, true, 1>{}.has_value())); +} + +TEST(test_unbounded_variant, ctor_2_copy) +{ + // Primitive `int` + { + using ub_var = unbounded_variant; + + const ub_var src{42}; + ub_var dst{src}; + + EXPECT_THAT(get(src), 42); + EXPECT_THAT(get(dst), 42); + } + + // Copyable and Movable `unbounded_variant` + { + using test = TestCopyableAndMovable; + using ub_var = unbounded_variant; + + const ub_var src{test{}}; + ub_var dst{src}; + + EXPECT_THAT(get(src).value_, 1 + 10); + EXPECT_THAT(get(src).value_, 1); + + EXPECT_THAT(get(dst).value_, 1 + 10 + 10); + EXPECT_THAT(get(dst).value_, 1 + 10); + EXPECT_THAT(get(dst).value_, 1 + 10); + + EXPECT_FALSE(get(dst).moved_); + EXPECT_THAT(get(std::move(dst)).value_, 1 + 10 + 1); + EXPECT_TRUE(get(dst).moved_); + } + + // Copyable only `unbounded_variant` + { + using test = TestCopyableOnly; + using ub_var = unbounded_variant; + + const test value{}; + ub_var src{value}; + const ub_var dst{src}; + + EXPECT_THAT(get(src).value_, 10); + EXPECT_THAT(get(src).value_, 10); + + EXPECT_THAT(get(dst).value_, 10 + 10); + } + + // Movable only `unbounded_variant` + { + using test = TestMovableOnly; + using ub_var = unbounded_variant; + + test value{'X'}; + EXPECT_FALSE(value.moved_); + EXPECT_THAT(value.payload_, 'X'); + + test value2{std::move(value)}; + EXPECT_TRUE(value.moved_); + EXPECT_THAT(value.payload_, '\0'); + EXPECT_FALSE(value2.moved_); + EXPECT_THAT(value2.value_, 1); + EXPECT_THAT(value2.payload_, 'X'); + // ub_var src{value}; //< expectedly won't compile (due to !copyable `test`) + ub_var src{std::move(value2)}; + EXPECT_THAT(get(src).payload_, 'X'); + // const ub_var dst{src}; //< expectedly won't compile (due to !copyable `unbounded_variant`) + } + + // Non-Copyable and non-movable `unbounded_variant` + { + using test = TestCopyableAndMovable; + using ub_var = unbounded_variant; + + ub_var src{test{}}; + EXPECT_THAT(get(src).value_, 1 + 10); + EXPECT_THAT(get(src).value_, 1); + EXPECT_THAT(get(std::move(src)).value_, 1 + 1); + // const ub_var dst{src}; //< expectedly won't compile (due to !copyable `unbounded_variant`) + // ub_var dst{std::move(src)}; //< expectedly won't compile (due to !movable `unbounded_variant`) + } +} + +TEST(test_unbounded_variant, ctor_3_move) +{ + // Primitive `int` + { + using ub_var = unbounded_variant; + + ub_var src{42}; + const ub_var dst{std::move(src)}; + + EXPECT_FALSE(src.has_value()); + EXPECT_THAT(get(dst), 42); + } + + // Copyable and Movable `unbounded_variant` + { + using test = TestCopyableAndMovable; + using ub_var = unbounded_variant; + + ub_var src{test{}}; + EXPECT_TRUE(src.has_value()); + + const ub_var dst{std::move(src)}; + EXPECT_TRUE(dst.has_value()); + EXPECT_FALSE(src.has_value()); + EXPECT_THAT(get(dst).value_, 2); + } + + // Movable only `unbounded_variant` + { + using test = TestMovableOnly; + using ub_var = unbounded_variant; + + ub_var src{test{'X'}}; + const ub_var dst{std::move(src)}; + + EXPECT_THAT(get_if(&src), IsNull()); + EXPECT_THAT(get(dst).value_, 2); + EXPECT_THAT(get(dst).payload_, 'X'); + // EXPECT_THAT(get_if(dst).value_, 2); //< expectedly won't compile (due to !copyable) + // EXPECT_THAT(get_if(dst).value_, 2); //< expectedly won't compile (due to const) + } + + // Copyable only `unbounded_variant`, movable only `unique_ptr` + { + using test = std::unique_ptr; + using ub_var = unbounded_variant; + + ub_var src{std::make_unique()}; + ub_var dst{std::move(src)}; + EXPECT_FALSE(src.has_value()); + + auto ptr = get(std::move(dst)); + EXPECT_TRUE(ptr); + EXPECT_THAT(ptr->value_, 0); + } +} + +TEST(test_unbounded_variant, ctor_4_move_value) +{ + using test = TestCopyableAndMovable; + using ub_var = unbounded_variant; + + test value{'Y'}; + const ub_var dst{std::move(value)}; + EXPECT_TRUE(value.moved_); + EXPECT_TRUE(dst.has_value()); + EXPECT_THAT(get(dst).value_, 1); + EXPECT_THAT(get(dst).payload_, 'Y'); +} + +TEST(test_unbounded_variant, ctor_5_in_place) +{ + struct TestType : rtti_helper> + { + char ch_; + int number_; + + TestType(const char ch, const int number) + { + ch_ = ch; + number_ = number; + } + }; + using ub_var = unbounded_variant; + + const ub_var src{in_place_type_t{}, 'Y', 42}; + + const auto test = get(src); + EXPECT_THAT(test.ch_, 'Y'); + EXPECT_THAT(test.number_, 42); +} + +TEST(test_unbounded_variant, ctor_6_in_place_initializer_list) +{ + struct TestType : rtti_helper> + { + std::size_t size_; + int number_; + + TestType(const std::initializer_list chars, const int number) + { + size_ = chars.size(); + number_ = number; + } + }; + using ub_var = unbounded_variant; + + const ub_var src{in_place_type_t{}, {'A', 'B', 'C'}, 42}; + + auto& test = get(src); + EXPECT_THAT(test.size_, 3); + EXPECT_THAT(test.number_, 42); +} + +TEST(test_unbounded_variant, assign_1_copy) +{ + // Primitive `int` + { + using ub_var = unbounded_variant; + + const ub_var src{42}; + EXPECT_TRUE(src.has_value()); + + ub_var dst{}; + EXPECT_FALSE(dst.has_value()); + + dst = src; + EXPECT_TRUE(src.has_value()); + EXPECT_TRUE(dst.has_value()); + EXPECT_THAT(get(dst), 42); + + const ub_var src2{147}; + dst = src2; + EXPECT_THAT(get(dst), 147); + + const ub_var empty{}; + dst = empty; + EXPECT_FALSE(dst.has_value()); + } + + // Copyable only `unbounded_variant` + // + side_effect_stats stats; + { + using test = TestCopyableOnly; + using ub_var = unbounded_variant; + + auto side_effects = stats.make_side_effect_fn(); + + const test value1{'X', side_effects}; + EXPECT_THAT(stats.ops, "@"); + + const ub_var src1{value1}; + EXPECT_THAT(stats.ops, "@C"); + + ub_var dst{}; + dst = src1; + EXPECT_THAT(stats.ops, "@CCC~"); + + EXPECT_THAT(get(src1).value_, 10); + EXPECT_THAT(get(src1).payload_, 'X'); + EXPECT_THAT(get(dst).value_, 30); + EXPECT_THAT(get(dst).payload_, 'X'); + + const test value2{'Z', side_effects}; + EXPECT_THAT(stats.ops, "@CCC~@"); + + const ub_var src2{value2}; + EXPECT_THAT(stats.ops, "@CCC~@C"); + + dst = src2; + EXPECT_THAT(stats.ops, "@CCC~@CCC~C~C~~"); + + auto dst_ptr = &dst; + dst = *dst_ptr; + EXPECT_THAT(stats.ops, "@CCC~@CCC~C~C~~"); + + EXPECT_THAT(get(src2).value_, 10); + EXPECT_THAT(get(src2).payload_, 'Z'); + EXPECT_THAT(get(dst).value_, 30); + EXPECT_THAT(get(dst).payload_, 'Z'); + } + EXPECT_THAT(stats.constructs, stats.destructs); + EXPECT_THAT(stats.ops, "@CCC~@CCC~C~C~~~~~~~"); +} + +TEST(test_unbounded_variant, assign_2_move) +{ + // Primitive `int` + { + using ub_var = unbounded_variant; + + ub_var src{42}; + EXPECT_TRUE(src.has_value()); + + ub_var dst{}; + EXPECT_FALSE(dst.has_value()); + + dst = std::move(src); + EXPECT_TRUE(dst.has_value()); + EXPECT_FALSE(src.has_value()); + EXPECT_THAT(get(dst), 42); + + dst = ub_var{147}; + EXPECT_THAT(get(dst), 147); + + auto dst_ptr = &dst; + dst = std::move(*dst_ptr); + EXPECT_THAT(get(dst), 147); + + dst = ub_var{}; + EXPECT_FALSE(dst.has_value()); + } + + // Movable only `unbounded_variant` + // + side_effect_stats stats; + { + using test = TestMovableOnly; + using ub_var = unbounded_variant; + + auto side_effects = stats.make_side_effect_fn(); + + ub_var src1{test{'X', side_effects}}; + EXPECT_THAT(stats.ops, "@M_"); + + ub_var dst{}; + dst = std::move(src1); + EXPECT_THAT(stats.ops, "@M_M_M_"); + + EXPECT_THAT(get_if(&src1), IsNull()); + EXPECT_THAT(get(dst).value_, 3); + EXPECT_THAT(get(dst).payload_, 'X'); + + ub_var src2{test{'Z', side_effects}}; + EXPECT_THAT(stats.ops, "@M_M_M_@M_"); + + dst = std::move(src2); + EXPECT_THAT(stats.ops, "@M_M_M_@M_M_M_M_M_~"); + + EXPECT_THAT(get_if(&src2), IsNull()); + EXPECT_THAT(get(dst).value_, 3); + EXPECT_THAT(get(dst).payload_, 'Z'); + } + EXPECT_THAT(stats.constructs, stats.destructs); + EXPECT_THAT(stats.ops, "@M_M_M_@M_M_M_M_M_~~"); +} + +TEST(test_unbounded_variant, assign_3_move_value) +{ + // Primitive `int` + { + using ub_var = unbounded_variant; + + ub_var dst{}; + EXPECT_FALSE(dst.has_value()); + + dst = 147; + EXPECT_THAT(get(dst), 147); + } +} + +TEST(test_unbounded_variant, make_unbounded_variant_cppref_example) +{ + using ub_var = unbounded_variant))>; + + auto a0 = make_unbounded_variant("Hello, cetl::unbounded_variant!\n"); + auto a1 = make_unbounded_variant, ub_var>(0.1, 2.3); + + EXPECT_THAT(get(a0), "Hello, cetl::unbounded_variant!\n"); + EXPECT_THAT(get>(a1), std::complex(0.1, 2.3)); + + using lambda = std::function; + using ub_var_lambda = cetl::unbounded_variant; + + auto a3 = make_unbounded_variant([] { return "Lambda #3.\n"; }); + EXPECT_TRUE(a3.has_value()); + EXPECT_THAT(get(a3)(), "Lambda #3.\n"); +} + +TEST(test_unbounded_variant, make_unbounded_variant_1) +{ + using ub_var = unbounded_variant; + + auto src = make_unbounded_variant(42); + EXPECT_THAT(get(src), 42); + static_assert(std::is_same::value, ""); +} + +TEST(test_unbounded_variant, make_unbounded_variant_1_like) +{ + auto src = make_unbounded_variant(static_cast(42)); + EXPECT_THAT(get(src), 42); + static_assert(std::is_same>::value, ""); + static_assert(std::is_same>::value, + ""); +} + +TEST(test_unbounded_variant, make_unbounded_variant_2_list) +{ + struct TestType : rtti_helper> + { + std::size_t size_; + int number_; + + TestType(const std::initializer_list chars, const int number) + { + size_ = chars.size(); + number_ = number; + } + }; + using ub_var = unbounded_variant; + + const auto src = make_unbounded_variant({'A', 'C'}, 42); + const auto& test = get(src); + EXPECT_THAT(test.size_, 2); + EXPECT_THAT(test.number_, 42); + + // `cetl::unbounded_variant_like` version + // + const auto dst = make_unbounded_variant({'B', 'D', 'E'}, 147); + static_assert(std::is_same>::value, ""); + EXPECT_THAT(get_if(&dst)->size_, 3); + EXPECT_THAT(get(dst).number_, 147); +} + +TEST(test_unbounded_variant, get_cppref_example) +{ + using ub_var = unbounded_variant; + + auto a1 = ub_var{12}; + EXPECT_THAT(get(a1), 12); + +#if defined(__cpp_exceptions) + + EXPECT_THROW(sink(get(a1)), cetl::bad_unbounded_variant_access); + +#endif + + // Pointer example + EXPECT_THAT(*get_if(&a1), 12); + EXPECT_THAT(get_if(&a1), IsNull()); + + // Advanced example + a1 = std::string("hello"); + auto& ra = get(a1); //< reference + ra[1] = 'o'; + EXPECT_THAT(get(a1), "hollo"); //< const reference + + auto s1 = get(std::move(a1)); //< rvalue reference + // Note: `s1` is a move-constructed std::string, `a1` is empty + static_assert(std::is_same::value, ""); + EXPECT_THAT(s1, "hollo"); +} + +TEST(test_unbounded_variant, get_1_const) +{ + using ub_var = unbounded_variant; + + const ub_var src{42}; + +#if defined(__cpp_exceptions) + + EXPECT_THROW(sink(get(src)), cetl::bad_unbounded_variant_access); + + const ub_var empty{}; + EXPECT_THROW(sink(get(empty)), cetl::bad_unbounded_variant_access); + +#endif + + EXPECT_THAT(get(src), 42); + // EXPECT_THAT(42, get(src)); //< won't compile expectedly + EXPECT_THAT(get(src), 42); + EXPECT_THAT(get(src), 42); +} + +TEST(test_unbounded_variant, get_2_non_const) +{ + using ub_var = unbounded_variant; + + ub_var src{42}; + +#if defined(__cpp_exceptions) + + EXPECT_THROW(sink(get(src)), cetl::bad_unbounded_variant_access); + + ub_var empty{}; + EXPECT_THROW(sink(get(empty)), cetl::bad_unbounded_variant_access); + +#endif + + EXPECT_THAT(get(src), 42); + EXPECT_THAT(get(src), 42); + EXPECT_THAT(get(src), 42); + EXPECT_THAT(get(src), 42); + + const auto test_str = "0123456789012345678901234567890123456789"s; + + src = test_str; + EXPECT_THAT(get(src), test_str); + +#if defined(__cpp_exceptions) + + EXPECT_THROW(sink(get(src)), cetl::bad_unbounded_variant_access); + + src.reset(); + EXPECT_THROW(sink(get(src)), cetl::bad_unbounded_variant_access); + EXPECT_THROW(sink(get(src)), cetl::bad_unbounded_variant_access); + +#endif +} + +TEST(test_unbounded_variant, get_3_move_primitive_int) +{ + using ub_var = unbounded_variant; + + ub_var src{147}; + EXPECT_THAT(get(std::move(src)), 147); + EXPECT_TRUE(src.has_value()); //< technically still "has" the value, but moved out. + + EXPECT_THAT(get(ub_var{42}), 42); + // EXPECT_THAT(get(ub_var{42}), 42); //< won't compile expectedly + EXPECT_THAT(get(ub_var{42}), 42); + EXPECT_THAT(get(ub_var{42}), 42); +} + +TEST(test_unbounded_variant, get_3_move_empty_bad_cast) +{ +#if defined(__cpp_exceptions) + + using ub_var = unbounded_variant; + + EXPECT_THROW(sink(get(ub_var{})), cetl::bad_unbounded_variant_access); + + const auto test_str = "0123456789012345678901234567890123456789"s; + + ub_var src{test_str}; + + // Try to move out but with wrong type + // + EXPECT_THROW(sink(get(std::move(src))), cetl::bad_unbounded_variant_access); + // + EXPECT_TRUE(src.has_value()); //< expectedly still has value b/c there was exception + EXPECT_THAT(get(src), test_str); + + // Retry to move out but now with correct type + // + EXPECT_THAT(get(std::move(src)), test_str); + // + EXPECT_TRUE(src.has_value()); //< technically still "has" the value, but moved out (hence empty). + EXPECT_TRUE(get(src).empty()); + +#endif +} + +TEST(test_unbounded_variant, get_if_4_const_ptr) +{ + using ub_var = unbounded_variant; + + const ub_var src{147}; + + auto int_ptr = get_if(&src); + static_assert(std::is_same::value, ""); + + EXPECT_TRUE(int_ptr); + EXPECT_THAT(*int_ptr, 147); + + auto const_int_ptr = get_if(&src); + static_assert(std::is_same::value, ""); + EXPECT_THAT(int_ptr, const_int_ptr); + + EXPECT_THAT(get_if(static_cast(nullptr)), IsNull()); +} + +TEST(test_unbounded_variant, get_if_5_non_const_ptr_with_custom_alignment) +{ + constexpr std::size_t alignment = 4096; + + using ub_var = unbounded_variant; + + ub_var src{'Y'}; + + auto char_ptr = get_if(&src); + static_assert(std::is_same::value, ""); + EXPECT_TRUE(char_ptr); + EXPECT_THAT(*char_ptr, 'Y'); + EXPECT_THAT(reinterpret_cast(char_ptr) & static_cast(alignment - 1), 0); + + auto const_char_ptr = get_if(&src); + static_assert(std::is_same::value, ""); + EXPECT_THAT(char_ptr, const_char_ptr); + + EXPECT_THAT(get_if(static_cast(nullptr)), IsNull()); +} + +TEST(test_unbounded_variant, get_if_polymorphic) +{ + side_effect_stats stats; + { + using ub_var = unbounded_variant; + + auto side_effects = stats.make_side_effect_fn(); + + ub_var test_ubv = TestCopyableAndMovable{'Y', side_effects}; + + auto& test_base1 = get(test_ubv); + EXPECT_THAT(test_base1.payload_, 'Y'); + EXPECT_THAT(test_base1.what(), "TestCopyableAndMovable"); + EXPECT_THAT(get_if(&test_ubv), NotNull()); + EXPECT_THAT(get_if(&test_ubv), IsNull()); + EXPECT_THAT(get_if(&test_ubv), IsNull()); + + test_ubv = TestBase{'X', side_effects}; + + auto& test_base2 = get(test_ubv); + EXPECT_THAT(test_base2.payload_, 'X'); + EXPECT_THAT(test_base2.what(), "TestBase"); + EXPECT_THAT(get_if(&test_ubv), IsNull()); + EXPECT_THAT(get_if(&test_ubv), IsNull()); + EXPECT_THAT(get_if(&test_ubv), IsNull()); + } + EXPECT_THAT(stats.constructs, stats.destructs); + EXPECT_THAT(stats.ops, "@M_@MM_M_M_~_~"); +} + +TEST(test_unbounded_variant, swap_copyable) +{ + using test = TestCopyableOnly; + using ub_var = unbounded_variant; + + ub_var empty{}; + ub_var a{in_place_type_t{}, 'A'}; + ub_var b{in_place_type_t{}, 'B'}; + + // Self swap + a.swap(a); + EXPECT_THAT(get(a).payload_, 'A'); + // EXPECT_THAT(get(&a), IsNull); //< won't compile expectedly b/c footprint is smaller + + a.swap(b); + EXPECT_THAT(get(a).payload_, 'B'); + EXPECT_THAT(get(b).payload_, 'A'); + + empty.swap(a); + EXPECT_FALSE(a.has_value()); + EXPECT_THAT(get(empty).payload_, 'B'); + + empty.swap(a); + EXPECT_FALSE(empty.has_value()); + EXPECT_THAT(get(a).payload_, 'B'); + + ub_var another_empty{}; + empty.swap(another_empty); + EXPECT_FALSE(empty.has_value()); + EXPECT_FALSE(another_empty.has_value()); +} + +TEST(test_unbounded_variant, swap_movable) +{ + using test = TestMovableOnly; + using ub_var = unbounded_variant; + + ub_var empty{}; + ub_var a{in_place_type_t{}, 'A'}; + ub_var b{in_place_type_t{}, 'B'}; + + // Self swap + a.swap(a); + EXPECT_TRUE(a.has_value()); + EXPECT_FALSE(get(a).moved_); + EXPECT_THAT(get(a).payload_, 'A'); + + a.swap(b); + EXPECT_TRUE(a.has_value()); + EXPECT_TRUE(b.has_value()); + EXPECT_FALSE(get(a).moved_); + EXPECT_FALSE(get(b).moved_); + EXPECT_THAT(get(a).payload_, 'B'); + EXPECT_THAT(get(b).payload_, 'A'); + + empty.swap(a); + EXPECT_FALSE(a.has_value()); + EXPECT_TRUE(empty.has_value()); + EXPECT_FALSE(get(empty).moved_); + EXPECT_THAT(get(empty).payload_, 'B'); + + empty.swap(a); + EXPECT_TRUE(a.has_value()); + EXPECT_FALSE(empty.has_value()); + EXPECT_FALSE(get(a).moved_); + EXPECT_THAT(get(a).payload_, 'B'); + + ub_var another_empty{}; + empty.swap(another_empty); + EXPECT_FALSE(empty.has_value()); + EXPECT_FALSE(another_empty.has_value()); +} + +TEST(test_unbounded_variant, emplace_1) +{ + // Primitive `char` + { + using ub_var = unbounded_variant; + + ub_var src; + src.emplace('Y'); + EXPECT_THAT(get(src), 'Y'); + } + + // `TestType` with two params ctor. + { + struct TestType : rtti_helper> + { + char ch_; + int number_; + + TestType(char ch, int number) + { + ch_ = ch; + number_ = number; + } + }; + using ub_var = unbounded_variant; + + ub_var t; + t.emplace('Y', 147); + EXPECT_THAT(get(t).ch_, 'Y'); + EXPECT_THAT(get(t).number_, 147); + } +} + +TEST(test_unbounded_variant, emplace_2_initializer_list) +{ + struct TestType : rtti_helper> + { + std::size_t size_; + int number_; + + TestType(const std::initializer_list chars, const int number) + { + size_ = chars.size(); + number_ = number; + } + }; + using ub_var = unbounded_variant; + + ub_var src; + src.emplace({'A', 'B', 'C'}, 42); + + const auto test = get(src); + EXPECT_THAT(test.size_, 3); + EXPECT_THAT(test.number_, 42); +} + +} // namespace + +namespace cetl +{ +template <> +constexpr type_id type_id_value = {1}; + +template <> +constexpr type_id type_id_value = {2}; + +template <> +constexpr type_id type_id_value = {3}; + +template <> +constexpr type_id type_id_value = {4}; + +template <> +constexpr type_id type_id_value = {5}; + +template <> +constexpr type_id type_id_value = {6}; + +template <> +constexpr type_id type_id_value = {7}; + +template <> +constexpr type_id type_id_value> = + {0xB3, 0xB8, 0x4E, 0xC1, 0x1F, 0xE4, 0x49, 0x35, 0x9E, 0xC9, 0x1A, 0x77, 0x7B, 0x82, 0x53, 0x25}; + +template <> +constexpr type_id type_id_value> = {8}; + +template <> +constexpr type_id type_id_value> = {9}; + +} // namespace cetl + +// NOLINTEND(*-use-after-move) diff --git a/include/cetl/cetl.hpp b/include/cetl/cetl.hpp index 0855a3e..7a5bee7 100644 --- a/include/cetl/cetl.hpp +++ b/include/cetl/cetl.hpp @@ -30,7 +30,7 @@ /// - @subpage example_07_polymorphic_alloc_deleter /// - @subpage example_08_variable_length_array_vs_vector /// - @subpage example_09_variant -/// - @subpage example_10_any +/// - @subpage example_10_unbounded_variant /// /// @page example_01_polyfill_20 Example 1: CETL C++20 Polyfill Header /// Full example for @ref cetl/pf20/cetlpf.hpp @@ -69,9 +69,9 @@ /// Full example for cetl::variant /// @include example_09_variant.cpp /// -/// @page example_10_any Example 10: Using CETL's any -/// Full example for cetl::any -/// @include example_10_any.cpp +/// @page example_10_unbounded_variant Example 10: Using CETL's unbounded_variant +/// Full example for cetl::unbounded_variant +/// @include example_10_unbounded_variant.cpp /// #ifndef CETL_H_INCLUDED diff --git a/include/cetl/pf17/any.hpp b/include/cetl/pf17/any.hpp deleted file mode 100644 index 2158790..0000000 --- a/include/cetl/pf17/any.hpp +++ /dev/null @@ -1,51 +0,0 @@ -/// @file -/// Defines the C++17 `std::any` type and several related entities. -/// @copyright -/// Copyright (C) OpenCyphal Development Team -/// Copyright Amazon.com Inc. or its affiliates. -/// SPDX-License-Identifier: MIT - -#ifndef CETL_PF17_ANY_HPP_INCLUDED -#define CETL_PF17_ANY_HPP_INCLUDED - -#include "attribute.hpp" - -#include // We need this for `std::bad_cast`. -#include // We need this even if exceptions are disabled for `std::terminate`. - -namespace cetl // NOLINT(*-concat-nested-namespaces) -{ -namespace pf17 -{ - -#if defined(__cpp_exceptions) || defined(CETL_DOXYGEN) - -/// \brief A polyfill for `std::bad_any_cast`. -/// -/// This is only available if exceptions are enabled (`__cpp_exceptions` is defined). -/// -class bad_any_cast : public std::bad_cast -{ -public: - bad_any_cast() noexcept = default; - bad_any_cast(const bad_any_cast&) noexcept = default; - bad_any_cast(bad_any_cast&&) noexcept = default; - bad_any_cast& operator=(const bad_any_cast&) noexcept = default; - bad_any_cast& operator=(bad_any_cast&&) noexcept = default; - ~bad_any_cast() noexcept override = default; - - CETL_NODISCARD const char* what() const noexcept override - { - return "bad any cast"; - } -}; - -#endif // defined(__cpp_exceptions) || defined(CETL_DOXYGEN) - -// TODO: Add polyfill `using any = cetl::any<32, true, false>;` when PMR support is implemented. -// The default PMR should be the `std::pmr::new_delete_resource`. - -} // namespace pf17 -} // namespace cetl - -#endif // CETL_PF17_ANY_HPP_INCLUDED diff --git a/include/cetl/pf17/cetlpf.hpp b/include/cetl/pf17/cetlpf.hpp index ce292b0..8f1f7ff 100644 --- a/include/cetl/pf17/cetlpf.hpp +++ b/include/cetl/pf17/cetlpf.hpp @@ -42,7 +42,6 @@ # include # include # include -# include namespace cetl { @@ -106,13 +105,9 @@ using std::get_if; using std::visit; using std::holds_alternative; -// any -using std::bad_any_cast; - } // namespace cetl #else -# include "cetl/pf17/any.hpp" # include "cetl/pf17/byte.hpp" # include "cetl/pf17/utility.hpp" # include "cetl/pf17/optional.hpp" @@ -185,11 +180,6 @@ using cetl::pf17::holds_alternative; using cetl::pf17::bad_variant_access; # endif -// any -# if defined(__cpp_exceptions) || defined(CETL_DOXYGEN) -using cetl::pf17::bad_any_cast; -# endif - } // namespace cetl #endif diff --git a/include/cetl/rtti.hpp b/include/cetl/rtti.hpp index 7767fc4..f3ec207 100644 --- a/include/cetl/rtti.hpp +++ b/include/cetl/rtti.hpp @@ -68,7 +68,10 @@ constexpr bool is_rtti_convertible = decltype(detail::has_cast_impl -constexpr type_id type_id_type_value = detail::type_id_type_value_impl(TypeIDType{}); +constexpr type_id type_id_type_value() noexcept +{ + return detail::type_id_type_value_impl(TypeIDType{}); +} /// The type ID value of the given type. /// This helper is provided for regularity; it has the same value as \c T::_get_type_id_(). @@ -250,7 +253,7 @@ struct rtti_helper : public virtual cetl::rtti, public Bases... /// The recommended implementation that simply returns the value of the \c TypeIDType template parameter. static constexpr type_id _get_type_id_() noexcept { - return type_id_type_value; + return type_id_type_value(); } /// The recommended implementation that performs an exhaustive search for a matching conversion /// throughout the entire type hierarchy tree in the presence of multiple inheritance. diff --git a/include/cetl/any.hpp b/include/cetl/unbounded_variant.hpp similarity index 73% rename from include/cetl/any.hpp rename to include/cetl/unbounded_variant.hpp index 45225b2..2ba19e0 100644 --- a/include/cetl/any.hpp +++ b/include/cetl/unbounded_variant.hpp @@ -1,17 +1,17 @@ /// @file -/// Defines the C++17 `std::any` type and several related entities. +/// Includes cetl::unbounded_variant type and non-member functions. +/// /// @copyright /// Copyright (C) OpenCyphal Development Team /// Copyright Amazon.com Inc. or its affiliates. /// SPDX-License-Identifier: MIT -#ifndef CETL_ANY_HPP_INCLUDED -#define CETL_ANY_HPP_INCLUDED +#ifndef CETL_UNBOUNDED_VARIANT_HPP_INCLUDED +#define CETL_UNBOUNDED_VARIANT_HPP_INCLUDED #include "rtti.hpp" #include "pf17/cetlpf.hpp" #include "pf17/utility.hpp" -#include "pf17/attribute.hpp" #include #include @@ -23,9 +23,33 @@ namespace cetl { +#if defined(__cpp_exceptions) || defined(CETL_DOXYGEN) + +/// \brief Defines a type of object to be thrown by the `get` on failure. +/// +/// This is only available if exceptions are enabled (`__cpp_exceptions` is defined). +/// +class bad_unbounded_variant_access : public std::bad_cast +{ +public: + bad_unbounded_variant_access() noexcept = default; + bad_unbounded_variant_access(const bad_unbounded_variant_access&) noexcept = default; + bad_unbounded_variant_access(bad_unbounded_variant_access&&) noexcept = default; + bad_unbounded_variant_access& operator=(const bad_unbounded_variant_access&) noexcept = default; + bad_unbounded_variant_access& operator=(bad_unbounded_variant_access&&) noexcept = default; + ~bad_unbounded_variant_access() noexcept override = default; + + CETL_NODISCARD const char* what() const noexcept override + { + return "bad unbounded variant access"; + } +}; + +#endif // defined(__cpp_exceptions) || defined(CETL_DOXYGEN) + // Forward declarations template -class any; +class unbounded_variant; namespace detail { @@ -143,9 +167,9 @@ struct base_storage // NOLINT(*-pro-type-member-init) private: // We need to align the buffer to the given value (maximum alignment by default). // Also, we need to ensure that the buffer is at least 1 byte long. - // NB! It's intentional and by design that the `buffer_` is the very first member of `any` memory layout. - // In such way pointer to a `any` and its stored value are the same - could be useful during - // debugging/troubleshooting. + // NB! It's intentional and by design that the `buffer_` is the very first member of `unbounded_variant` + // memory layout. In such way pointer to a `unbounded_variant` and its stored value are the same - + // could be useful during debugging/troubleshooting. alignas(Alignment) char buffer_[std::max(Footprint, 1UL)]; // Holds type-erased value destroyer. `nullptr` if storage has no value stored. @@ -413,10 +437,10 @@ struct base_handlers : base_storage : base_storage -class any : detail::base_handlers +class unbounded_variant : detail::base_handlers { using base = detail::base_handlers; public: - /// \brief Constructs an empty `any` object. - constexpr any() = default; - /// \brief Constructs an `any` object with a copy of the content of `other`. - any(const any& other) = default; - /// \brief Constructs an `any` object with the content of `other` using move semantics. - any(any&& other) noexcept = default; - - /// \brief Constructs an `any` object with `value` using move semantics. + /// \brief Constructs an empty `unbounded_variant` object. + constexpr unbounded_variant() = default; + /// \brief Constructs an `unbounded_variant` object with a copy of the content of `other`. + unbounded_variant(const unbounded_variant& other) = default; + /// \brief Constructs an `unbounded_variant` object with the content of `other` using move semantics. + unbounded_variant(unbounded_variant&& other) noexcept = default; + + /// \brief Constructs an `unbounded_variant` object with `value` using move semantics. /// /// \tparam ValueType Type of the value to be stored. Its size must be less than or equal to `Footprint`. /// - template < - typename ValueType, - typename Tp = std::decay_t, - typename = std::enable_if_t::value && !pf17::detail::is_in_place_type::value>> - any(ValueType&& value) // NOLINT(*-explicit-constructor) + template , + typename = std::enable_if_t::value && + !pf17::detail::is_in_place_type::value>> + unbounded_variant(ValueType&& value) // NOLINT(*-explicit-constructor) { create(std::forward(value)); } - /// \brief Constructs an `any` object with in place constructed value. + /// \brief Constructs an `unbounded_variant` object with in place constructed value. /// /// \tparam ValueType Type of the value to be stored. Its size must be less than or equal to `Footprint`. /// \tparam Args Types of arguments to be passed to the constructor of `ValueType`. /// \param args Arguments to be forwarded to the constructor of `ValueType`. /// template > - explicit any(in_place_type_t, Args&&... args) + explicit unbounded_variant(in_place_type_t, Args&&... args) { create(std::forward(args)...); } - /// \brief Constructs an `any` object with in place constructed value. + /// \brief Constructs an `unbounded_variant` object with in place constructed value. /// /// \tparam ValueType Type of the value to be stored. Its size must be less than or equal to `Footprint`. /// \tparam Up Type of the elements of the initializer list. @@ -481,33 +506,33 @@ class any : detail::base_handlers /// \param args Arguments to be forwarded to the constructor of `ValueType`. /// template > - explicit any(in_place_type_t, std::initializer_list list, Args&&... args) + explicit unbounded_variant(in_place_type_t, std::initializer_list list, Args&&... args) { create(list, std::forward(args)...); } /// \brief Destroys the contained object if there is one. - ~any() + ~unbounded_variant() { reset(); } /// \brief Assigns the content of `rhs` to `*this`. - any& operator=(const any& rhs) + unbounded_variant& operator=(const unbounded_variant& rhs) { if (this != &rhs) { - any(rhs).swap(*this); + unbounded_variant(rhs).swap(*this); } return *this; } /// \brief Assigns the content of `rhs` to `*this` using move semantics. - any& operator=(any&& rhs) noexcept + unbounded_variant& operator=(unbounded_variant&& rhs) noexcept { if (this != &rhs) { - any(std::move(rhs)).swap(*this); + unbounded_variant(std::move(rhs)).swap(*this); } return *this; } @@ -518,10 +543,10 @@ class any : detail::base_handlers /// template , - typename = std::enable_if_t::value>> - any& operator=(ValueType&& value) + typename = std::enable_if_t::value>> + unbounded_variant& operator=(ValueType&& value) { - any(std::forward(value)).swap(*this); + unbounded_variant(std::forward(value)).swap(*this); return *this; } @@ -564,12 +589,12 @@ class any : detail::base_handlers /// \brief Swaps the content of `*this` with the content of `rhs` using copy semantics. /// - /// In use for copyable-only `any` objects. + /// In use for copyable-only `unbounded_variant` objects. /// template > - void swap(any& rhs) + void swap(unbounded_variant& rhs) { if (this == &rhs) { @@ -580,7 +605,7 @@ class any : detail::base_handlers { if (rhs.has_value()) { - any tmp{rhs}; + unbounded_variant tmp{rhs}; static_cast(rhs) = *this; static_cast(*this) = tmp; } @@ -599,10 +624,10 @@ class any : detail::base_handlers /// \brief Swaps the content of `*this` with the content of `rhs` using move semantics. /// - /// In use for moveable `any` objects. + /// In use for moveable `unbounded_variant` objects. /// template > - void swap(any& rhs) noexcept + void swap(unbounded_variant& rhs) noexcept { if (this == &rhs) { @@ -613,7 +638,7 @@ class any : detail::base_handlers { if (rhs.has_value()) { - any tmp{std::move(rhs)}; + unbounded_variant tmp{std::move(rhs)}; static_cast(rhs) = std::move(*this); static_cast(*this) = std::move(tmp); } @@ -634,11 +659,11 @@ class any : detail::base_handlers } private: - template - friend std::add_pointer_t any_cast(Any* operand) noexcept; + template + friend std::add_pointer_t get_if(UnboundedVariant* operand) noexcept; - template - friend std::add_pointer_t> any_cast(const Any* operand) noexcept; + template + friend std::add_pointer_t> get_if(const UnboundedVariant* operand) noexcept; template Tp& create(Args&&... args) @@ -647,101 +672,106 @@ class any : detail::base_handlers return *new (base::get_raw_storage()) Tp(std::forward(args)...); } -}; // class any +}; // class unbounded_variant -/// \brief Typealias for `any` with the given `ValueType` with the default +/// \brief Typealias for `unbounded_variant` with the given `ValueType` with the default /// footprint, copyability, movability, and alignment of the `ValueType`. /// -/// In use by `cetl::make_any` overloads to make them close to `std::make_any`. +/// In use by `cetl::make_unbounded_variant` overloads. /// template -using any_like = any::value, - std::is_move_constructible::value, - alignof(ValueType)>; +using unbounded_variant_like = unbounded_variant::value, + std::is_move_constructible::value, + alignof(ValueType)>; -/// \brief Constructs an any object containing an object of type T, passing the provided arguments to T's constructor. +/// \brief Constructs an unbounded_variant object containing an object of type T, +/// passing the provided arguments to T's constructor. /// -/// Equivalent to `cetl::any(cetl::in_place_type, std::forward(args)...)`. +/// Equivalent to `cetl::unbounded_variant(cetl::in_place_type, std::forward(args)...)`. /// -template , typename... Args> -CETL_NODISCARD Any make_any(Args&&... args) +template , typename... Args> +CETL_NODISCARD UnboundedVariant make_unbounded_variant(Args&&... args) { - return Any(in_place_type, std::forward(args)...); + return UnboundedVariant(in_place_type, std::forward(args)...); } -/// \brief Constructs an any object containing an object of type T, passing the provided arguments to T's constructor. +/// \brief Constructs an unbounded_variant object containing an object of type T, +/// passing the provided arguments to T's constructor. /// -/// Equivalent to `cetl::any(cetl::in_place_type, list, std::forward(args)...)`. +/// Equivalent to `cetl::unbounded_variant(cetl::in_place_type, list, std::forward(args)...)`. /// -template , typename Up, typename... Args> -CETL_NODISCARD Any make_any(std::initializer_list list, Args&&... args) +template , + typename Up, + typename... Args> +CETL_NODISCARD UnboundedVariant make_unbounded_variant(std::initializer_list list, Args&&... args) { - return Any(in_place_type, list, std::forward(args)...); + return UnboundedVariant(in_place_type, list, std::forward(args)...); } /// \brief Performs type-safe access to the contained object. /// -/// \param operand Target any object. -/// \return Returns `std::static_cast(*cetl::any_cast(&operand))`, +/// \param operand Target unbounded_variant object. +/// \return Returns `std::static_cast(*cetl::get_if(&operand))`, /// where let `U` be `std::remove_cv_t>`. /// -template -CETL_NODISCARD ValueType any_cast(const Any& operand) +template +CETL_NODISCARD ValueType get(const UnboundedVariant& operand) { using RawValueType = std::remove_cv_t>; static_assert(std::is_constructible::value, "ValueType is required to be a const lvalue reference " "or a CopyConstructible type"); - const auto ptr = any_cast>(&operand); + const auto ptr = get_if>(&operand); if (ptr == nullptr) { - detail::throw_bad_any_cast(); + detail::throw_bad_unbounded_variant_access(); } return static_cast(*ptr); } /// \brief Performs type-safe access to the contained object. /// -/// \param operand Target any object. -/// \return Returns `std::static_cast(*cetl::any_cast(&operand))`, +/// \param operand Target unbounded_variant object. +/// \return Returns `std::static_cast(*cetl::get_if(&operand))`, /// where let `U` be `std::remove_cv_t>`. /// -template -CETL_NODISCARD ValueType any_cast(Any& operand) +template +CETL_NODISCARD ValueType get(UnboundedVariant& operand) { using RawValueType = std::remove_cv_t>; static_assert(std::is_constructible::value, "ValueType is required to be an lvalue reference " "or a CopyConstructible type"); - const auto ptr = any_cast(&operand); + const auto ptr = get_if(&operand); if (ptr == nullptr) { - detail::throw_bad_any_cast(); + detail::throw_bad_unbounded_variant_access(); } return static_cast(*ptr); } /// \brief Performs type-safe access to the contained object. /// -/// \param operand Target any object. -/// \return Returns `std::static_cast(std::move(*cetl::any_cast(&operand)))`, +/// \param operand Target unbounded_variant object. +/// \return Returns `std::static_cast(std::move(*cetl::get_if(&operand)))`, /// where let `U` be `std::remove_cv_t>`. /// -template -CETL_NODISCARD ValueType any_cast(Any&& operand) +template +CETL_NODISCARD ValueType get(UnboundedVariant&& operand) { using RawValueType = std::remove_cv_t>; static_assert(std::is_constructible::value, "ValueType is required to be an rvalue reference " "or a CopyConstructible type"); - const auto ptr = any_cast(&operand); + const auto ptr = get_if(&operand); if (ptr == nullptr) { - detail::throw_bad_any_cast(); + detail::throw_bad_unbounded_variant_access(); } return static_cast(std::move(*ptr)); } @@ -749,14 +779,14 @@ CETL_NODISCARD ValueType any_cast(Any&& operand) /// \brief Performs type-safe access to the `const` contained object. /// /// \tparam ValueType Type of the requested value; may not be a reference. -/// \tparam Any Type of the `any` object. -/// \param operand Target constant any object. +/// \tparam UnboundedVariant Type of the `unbounded_variant` object. +/// \param operand Target constant unbounded_variant object. /// \return If operand is not a null pointer, /// and the typeid of the requested `ValueType` matches that of the contents of operand, /// a pointer to the value contained by operand, otherwise a null pointer. /// -template -CETL_NODISCARD std::add_pointer_t> any_cast(const Any* const operand) noexcept +template +CETL_NODISCARD std::add_pointer_t> get_if(const UnboundedVariant* const operand) noexcept { static_assert(!std::is_reference::value, "`ValueType` may not be a reference."); @@ -775,14 +805,14 @@ CETL_NODISCARD std::add_pointer_t> any_cast(const An /// \brief Performs type-safe access to the contained object. /// /// \tparam ValueType Type of the requested value; may not be a reference. -/// \tparam Any Type of the `any` object. -/// \param operand Target `any` object. +/// \tparam UnboundedVariant Type of the `unbounded_variant` object. +/// \param operand Target `unbounded_variant` object. /// \return If operand is not a null pointer, /// and the typeid of the requested `ValueType` matches that of the contents of operand, /// a pointer to the value contained by operand, otherwise a null pointer. /// -template -CETL_NODISCARD std::add_pointer_t any_cast(Any* const operand) noexcept +template +CETL_NODISCARD std::add_pointer_t get_if(UnboundedVariant* const operand) noexcept { static_assert(!std::is_reference::value, "`ValueType` may not be a reference."); @@ -800,4 +830,4 @@ CETL_NODISCARD std::add_pointer_t any_cast(Any* const operand) noexce } // namespace cetl -#endif // CETL_ANY_HPP_INCLUDED +#endif // CETL_UNBOUNDED_VARIANT_HPP_INCLUDED