Skip to content

Commit

Permalink
Added type_id() & type_size() methods to the `cetl::unbounded_var…
Browse files Browse the repository at this point in the history
…iant`. #verification #docs #sonar
  • Loading branch information
serges147 committed Jun 5, 2024
1 parent 8066f01 commit a71a5da
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 58 deletions.
109 changes: 70 additions & 39 deletions cetlvast/suites/unittest/test_unbounded_variant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,44 @@

// NOLINTBEGIN(*-use-after-move)

namespace cetl
{

template <>
constexpr type_id type_id_value<bool> = {1};

template <>
constexpr type_id type_id_value<int> = {2};

template <>
constexpr type_id type_id_value<float> = {3};

template <>
constexpr type_id type_id_value<double> = {4};

template <>
constexpr type_id type_id_value<char> = {5};

template <>
constexpr type_id type_id_value<std::string> = {6};

template <>
constexpr type_id type_id_value<uint16_t> = {7};

template <>
constexpr type_id type_id_value<std::complex<double>> = {8};

template <>
constexpr type_id type_id_value<std::function<const char*()>> = {9};

template <>
constexpr type_id type_id_value<std::uint32_t> = {10};

template <>
constexpr type_id type_id_value<std::vector<char>> = {11};

} // namespace cetl

namespace
{

Expand All @@ -28,6 +66,7 @@ using cetl::get_if;
using cetl::make_unbounded_variant;
using cetl::type_id;
using cetl::type_id_type;
using cetl::type_id_invalid;
using cetl::rtti_helper;

using testing::_;
Expand Down Expand Up @@ -94,7 +133,7 @@ struct side_effect_stats
}
};

struct MyBase : rtti_helper<type_id_type<0x0>>
struct MyBase : rtti_helper<type_id_type<0x1, 0x0>>
{
char payload_;
int value_ = 0;
Expand Down Expand Up @@ -186,7 +225,7 @@ struct MyCopyableOnly final : MyBase

static constexpr type_id _get_type_id_() noexcept
{
return {0x0, 0b01};
return {0x1, 0b01};
}

CETL_NODISCARD void* _cast_(const type_id& id) & noexcept override
Expand Down Expand Up @@ -225,7 +264,7 @@ struct MyMovableOnly final : MyBase

static constexpr type_id _get_type_id_() noexcept
{
return {0x0, 0b10};
return {0x1, 0b10};
}

CETL_NODISCARD void* _cast_(const type_id& id) & noexcept override
Expand Down Expand Up @@ -263,7 +302,7 @@ struct MyCopyableAndMovable final : MyBase

static constexpr type_id _get_type_id_() noexcept
{
return {0x0, 0b11};
return {0x1, 0b11};
}

CETL_NODISCARD void* _cast_(const type_id& id) & noexcept override
Expand Down Expand Up @@ -382,6 +421,9 @@ TEST_F(TestPmrUnboundedVariant, cppref_example)

TEST_F(TestPmrUnboundedVariant, ctor_1_default)
{
EXPECT_THAT(unbounded_variant<1>{}.type_size(), 0UL);
EXPECT_THAT(unbounded_variant<1>{}.type_id(), type_id_invalid);

EXPECT_FALSE((unbounded_variant<1>{}.has_value()));
EXPECT_FALSE((unbounded_variant<1, false>{}.has_value()));
EXPECT_FALSE((unbounded_variant<1, false, true>{}.has_value()));
Expand All @@ -397,6 +439,9 @@ TEST_F(TestPmrUnboundedVariant, ctor_1_default)

TEST_F(TestPmrUnboundedVariant, ctor_1_default_pmr)
{
EXPECT_THAT((unbounded_variant<0, true, true, 8, pmr>{get_mr()}.type_size()), 0UL);
EXPECT_THAT((unbounded_variant<0, true, true, 8, pmr>{get_mr()}.type_id()), type_id_invalid);

EXPECT_FALSE((unbounded_variant<0, false, false, 8, pmr>{get_mr()}.has_value()));
EXPECT_FALSE((unbounded_variant<0, false, true, 8, pmr>{get_mr()}.has_value()));
EXPECT_FALSE((unbounded_variant<0, true, false, 8, pmr>{get_mr()}.has_value()));
Expand All @@ -420,12 +465,22 @@ TEST_F(TestPmrUnboundedVariant, ctor_2_copy)
using ub_var = unbounded_variant<sizeof(int)>;

const ub_var src{42};
ub_var dst{src};
EXPECT_THAT(src.type_size(), sizeof(int));
EXPECT_THAT(src.type_id(), cetl::type_id_value<int>);

ub_var dst{src};
EXPECT_THAT(src.type_size(), sizeof(int));
EXPECT_THAT(src.type_id(), cetl::type_id_value<int>);
EXPECT_THAT(dst.type_size(), sizeof(int));
EXPECT_THAT(dst.type_id(), cetl::type_id_value<int>);

EXPECT_THAT(get<int>(src), 42);
EXPECT_THAT(get<int>(dst), 42);

const ub_var empty{};
EXPECT_THAT(empty.type_size(), 0);
EXPECT_THAT(empty.type_id(), type_id_invalid);

ub_var dst2{empty};
EXPECT_THAT(dst2.has_value(), false);
dst2 = {};
Expand Down Expand Up @@ -1011,6 +1066,8 @@ TEST_F(TestPmrUnboundedVariant, get_if_polymorphic)
auto side_effects = stats.make_side_effect_fn();

ub_var test_ubv = MyCopyableAndMovable{'Y', side_effects};
EXPECT_THAT(test_ubv.type_size(), sizeof(MyCopyableAndMovable));
EXPECT_THAT(test_ubv.type_id(), cetl::type_id_value<MyCopyableAndMovable>);

auto& test_base1 = get<const MyBase&>(test_ubv);
EXPECT_THAT(test_base1.payload_, 'Y');
Expand All @@ -1020,6 +1077,8 @@ TEST_F(TestPmrUnboundedVariant, get_if_polymorphic)
EXPECT_THAT(get_if<MyMovableOnly>(&test_ubv), IsNull());

test_ubv = MyBase{'X', side_effects};
EXPECT_THAT(test_ubv.type_size(), sizeof(MyBase));
EXPECT_THAT(test_ubv.type_id(), cetl::type_id_value<MyBase>);

auto& test_base2 = get<const MyBase&>(test_ubv);
EXPECT_THAT(test_base2.payload_, 'X');
Expand Down Expand Up @@ -1166,6 +1225,8 @@ TEST_F(TestPmrUnboundedVariant, emplace_1_ctor_exception)

EXPECT_THAT(t.has_value(), false);
EXPECT_THAT(t.valueless_by_exception(), true);
EXPECT_THAT(t.type_size(), 0);
EXPECT_THAT(t.type_id(), type_id_invalid);
EXPECT_THAT(stats.constructs, 1);
EXPECT_THAT(stats.destructs, 0);
t.reset();
Expand Down Expand Up @@ -1402,6 +1463,8 @@ TEST_F(TestPmrUnboundedVariant, pmr_with_footprint_move_value_when_out_of_memory
#endif
EXPECT_THAT(dst.has_value(), false);
EXPECT_THAT(dst.valueless_by_exception(), true);
EXPECT_THAT(dst.type_size(), 0);
EXPECT_THAT(dst.type_id(), type_id_invalid);
EXPECT_THAT(stats.ops, "@");
}
EXPECT_THAT(stats.constructs, stats.destructs);
Expand Down Expand Up @@ -1725,45 +1788,13 @@ TEST_F(TestPmrUnboundedVariant, pmr_use_mock_as_custom_mr_type)
namespace cetl
{

template <>
constexpr type_id type_id_value<bool> = {1};

template <>
constexpr type_id type_id_value<int> = {2};

template <>
constexpr type_id type_id_value<float> = {3};

template <>
constexpr type_id type_id_value<double> = {4};

template <>
constexpr type_id type_id_value<char> = {5};

template <>
constexpr type_id type_id_value<std::string> = {6};

template <>
constexpr type_id type_id_value<uint16_t> = {7};

template <>
constexpr type_id type_id_value<std::unique_ptr<MyCopyableAndMovable>> =
{0xB3, 0xB8, 0x4E, 0xC1, 0x1F, 0xE4, 0x49, 0x35, 0x9E, 0xC9, 0x1A, 0x77, 0x7B, 0x82, 0x53, 0x25};

template <>
constexpr type_id type_id_value<std::complex<double>> = {8};

template <>
constexpr type_id type_id_value<std::function<const char*()>> = {9};

template <>
constexpr type_id type_id_value<Empty> = {10};

template <>
constexpr type_id type_id_value<std::uint32_t> = {11};

template <>
constexpr type_id type_id_value<std::vector<char>> = {12};
constexpr type_id type_id_value<Empty> =
{0xD5, 0x62, 0x39, 0x66, 0x90, 0x8B, 0x4F, 0x56, 0x8F, 0x2A, 0x2F, 0x4F, 0xDF, 0x3F, 0x31, 0x5B};

} // namespace cetl

Expand Down
10 changes: 10 additions & 0 deletions include/cetl/rtti.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ using type_id = std::array<std::uint8_t, type_id_size>;
/// The bytes of the UUID are given as a list of template parameters; there shall be at most 16 of them;
/// if any are missing, they are assumed to be 0.
/// For conversion to \ref type_id use \ref cetl::type_id_type_value.
/// Please don't use empty or all zeros bytes, as it will be treated as the invalid type ID (see \ref type_id_invalid).
template <std::uint8_t... Bytes>
using type_id_type = std::integer_sequence<std::uint8_t, Bytes...>;

Expand Down Expand Up @@ -81,6 +82,15 @@ constexpr type_id type_id_type_value() noexcept
template <typename T>
constexpr type_id type_id_value = T::_get_type_id_();

/// The type ID value specializations for `void` type.
///
/// In use for the `type_id_invalid` constant, which f.e. is in use at
/// `unbounded_variant::type_id()` method to indicate that the variant is valueless.
///
template <>
constexpr type_id type_id_value<void>{};
constexpr type_id type_id_invalid{type_id_value<void>};

/// An alternative implementation of simple runtime type information (RTTI) capability designed for high-integrity
/// real-time systems, where the use of the standard C++ RTTI is discouraged.
///
Expand Down
80 changes: 61 additions & 19 deletions include/cetl/unbounded_variant.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <cstddef>
#include <exception>
#include <initializer_list>
#include <utility>

namespace cetl
{
Expand Down Expand Up @@ -542,7 +543,7 @@ struct base_access : base_storage<Pmr, Footprint, Alignment>
base::template check_footprint<Tp>();

CETL_DEBUG_ASSERT(nullptr == value_destroyer_, "Expected to be empty before making handlers.");
CETL_DEBUG_ASSERT(nullptr == value_converter_, "");
CETL_DEBUG_ASSERT(nullptr == value_mut_converter_, "");
CETL_DEBUG_ASSERT(nullptr == value_const_converter_, "");

value_destroyer_ = [](void* const storage) {
Expand All @@ -556,27 +557,55 @@ struct base_access : base_storage<Pmr, Footprint, Alignment>
template <typename Tp, std::enable_if_t<is_rtti_convertible<Tp>, int> = 0>
void make_converters() noexcept
{
value_const_converter_ = [](const void* const storage, const type_id& id) {
const auto ptr = static_cast<const Tp*>(storage);
return ptr->_cast_(id);
value_const_converter_ = [](const void* const storage, const cetl::type_id& dst_type_id) {
CETL_DEBUG_ASSERT(nullptr != storage, "");
const auto ptr = static_cast<const Tp*>(storage);
const void* const dst_ptr = ptr->_cast_(dst_type_id);
return std::make_pair(dst_ptr, cetl::type_id_value<Tp>);
};
value_converter_ = [](void* const storage, const type_id& id) {
auto ptr = static_cast<Tp*>(storage);
return ptr->_cast_(id);
value_mut_converter_ = [](void* const storage, const cetl::type_id& dst_type_id) {
CETL_DEBUG_ASSERT(nullptr != storage, "");
const auto ptr = static_cast<Tp*>(storage);
return ptr->_cast_(dst_type_id);
};
}

template <typename Tp, std::enable_if_t<!is_rtti_convertible<Tp>, int> = 0>
void make_converters() noexcept
{
value_const_converter_ = [](const void* const storage, const type_id& id) {
return (id == type_id_value<Tp>) ? storage : nullptr;
value_const_converter_ = [](const void* const storage, const cetl::type_id& dst_type_id) {
CETL_DEBUG_ASSERT(nullptr != storage, "");
const void* const dst_ptr = (dst_type_id == cetl::type_id_value<Tp>) ? storage : nullptr;
return std::make_pair(dst_ptr, cetl::type_id_value<Tp>);
};
value_converter_ = [](void* const storage, const type_id& id) {
return (id == type_id_value<Tp>) ? storage : nullptr;
value_mut_converter_ = [](void* const storage, const cetl::type_id& dst_type_id) {
CETL_DEBUG_ASSERT(nullptr != storage, "");
return (dst_type_id == cetl::type_id_value<Tp>) ? storage : nullptr;
};
}

/// \brief Returns the unique identifier of the actual type of the stored value.
/// `cetl::type_id_invalid` if storage is empty.
///
CETL_NODISCARD cetl::type_id type_id() const noexcept
{
if (!has_value())
{
return cetl::type_id_invalid;
}
CETL_DEBUG_ASSERT(nullptr != value_const_converter_, "Non-empty storage is expected to have value converter.");

return value_const_converter_(base::get_raw_storage(), {}).second;
}

/// \brief Returns the size of the stored value in bytes.
/// Zero if storage is empty.
///
CETL_NODISCARD std::size_t type_size() const noexcept
{
return has_value() ? base::get_value_size() : 0UL;
}

template <typename ValueType>
CETL_NODISCARD void* get_ptr() noexcept
{
Expand All @@ -588,7 +617,7 @@ struct base_access : base_storage<Pmr, Footprint, Alignment>
}
CETL_DEBUG_ASSERT(nullptr != value_const_converter_, "Non-empty storage is expected to have value converter.");

return value_converter_(base::get_raw_storage(), type_id_value<ValueType>);
return value_mut_converter_(base::get_raw_storage(), cetl::type_id_value<ValueType>);
}

template <typename ValueType>
Expand All @@ -602,20 +631,20 @@ struct base_access : base_storage<Pmr, Footprint, Alignment>
}
CETL_DEBUG_ASSERT(nullptr != value_const_converter_, "Non-empty storage is expected to have value converter.");

return value_const_converter_(base::get_raw_storage(), type_id_value<ValueType>);
return value_const_converter_(base::get_raw_storage(), cetl::type_id_value<ValueType>).first;
}

void copy_handlers_from(const base_access& src) noexcept
{
value_destroyer_ = src.value_destroyer_;
value_converter_ = src.value_converter_;
value_mut_converter_ = src.value_mut_converter_;
value_const_converter_ = src.value_const_converter_;
}

void move_handlers_from(base_access& src) noexcept
{
value_destroyer_ = src.value_destroyer_;
value_converter_ = src.value_converter_;
value_mut_converter_ = src.value_mut_converter_;
value_const_converter_ = src.value_const_converter_;

src.reset();
Expand All @@ -632,7 +661,7 @@ struct base_access : base_storage<Pmr, Footprint, Alignment>
}

value_destroyer_ = nullptr;
value_converter_ = nullptr;
value_mut_converter_ = nullptr;
value_const_converter_ = nullptr;

base::reset();
Expand All @@ -644,10 +673,21 @@ struct base_access : base_storage<Pmr, Footprint, Alignment>
// Holds type-erased value destroyer. `nullptr` if storage has no value stored.
void (*value_destroyer_)(void* self) = nullptr;

// Holds type-erased value converters (const and non-const). `nullptr` if storage has no value stored.
// Holds type-erased value converter. `nullptr` if storage has no value stored.
// This function does polymorphic casting, and returns converted raw pointer to the destination mutable value
// type (according to `dst_type_id`), otherwise `nullptr` if conversion is impossible (or `self` is `nullptr`).
//
void* (*value_mut_converter_)(void* self, const cetl::type_id& dst_type_id) = nullptr;

// Holds type-erased const value converter. `nullptr` if storage has no value stored.
// This function does polymorphic casting, and returns:
// - Converted raw pointer to the destination const value type (according to `dst_type_id`),
// otherwise `nullptr` if conversion is impossible (or `self` is `nullptr`);
// - Unique identifier of the actual type of the stored value,
// otherwise `type_id_invalid` (all zeros) if storage is empty.
//
void* (*value_converter_)(void* self, const type_id& id) = nullptr;
const void* (*value_const_converter_)(const void* self, const type_id& id) = nullptr;
using ValueConstPtrAndTypeId = std::pair<const void*, cetl::type_id>;
ValueConstPtrAndTypeId (*value_const_converter_)(const void* self, const cetl::type_id& dst_type_id) = nullptr;

}; // base_access

Expand Down Expand Up @@ -1001,6 +1041,8 @@ class unbounded_variant : detail::base_move<Pmr, Footprint, Copyable, Movable, A
using pmr_type = Pmr;

using base::reset;
using base::type_id;
using base::type_size;
using base::has_value;
using base::valueless_by_exception;

Expand Down

0 comments on commit a71a5da

Please sign in to comment.