diff --git a/.gitmodules b/.gitmodules index 7447347a..a5ec6372 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "submodules/o1heap"] path = submodules/o1heap url = https://github.com/pavel-kirienko/o1heap.git +[submodule "submodules/CETL"] + path = submodules/CETL + url = https://github.com/OpenCyphal/CETL.git diff --git a/docs/languages.rst b/docs/languages.rst index 9e81d377..7e8e3a94 100644 --- a/docs/languages.rst +++ b/docs/languages.rst @@ -29,6 +29,16 @@ vector.yaml variable_array_type_include: variable_array_type_template: std::vector<{TYPE}> +variable_length_array_with_polymorphic_allocator.yaml +""""""""""""""""" + +.. code-block :: yaml + + variable_array_type_include: + variable_array_type_template: "cetl::VariableLengthArray<{TYPE}, typename std::allocator_traits::template rebind_alloc<{TYPE}>>" + variable_array_type_init_args_template: "{SIZE}" + variable_array_type_allocator_type: "cetl::pf17::pmr::polymorphic_allocator" + nnvg command """""""""""""""""" diff --git a/src/nunavut/lang/cpp/__init__.py b/src/nunavut/lang/cpp/__init__.py index 23621225..1062c245 100644 --- a/src/nunavut/lang/cpp/__init__.py +++ b/src/nunavut/lang/cpp/__init__.py @@ -18,6 +18,8 @@ import pydsdl +from enum import auto, Enum + from nunavut._dependencies import Dependencies from nunavut._exceptions import InternalError as NunavutInternalError from nunavut._templates import ( @@ -34,6 +36,15 @@ from nunavut.lang.c import filter_literal as c_filter_literal +class SpecialMethods(Enum): + DefaultConstructorWithOptionalAllocator = auto() + CopyConstructor = auto() + CopyConstructorWithAllocator = auto() + MoveConstructor = auto() + MoveConstructorWithAllocator = auto() + Destructor = auto() + + class Language(BaseLanguage): """ Concrete, C++-specific :class:`nunavut.lang.Language` object. @@ -165,40 +176,6 @@ def _get_variable_length_array_path(self) -> pathlib.Path: "variable_length_array file was not found in the c++ support files?!" ) # pragma: no cover - @functools.lru_cache() - def _get_default_vla_template(self) -> str: - """ - Returns a template to the built-in Variable Length Array (VLA) implementation. This is used when no override - was provided. - - options = {"target_endianness": "big"} - lctx = ( - LanguageContextBuilder(include_experimental_languages=True) - .set_target_language("cpp") - .set_target_language_configuration_override(Language.WKCV_SUPPORT_NAMESPACE, "") - .set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options) - .create() - ) - template_w_no_namespace = lctx.get_target_language()._get_default_vla_template() - assert template_w_no_namespace.startswith("VariableLengthArray") - - lctx = ( - LanguageContextBuilder(include_experimental_languages=True) - .set_target_language("cpp") - .set_target_language_configuration_override(Language.WKCV_SUPPORT_NAMESPACE, "foo.bar") - .set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options) - .create() - ) - template_w_namespace = lctx.get_target_language()._get_default_vla_template() - assert template_w_namespace.startswith("foo::bar::VariableLengthArray") - - """ - base_template = "VariableLengthArray<{TYPE}, {MAX_SIZE}>" - if len(self.support_namespace) > 0: - return "::".join(self.support_namespace) + "::" + base_template - else: - return base_template - def get_includes(self, dep_types: Dependencies) -> typing.List[str]: """ Get includes for c++ source. @@ -255,6 +232,7 @@ def do_includes_test(use_foobar, extension): """ std_includes = [] # type: typing.List[str] std_includes.append("limits") # we always include limits to support static assertions + std_includes.append("memory") # for std::allocator and std::allocator_traits if self.get_config_value_as_bool("use_standard_types"): if dep_types.uses_integer: std_includes.append("cstdint") @@ -278,6 +256,17 @@ def do_includes_test(use_foobar, extension): return includes_formatted + def get_globals(self) -> typing.Mapping[str, typing.Any]: + """ + Make additional globals available in the cpp jinja templates + + :return: A mapping of global names to global values. + """ + globals_map : dict[str, typing.Any]= {} + globals_map.update(super().get_globals()) + globals_map["SpecialMethods"] = SpecialMethods + return globals_map + def filter_id(self, instance: typing.Any, id_type: str = "any") -> str: raw_name = self.default_filter_id_for_target(instance) @@ -289,14 +278,6 @@ def create_bitset_decl(self, type: str, max_size: int) -> str: def create_array_decl(self, type: str, max_size: int) -> str: return "std::array<{TYPE},{MAX_SIZE}>".format(TYPE=type, MAX_SIZE=max_size) - def create_vla_decl(self, type: str, max_size: int) -> str: - variable_array_type_template = self.get_option("variable_array_type_template") - - if not isinstance(variable_array_type_template, str) or len(variable_array_type_template) == 0: - variable_array_type_template = self._get_default_vla_template() - - return variable_array_type_template.format(TYPE=type, MAX_SIZE=max_size) - @template_language_test(__name__) def uses_std_variant(language: Language) -> bool: @@ -968,15 +949,80 @@ def filter_destructor_name(language: Language, instance: pydsdl.Any) -> str: @template_language_filter(__name__) -def filter_default_value_initializer(language: Language, instance: pydsdl.Any) -> str: +def filter_value_initializer(language: Language, instance: pydsdl.Any, initialization_context: SpecialMethods) -> str: """ Emit a default initialization statement for the given instance if primitive or array. """ - if isinstance(instance, pydsdl.PrimitiveType) or isinstance(instance, pydsdl.ArrayType): - return "{}" + + def needs_rhs(initialization_context: SpecialMethods) -> bool: + return (initialization_context in ( + SpecialMethods.CopyConstructor, + SpecialMethods.CopyConstructorWithAllocator, + SpecialMethods.MoveConstructor, + SpecialMethods.MoveConstructorWithAllocator)) + + def needs_allocator(instance: pydsdl.Any, initialization_context: SpecialMethods) -> bool: + return (initialization_context in ( + SpecialMethods.DefaultConstructorWithOptionalAllocator, + SpecialMethods.CopyConstructorWithAllocator, + SpecialMethods.MoveConstructorWithAllocator) + and (isinstance(instance.data_type, pydsdl.VariableLengthArrayType) + or isinstance(instance.data_type, pydsdl.CompositeType))) + + def needs_vla_init_args(instance: pydsdl.Any, initialization_context: SpecialMethods) -> bool: + return (initialization_context == SpecialMethods.DefaultConstructorWithOptionalAllocator + and isinstance(instance.data_type, pydsdl.VariableLengthArrayType)) + + def needs_move(initialization_context: SpecialMethods) -> bool: + return (initialization_context in ( + SpecialMethods.MoveConstructor, + SpecialMethods.MoveConstructorWithAllocator)) + + if (isinstance(instance.data_type, pydsdl.PrimitiveType) + or isinstance(instance.data_type, pydsdl.ArrayType) + or isinstance(instance.data_type, pydsdl.CompositeType)): + + wrap: str = "" + rhs: str = "" + trailing_args: list[str] = [] + + if needs_rhs(initialization_context): + rhs = "rhs." + language.filter_id(instance) + + if needs_allocator(instance, initialization_context): + trailing_args.append("allocator") + + if needs_vla_init_args(instance, initialization_context): + init_args_template = language.get_option("variable_array_type_init_args_template") + if isinstance(init_args_template, str) and len(init_args_template) > 0: + trailing_args.append(init_args_template.format(SIZE=instance.data_type.capacity)) + + if needs_move(initialization_context): + wrap = "std::move" + + if wrap: + rhs = wrap + "(" + rhs + ")" + constructor_args = [] + if rhs: + constructor_args.append(rhs) + if trailing_args: + constructor_args.extend(trailing_args) + return "{" + ",".join(constructor_args) + "}" + return "" +@template_language_filter(__name__) +def filter_field_declaration(language: Language, instance: pydsdl.Any) -> str: + """ + Emit a field declaration that accounts for the message being a templated type + """ + suffix: str = "" + if isinstance(instance, pydsdl.CompositeType): + suffix = "_Impl" + return filter_declaration(language, instance) + suffix + + @template_language_filter(__name__) def filter_declaration(language: Language, instance: pydsdl.Any) -> str: """ @@ -986,13 +1032,12 @@ def filter_declaration(language: Language, instance: pydsdl.Any) -> str: return filter_type_from_primitive(language, instance) elif isinstance(instance, pydsdl.VariableLengthArrayType): variable_array_type_template = language.get_option("variable_array_type_template") - if not isinstance(variable_array_type_template, str) or len(variable_array_type_template) == 0: - return language.create_vla_decl(filter_declaration(language, instance.element_type), instance.capacity) - else: - return variable_array_type_template.format( - TYPE=filter_declaration(language, instance.element_type), MAX_SIZE=instance.capacity - ) + raise RuntimeError("You must specify a value for the 'variable_array_type_template' option.") + + return variable_array_type_template.format( + TYPE=filter_declaration(language, instance.element_type), MAX_SIZE=instance.capacity + ) elif isinstance(instance, pydsdl.ArrayType): if isinstance(instance.element_type, pydsdl.BooleanType): return language.create_bitset_decl(filter_declaration(language, instance.element_type), instance.capacity) diff --git a/src/nunavut/lang/cpp/support/variable_length_array.hpp b/src/nunavut/lang/cpp/support/variable_length_array.hpp deleted file mode 100644 index c4452785..00000000 --- a/src/nunavut/lang/cpp/support/variable_length_array.hpp +++ /dev/null @@ -1,1644 +0,0 @@ -// -// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// Copyright (C) 2014 Pavel Kirienko -// Copyright (C) 2021 OpenCyphal Development Team -// This software is distributed under the terms of the MIT License. -// - -#ifndef NUNAVUT_SUPPORT_VARIABLE_LENGTH_ARRAY_HPP_INCLUDED -#define NUNAVUT_SUPPORT_VARIABLE_LENGTH_ARRAY_HPP_INCLUDED - -#include -#include -#include -#include // For floating-point array comparison. -#include -#include -#include -#include -#include -#if __cpp_exceptions -# include -#endif - -static_assert( - __cplusplus >= 201402L, - "Unsupported language: ISO C14, C++14, or a newer version of either is required to use the built-in VLA type"); - -namespace nunavut -{ -namespace support -{ - -/// -/// Default allocator using malloc and free. -/// -template -class MallocAllocator -{ -public: - using value_type = T; - - MallocAllocator() noexcept {} - - T* allocate(std::size_t n) noexcept - { - return reinterpret_cast(malloc(n * sizeof(T))); - } - - constexpr void deallocate(T* p, std::size_t n) noexcept - { - (void) n; - free(p); - } -}; - -/// -/// Minimal, generic container for storing Cyphal variable-length arrays. One property that is unique -/// for variable-length arrays is that they have a maximum bound which this implementation enforces. -/// This allows use of an allocator that is backed by statically allocated memory. -/// -/// @tparam T The type of elements in the array. -/// @tparam MaxSize The maximum allowable size and capacity of the array. -/// @tparam Allocator The type of allocator. -/// -template > -class VariableLengthArray -{ -public: - /// - /// STL-like declaration of pointer type. - /// - using pointer = typename std::add_pointer::type; - - /// - /// STL-like declaration of the allocator type. - /// - using allocator_type = Allocator; - - /// - /// STL-like declaration of the iterator type. - /// - using iterator = typename std::add_pointer::type; - - /// - /// STL-like declaration of the const iterator type. - /// - using const_iterator = typename std::add_pointer::type>::type; - - /// - /// STL-like declaration of the container's storage type. - /// - using value_type = T; - - constexpr VariableLengthArray() noexcept(std::is_nothrow_constructible::value) - : data_(nullptr) - , capacity_(0) - , size_(0) - , alloc_() - { - } - - VariableLengthArray(std::initializer_list l) noexcept( - noexcept(VariableLengthArray().reserve(1)) && - std::is_nothrow_constructible::value && std::is_nothrow_copy_constructible::value) - : data_(nullptr) - , capacity_(0) - , size_(0) - , alloc_() - { - reserve(l.size()); - for (const_iterator list_item = l.begin(), end = l.end(); list_item != end; ++list_item) - { - push_back_impl(*list_item); - } - } - - template - constexpr VariableLengthArray( - InputIt first, - InputIt last, - const std::size_t length, - const allocator_type& alloc = - allocator_type()) noexcept(noexcept(VariableLengthArray().reserve(1)) && - std::is_nothrow_copy_constructible::value && - std::is_nothrow_copy_constructible::value) - : data_(nullptr) - , capacity_(0) - , size_(0) - , alloc_(alloc) - { - reserve(length); - for (size_t inserted = 0; first != last && inserted < length; ++first) - { - push_back_impl(*first); - } - } - - // - // Rule of Five. - // - VariableLengthArray(const VariableLengthArray& rhs) noexcept( - noexcept(VariableLengthArray().reserve(1)) && - std::is_nothrow_copy_constructible::value && std::is_nothrow_copy_constructible::value) - : data_(nullptr) - , capacity_(0) - , size_(0) - , alloc_(rhs.alloc_) - { - reserve(rhs.size()); - for (const_iterator list_item = rhs.cbegin(), end = rhs.cend(); list_item != end; ++list_item) - { - push_back_impl(*list_item); - } - } - - VariableLengthArray& operator=(const VariableLengthArray& rhs) noexcept( - noexcept(VariableLengthArray::template fast_deallocate( - nullptr, - 0, - 0, - std::declval())) && noexcept(VariableLengthArray().reserve(1)) && - std::is_nothrow_copy_constructible::value && std::is_nothrow_copy_constructible::value) - { - if (this == &rhs) - { - return *this; - } - fast_deallocate(data_, size_, capacity_, alloc_); - data_ = nullptr; - capacity_ = 0; - size_ = 0; - alloc_ = rhs.alloc_; - reserve(rhs.size()); - for (const_iterator list_item = rhs.cbegin(), end = rhs.cend(); list_item != end; ++list_item) - { - push_back_impl(*list_item); - } - return *this; - } - - VariableLengthArray(VariableLengthArray&& rhs) noexcept(std::is_nothrow_move_constructible::value) - : data_(rhs.data_) - , capacity_(rhs.capacity_) - , size_(rhs.size_) - , alloc_(std::move(rhs.alloc_)) - { - rhs.data_ = nullptr; - rhs.capacity_ = 0; - rhs.size_ = 0; - } - - VariableLengthArray& operator=(VariableLengthArray&& rhs) noexcept( - noexcept(VariableLengthArray::template fast_deallocate< - T>(nullptr, 0, 0, std::declval())) && - std::is_nothrow_move_assignable::value) - { - fast_deallocate(data_, size_, capacity_, alloc_); - - alloc_ = std::move(rhs.alloc_); - - data_ = rhs.data_; - rhs.data_ = nullptr; - - capacity_ = rhs.capacity_; - rhs.capacity_ = 0; - - size_ = rhs.size_; - rhs.size_ = 0; - - return *this; - } - - ~VariableLengthArray() noexcept(noexcept(VariableLengthArray::template fast_deallocate< - T>(nullptr, 0, 0, std::declval()))) - { - fast_deallocate(data_, size_, capacity_, alloc_); - } - - constexpr bool operator==(const VariableLengthArray& rhs) const noexcept - { - if (size() != rhs.size()) - { - return false; - } - if (data() != rhs.data()) - { - const_iterator rnext = rhs.cbegin(); - const_iterator rend = rhs.cend(); - const_iterator lnext = cbegin(); - const_iterator lend = cend(); - for (; rnext != rend && lnext != lend; ++lnext, ++rnext) - { - if (!internal_compare_element(*lnext, *rnext)) - { - return false; - } - } - if (rnext != rend || lnext != lend) - { - // One of the two iterators returned less then the size value? This is probably a bug but we can only - // say they are not equal here. - return false; - } - } - return true; - } - - constexpr bool operator!=(const VariableLengthArray& rhs) const noexcept(noexcept(operator==(rhs))) - { - return !(operator==(rhs)); - } - - /// - /// The maximum size (and capacity) of this array type. This size is derived - /// from the DSDL definition for a field and represents the maximum number of - /// elements allowed if the specified allocator is able to provide adequate - /// memory (i.e. there may be up to this many elements but there shall never - /// be more). - /// - static constexpr const std::size_t type_max_size = MaxSize; - - /// - /// The maximum size (and capacity) of this array. This method is compatible - /// with {@code stl::vector::max_size} and always returns {@code type_max_size}. - /// - constexpr std::size_t max_size() const noexcept - { - return type_max_size; - } - - // +----------------------------------------------------------------------+ - // | ELEMENT ACCESS - // +----------------------------------------------------------------------+ - /// - /// Provides direct, unsafe access to the internal data buffer. This pointer - /// is invalidated by calls to {@code shrink_to_fit} and {@code reserve}. - /// - constexpr const_iterator cbegin() const noexcept - { - return data_; - } - - /// - /// Pointer to memory location after the last, valid element. This pointer - /// is invalidated by calls to {@code shrink_to_fit} and {@code reserve}. - /// - constexpr const_iterator cend() const noexcept - { - if (nullptr == data_) - { - return nullptr; - } - else - { - return &data_[size_]; - } - } - - /// - /// Provides direct, unsafe access to the internal data buffer. This pointer - /// is invalidated by calls to {@code shrink_to_fit} and {@code reserve}. - /// - constexpr iterator begin() noexcept - { - return data_; - } - constexpr const_iterator begin() const noexcept - { - return cbegin(); - } - - /// - /// Pointer to memory location after the last, valid element. This pointer - /// is invalidated by calls to {@code shrink_to_fit} and {@code reserve}. - /// - constexpr iterator end() noexcept - { - if (nullptr == data_) - { - return nullptr; - } - else - { - return &data_[size_]; - } - } - constexpr const_iterator end() const noexcept - { - return cend(); - } - - /// - /// Provides direct, unsafe access to the internal data buffer. This pointer - /// is invalidated by calls to {@code shrink_to_fit} and {@code reserve}. - /// - constexpr T* data() noexcept - { - return data_; - } - - /// - /// Provides direct, unsafe access to the internal data buffer. This pointer - /// is invalidated by calls to {@code shrink_to_fit} and {@code reserve}. - /// - constexpr const T* data() const noexcept - { - return data_; - } - - /// - /// Direct, const access to an element. If {@code pos} is > {@code size} - /// the behavior is undefined. - /// - /// The returned reference is valid while this object is unless - /// {@code reserve} or {@code shrink_to_fit} is called. - /// - constexpr const T& operator[](std::size_t pos) const noexcept - { - return data_[pos]; - } - - /// - /// Direct access to an element. If {@code pos} is > {@code size} - /// the behavior is undefined. - /// - /// The returned reference is valid while this object is unless - /// {@code reserve} or {@code shrink_to_fit} is called. - /// - constexpr T& operator[](std::size_t pos) noexcept - { - return data_[pos]; - } - - /// - /// Safe, const access to an element. Returns a pointer to the element if - /// pos is < {@link size} otherwise returns {@code nullptr}. - /// - /// The returned pointer is valid while this object is unless - /// {@code reserve} or {@code shrink_to_fit} is called. - /// - constexpr const T* at_or_null(std::size_t pos) const noexcept - { - if (pos < size_) - { - return &data_[pos]; - } - else - { - return nullptr; - } - } - - /// - /// Safe access to an element. Returns a pointer to the element if - /// pos is < {@link size} otherwise returns {@code nullptr}. - /// - /// The returned pointer is valid while this object is unless - /// {@code reserve} or {@code shrink_to_fit} is called. - /// - constexpr T* at_or_null(std::size_t pos) noexcept - { - if (pos < size_) - { - return &data_[pos]; - } - else - { - return nullptr; - } - } - - /// - /// STL-like access to a copy of the internal allocator. - /// - constexpr allocator_type get_allocator() const noexcept - { - return alloc_; - } - - /// - /// Provided to allow access to statically allocated buffers stored within - /// the allocator instance. - /// - constexpr const allocator_type& peek_allocator() const noexcept - { - return alloc_; - } - - // +----------------------------------------------------------------------+ - // | CAPACITY - // +----------------------------------------------------------------------+ - /// - /// The number of elements that can be stored in the array without further - /// allocations. This number will only grow through calls to {@code reserve} - /// and can shrink through calls to {@code shrink_to_fit}. This value shall - /// never exceed {@code max_size}. - /// - constexpr std::size_t capacity() const noexcept - { - return capacity_; - } - - /// - /// The current number of elements in the array. This number increases with each - /// successful call to {@code push_back} and decreases with each call to - /// {@code pop_back} (when size is > 0). - /// - constexpr std::size_t size() const noexcept - { - return size_; - } - - /// - /// Ensure enough memory is allocated to store at least the {@code desired_capacity} number of elements. - /// - /// @param desired_capacity The number of elements to allocate, but not initialize, memory for. - /// @return The new (or unchanged) capacity of this object. - /// - std::size_t reserve(const std::size_t desired_capacity) noexcept(noexcept(allocator_type().allocate(0)) && noexcept( - VariableLengthArray::template move_and_free(nullptr, - nullptr, - 0, - 0, - std::declval()))) - { - const std::size_t clamped_capacity = (desired_capacity > MaxSize) ? MaxSize : desired_capacity; - const std::size_t no_shrink_capacity = (clamped_capacity > size_) ? clamped_capacity : size_; - - T* new_data = nullptr; - -#if __cpp_exceptions - try - { - new_data = alloc_.allocate(no_shrink_capacity); - } catch (const std::bad_alloc& e) - { - // we ignore the exception since all allocation failures are modeled using - // null by this class. - (void) e; - } -#else - new_data = alloc_.allocate(no_shrink_capacity); -#endif - - if (new_data != nullptr) - { - if (new_data != data_) - { - move_and_free(new_data, data_, size_, capacity_, alloc_); - } // else the allocator was able to simply extend the reserved area for the same memory pointer. - data_ = new_data; - capacity_ = no_shrink_capacity; - } - - return capacity_; - } - - /// - /// Deallocate or reallocate memory such that not more than {@code size()} elements can be stored in this object. - /// - /// @return True if nothing was done or if memory was successfully resized. False to indicate that the allocator - /// could not provide enough memory to move the existing objects to a smaller allocation. - /// - bool shrink_to_fit() noexcept( - noexcept(allocator_type().deallocate(nullptr, 0)) && noexcept(allocator_type().allocate(0)) && noexcept( - VariableLengthArray::template move_and_free(nullptr, - nullptr, - 0, - 0, - std::declval()))) - { - if (size_ == capacity_) - { - // already shrunk - return true; - } - - // Special case where we are shrinking to empty - if (size_ == 0) - { - alloc_.deallocate(data_, capacity_); - data_ = nullptr; - capacity_ = 0; - return true; - } - - // Allocate only enough to store what we have. - T* minimized_data = nullptr; -#if __cpp_exceptions - try - { -#endif - minimized_data = alloc_.allocate(size_ * sizeof(T)); -#if __cpp_exceptions - } catch (const std::bad_alloc& e) - { - (void) e; - } -#endif - - if (minimized_data == nullptr) - { - return false; - } - else - { - if (minimized_data != data_) - { - move_and_free(minimized_data, data_, size_, capacity_, alloc_); - } // else the allocator was able to simply shrink the reserved area for the same memory pointer. - data_ = minimized_data; - capacity_ = size_; - return true; - } - } - - // +----------------------------------------------------------------------+ - // | MODIFIERS - // +----------------------------------------------------------------------+ - /// - /// Construct a new element on to the back of the array. Grows size by 1 and may grow capacity. - /// - /// If exceptions are disabled the caller must check before and after to see if the size grew to determine success. - /// If using exceptions this method throws {@code std::length_error} if the size of this collection is at capacity - /// or {@code std::bad_alloc} if the allocator failed to provide enough memory. - /// - /// If exceptions are disabled use the following logic: - /// - /// const size_t size_before = my_array.size(); - /// my_array.push_back(); - /// if (size_before == my_array.size()) - /// { - /// // failure - /// if (size_before == my_array.max_size()) - /// { - /// // length_error: you probably should have checked this first. - /// } - /// else - /// { - /// // bad_alloc: out of memory - /// } - // } // else, success. - /// - /// @throw std::length_error if the size of this collection is at capacity. - /// @throw std::bad_alloc if memory was needed and none could be allocated. - /// - constexpr void push_back() - { - if (!ensure_size_plus_one()) - { - return; - } - - if (nullptr == push_back_impl()) - { -#if __cpp_exceptions - throw std::length_error("max_size is reached, the array cannot grow further"); -#endif - } - } - - /// - /// Allocate a new element on to the back of the array and move value into it. Grows size by 1 and - /// may grow capacity. - /// - /// See VariableLengthArray::push_back() for full documentation. - /// - /// @throw std::length_error if the size of this collection is at capacity. - /// @throw std::bad_alloc if memory was needed and none could be allocated. - /// - constexpr void push_back(T&& value) - { - if (!ensure_size_plus_one()) - { - return; - } - - if (nullptr == push_back_impl(std::move(value))) - { -#if __cpp_exceptions - throw std::length_error("size is at capacity. Use reserve to grow the capacity."); -#endif - } - } - - /// - /// Allocate a new element on to the back of the array and copy value into it. Grows size by 1 and - /// may grow capacity. - /// - /// See VariableLengthArray::push_back() for full documentation. - /// - /// @throw std::length_error if the size of this collection is at capacity. - /// @throw std::bad_alloc if memory was needed and none could be allocated. - /// - constexpr void push_back(const T& value) - { - if (!ensure_size_plus_one()) - { - return; - } - - if (nullptr == push_back_impl(value)) - { -#if __cpp_exceptions - throw std::length_error("size is at capacity. Use reserve to grow the capacity."); -#endif - } - } - - /// - /// Remove and destroy the last item in the array. This reduces the array size by 1 unless - /// the array is already empty. - /// - constexpr void pop_back() noexcept(std::is_nothrow_destructible::value) - { - if (size_ > 0) - { - data_[--size_].~T(); - } - } - -private: - /** - * Ensure amortized, constant-time expansion of capacity as single items are added. - */ - constexpr bool ensure_size_plus_one() - { - if (size_ < capacity_) - { - return true; - } - - if (capacity_ == MaxSize) - { -#if __cpp_exceptions - throw std::length_error("Already at max size. Cannot increase capacity."); -#endif - return false; - } - - // https://stackoverflow.com/questions/1100311/what-is-the-ideal-growth-rate-for-a-dynamically-allocated-array/20481237#20481237 - const std::size_t capacity_before = capacity_; - - const std::size_t half_capacity = capacity_before / 2U; - - // That is, instead of a capacity sequence of 1, 2, 3, 4, 6, 9 we start from zero as 2, 4, 6, 9. The first - // opportunity for reusing previously freed memory comes when increasing to 19 from 13 since E(n-1) == 21. - const std::size_t new_capacity = capacity_before + ((half_capacity <= 1) ? 2 : half_capacity); - - if (new_capacity > MaxSize) - { - reserve(MaxSize); - } - else - { - reserve(new_capacity); - } - - if (capacity_before == capacity_) - { -#if __cpp_exceptions - throw std::bad_alloc(); -#endif - return false; - } - else - { - return true; - } - } - - template - static constexpr bool internal_compare_element( - const U& lhs, - const U& rhs, - typename std::enable_if::value>::type* = nullptr) noexcept - { - return (lhs == rhs); - } - - template - static constexpr bool internal_compare_element( - const U& lhs, - const U& rhs, - typename std::enable_if::value>::type* = nullptr) noexcept - { - // From the C++ documentation for std::numeric_limits::epsilon() - // https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon - - const auto diff = std::fabs(lhs - rhs); - const auto sum = std::fabs(lhs + rhs); - // Scale the relative machine epsilon up. - const auto epsilon = std::numeric_limits::epsilon() * sum; - - return (diff <= epsilon) || diff < std::numeric_limits::min(); - } - - constexpr pointer push_back_impl() noexcept(std::is_nothrow_default_constructible::value) - { - if (size_ < capacity_) - { - return new (&data_[size_++]) T(); - } - else - { - return nullptr; - } - } - - constexpr pointer push_back_impl(T&& value) noexcept(std::is_nothrow_move_constructible::value) - { - if (size_ < capacity_) - { - return new (&data_[size_++]) T(std::move(value)); - } - else - { - return nullptr; - } - } - - constexpr pointer push_back_impl(const T& value) noexcept(std::is_nothrow_copy_constructible::value) - { - if (size_ < capacity_) - { - return new (&data_[size_++]) T(value); - } - else - { - return nullptr; - } - } - - /// - /// If trivially destructible then we don't have to call the destructors. - /// - template - static constexpr void fast_deallocate(U* const src, - const std::size_t src_size_count, - const std::size_t src_capacity_count, - allocator_type& alloc, - typename std::enable_if::value>::type* = - nullptr) noexcept(noexcept(allocator_type().deallocate(nullptr, 0))) - { - (void) src_size_count; - alloc.deallocate(src, src_capacity_count); - } - - /// - /// If not trivially destructible then we invoke each destructor. - /// - template - static constexpr void fast_deallocate( - U* const src, - const std::size_t src_size_count, - const std::size_t src_capacity_count, - allocator_type& alloc, - typename std::enable_if::value>::type* = - nullptr) noexcept(std::is_nothrow_destructible::value&& noexcept(allocator_type().deallocate(nullptr, - 0))) - { - std::size_t dtor_iterator = src_size_count; - while (dtor_iterator > 0) - { - src[--dtor_iterator].~U(); - } - alloc.deallocate(src, src_capacity_count); - } - - /// - /// Move stuff in src to dst and then free all the memory allocated for src. - /// - template - static constexpr void move_and_free( - U* const dst, - U* const src, - std::size_t src_len_count, - std::size_t src_capacity_count, - allocator_type& alloc, - typename std::enable_if::value>::type* = - nullptr) noexcept(noexcept(fast_deallocate(nullptr, 0, 0, std::declval()))) - { - if (src_len_count > 0) - { - // The allocator returned new memory. Copy any initialized objects in the old region to the new one. - std::memcpy(dst, src, src_len_count * sizeof(U)); - } - fast_deallocate(src, src_len_count, src_capacity_count, alloc); - } - - /// - /// Same as above but for non-fundamental types. We can't just memcpy for these. - /// - template - static constexpr void move_and_free( - U* const dst, - U* const src, - std::size_t src_len_count, - std::size_t src_capacity_count, - allocator_type& alloc, - typename std::enable_if::value>::type* = nullptr, - typename std::enable_if::value || std::is_copy_constructible::value>::type* = - nullptr) noexcept((std::is_nothrow_move_constructible::value || - std::is_nothrow_copy_constructible< - U>::value) && noexcept(fast_deallocate(nullptr, - 0, - 0, - std::declval()))) - { - if (src_len_count > 0) - { - // The allocator returned new memory. Copy any initialized objects in the old region to the new one. - for (size_t i = 0; i < src_len_count; ++i) - { - new (&dst[i]) U(std::move_if_noexcept(src[i])); - } - } - fast_deallocate(src, src_len_count, src_capacity_count, alloc); - } - - // +----------------------------------------------------------------------+ - // | DATA MEMBERS - // +----------------------------------------------------------------------+ - T* data_; - std::size_t capacity_; - std::size_t size_; - allocator_type alloc_; -}; - -// required till C++ 17. Redundant but allowed after that. -template -const std::size_t VariableLengthArray::type_max_size; - -/// -/// A memory-optimized specialization for bool storing 8 bits per byte; -/// the internal bit ordering follows the Cyphal DSDL specification. -/// Some features are not supported. -/// -template -class VariableLengthArray -{ - using Storage = std::uint8_t; - -public: - using value_type = bool; - using difference_type = std::ptrdiff_t; - using size_type = std::size_t; - using allocator_type = typename std::allocator_traits::template rebind_alloc; - - class reference final - { - public: - reference(const reference&) noexcept = default; - reference(reference&&) noexcept = default; - ~reference() noexcept = default; - - reference& operator=(const bool x) - { - array_.set(index_, x); - return *this; - } - reference& operator=(const reference& x) - { - return this->operator=(static_cast(x)); - } - reference& operator=(reference&& x) - { - return this->operator=(static_cast(x)); - } - - bool operator~() const - { - return !array_.test(index_); - } - operator bool() const - { - return array_.test(index_); - } - - bool operator==(const reference& rhs) const - { - return static_cast(*this) == static_cast(rhs); - } - bool operator!=(const reference& rhs) const - { - return !((*this) == rhs); - } - - void flip() - { - array_.set(index_, !array_.test(index_)); - } - - private: - friend class VariableLengthArray; - - reference(VariableLengthArray& array, const size_type index) noexcept - : array_(array) - , index_(index) - { - } - - // The reference does not need to be copyable because URVO will elide the copy. - VariableLengthArray& array_; - const size_type index_; - }; - using const_reference = bool; - -private: - template - class IteratorImpl final - { - public: - using iterator_category = std::random_access_iterator_tag; - using value_type = typename A::value_type; - using difference_type = typename A::difference_type; - using reference = typename A::reference; - using const_reference = typename A::const_reference; - using pointer = void; - - IteratorImpl() noexcept = default; - - IteratorImpl& operator++() noexcept - { - ++index_; - return *this; - } - IteratorImpl operator++(int) noexcept - { - const IteratorImpl tmp(*this); - ++index_; - return tmp; - } - IteratorImpl& operator--() noexcept - { - --index_; - return *this; - } - IteratorImpl operator--(int) noexcept - { - IteratorImpl tmp(*this); - --index_; - return tmp; - } - - IteratorImpl& operator+=(const difference_type n) noexcept - { - index_ = static_cast(static_cast(index_) + n); - return *this; - } - IteratorImpl& operator-=(const difference_type n) noexcept - { - index_ = static_cast(static_cast(index_) - n); - return *this; - } - IteratorImpl operator+(const difference_type n) const noexcept - { - return IteratorImpl(*array_, static_cast(static_cast(index_) + n)); - } - IteratorImpl operator-(const difference_type n) const noexcept - { - return IteratorImpl(*array_, static_cast(static_cast(index_) - n)); - } - difference_type operator-(const IteratorImpl& other) const noexcept - { - return static_cast(index_) - static_cast(other.index_); - } - - /// - /// The return type may be either a reference or const_reference depending on whether this iterator is const - /// or mutable. - /// - auto operator*() -> decltype(std::declval()[0]) - { - return this->operator[](0); - } - const_reference operator*() const - { - return this->operator[](0); - } - - /// - /// The return type may be either a reference or const_reference depending on whether this iterator is const - /// or mutable. - /// - auto operator[](const difference_type n) -> decltype(std::declval()[n]) - { - return array_->operator[](static_cast(static_cast(index_) + n)); - } - const_reference operator[](const difference_type n) const - { - return array_->operator[](static_cast(static_cast(index_) + n)); - } - - bool operator==(const IteratorImpl& other) const noexcept - { - return array_ == other.array_ && index_ == other.index_; - } - bool operator!=(const IteratorImpl& other) const noexcept - { - return !((*this) == other); - } - bool operator<(const IteratorImpl& other) const noexcept - { - return index_ < other.index_; - } - bool operator>(const IteratorImpl& other) const noexcept - { - return index_ > other.index_; - } - bool operator<=(const IteratorImpl& other) const noexcept - { - return index_ <= other.index_; - } - bool operator>=(const IteratorImpl& other) const noexcept - { - return index_ >= other.index_; - } - - private: - friend class VariableLengthArray; - - IteratorImpl(A& array, const size_type index) noexcept - : array_(&array) - , index_(index) - { - } - - A* array_ = nullptr; - size_type index_ = 0; - }; - -public: - using iterator = IteratorImpl; - using const_iterator = IteratorImpl; - - constexpr VariableLengthArray() noexcept - : data_(nullptr) - , capacity_(0) - , size_(0) - , alloc_() - { - } - - VariableLengthArray(std::initializer_list l) noexcept( - noexcept(VariableLengthArray().reserve(1))) - : data_(nullptr) - , capacity_(0) - , size_(0) - , alloc_() - { - reserve(l.size()); - for (auto list_item : l) - { - push_back_impl(list_item); - } - } - - template - constexpr VariableLengthArray( - InputIt first, - InputIt last, - const size_type length, - const allocator_type& alloc = - allocator_type()) noexcept(noexcept(VariableLengthArray().reserve(1))) - : data_(nullptr) - , capacity_(0) - , size_(0) - , alloc_(alloc) - { - reserve(length); - for (size_t inserted = 0; first != last && inserted < length; ++first) - { - push_back_impl(*first); - } - } - - // - // Rule of Five. - // - VariableLengthArray(const VariableLengthArray& rhs) noexcept( - noexcept(VariableLengthArray().reserve(1))) - : data_(nullptr) - , capacity_(0) - , size_(0) - , alloc_(rhs.alloc_) - { - reserve(rhs.size()); - for (const_iterator list_item = rhs.cbegin(), end = rhs.cend(); list_item != end; ++list_item) - { - push_back_impl(*list_item); - } - } - - VariableLengthArray& operator=(const VariableLengthArray& rhs) noexcept( - noexcept(VariableLengthArray::fast_deallocate( - nullptr, - 0, - 0, - std::declval())) && noexcept(VariableLengthArray() - .reserve(1))) - { - if (this == &rhs) - { - return *this; - } - fast_deallocate(data_, size_, capacity_, alloc_); - data_ = nullptr; - capacity_ = 0; - size_ = 0; - alloc_ = rhs.alloc_; - reserve(rhs.size()); - for (const_iterator list_item = rhs.cbegin(), end = rhs.cend(); list_item != end; ++list_item) - { - push_back_impl(*list_item); - } - return *this; - } - - VariableLengthArray(VariableLengthArray&& rhs) noexcept - : data_(rhs.data_) - , capacity_(rhs.capacity_) - , size_(rhs.size_) - , alloc_(std::move(rhs.alloc_)) - { - rhs.data_ = nullptr; - rhs.capacity_ = 0; - rhs.size_ = 0; - } - - VariableLengthArray& operator=(VariableLengthArray&& rhs) noexcept( - noexcept(VariableLengthArray::fast_deallocate(nullptr, - 0, - 0, - std::declval()))) - { - fast_deallocate(data_, size_, capacity_, alloc_); - - alloc_ = std::move(rhs.alloc_); - - data_ = rhs.data_; - rhs.data_ = nullptr; - - capacity_ = rhs.capacity_; - rhs.capacity_ = 0; - - size_ = rhs.size_; - rhs.size_ = 0; - - return *this; - } - - ~VariableLengthArray() noexcept( - noexcept(VariableLengthArray::fast_deallocate(nullptr, - 0, - 0, - std::declval()))) - { - fast_deallocate(data_, size_, capacity_, alloc_); - } - - constexpr bool operator==(const VariableLengthArray& rhs) const noexcept - { - if (size() != rhs.size()) - { - return false; - } - if (data_ != rhs.data_) - { - const_iterator rnext = rhs.cbegin(); - const_iterator rend = rhs.cend(); - const_iterator lnext = cbegin(); - const_iterator lend = cend(); - for (; (rnext != rend) && (lnext != lend); ++lnext, ++rnext) - { - if ((*lnext) != (*rnext)) - { - return false; - } - } - if ((rnext != rend) || (lnext != lend)) - { - // One of the two iterators returned less then the size value? This is probably a bug but we can only - // say they are not equal here. - return false; - } - } - return true; - } - constexpr bool operator!=(const VariableLengthArray& rhs) const noexcept - { - return !(operator==(rhs)); - } - - /// - /// The maximum size (and capacity) of this array type. This size is derived - /// from the DSDL definition for a field and represents the maximum number of - /// elements allowed if the specified allocator is able to provide adequate - /// memory (i.e. there may be up to this many elements but there shall never - /// be more). - /// - static constexpr const size_type type_max_size = MaxSize; - - constexpr size_type max_size() const noexcept - { - return type_max_size; - } - - // +----------------------------------------------------------------------+ - // | ELEMENT ACCESS - // +----------------------------------------------------------------------+ - - /// - /// Returns true iff the bit at the specified position is set and the argument is within the range. - /// - constexpr bool test(const size_type pos) const noexcept - { - const auto idx = split_index(pos); - return (pos < size_) && ((data_[idx.first] & (1U << idx.second)) != 0); - } - - /// - /// Sets the bit at the specified position to the specified value. Does nothing if position is out of range. - /// - constexpr void set(const size_type pos, const bool value = true) noexcept - { - if (pos < size_) - { - const auto idx = split_index(pos); - if (value) - { - data_[idx.first] = static_cast(data_[idx.first] | (1U << idx.second)); - } - else - { - data_[idx.first] = static_cast(data_[idx.first] & (~(1U << idx.second))); - } - } - } - - /// - /// The returned iterators are invalidated by calls to {@code shrink_to_fit} and {@code reserve}. - /// - constexpr const_iterator cbegin() const noexcept - { - return const_iterator(*this, 0); - } - constexpr const_iterator cend() const noexcept - { - return const_iterator(*this, size_); - } - constexpr iterator begin() noexcept - { - return iterator(*this, 0); - } - constexpr iterator end() noexcept - { - return iterator(*this, size_); - } - constexpr const_iterator begin() const noexcept - { - return cbegin(); - } - constexpr const_iterator end() const noexcept - { - return cend(); - } - - /// - /// This is an alias for {@code test}. - /// - constexpr bool operator[](const size_type pos) const noexcept - { - return test(pos); - } - - /// - /// If {@code pos} is > {@code size} the behavior is undefined. - /// The returned reference is valid while this object is unless {@code reserve} or {@code shrink_to_fit} is called. - /// - constexpr reference operator[](const size_type pos) noexcept - { - return reference(*this, pos); - } - - /// - /// Throws std::out_of_range if {@code pos} is >= {@code size}; calls std::abort() if exceptions are not enabled. - /// The returned reference is valid while this object is unless {@code reserve} or {@code shrink_to_fit} is called. - /// - constexpr bool at(const size_type pos) const - { - if (pos >= size_) - { -#if __cpp_exceptions - throw std::out_of_range("VariableLengthArray::at"); -#else - std::abort(); -#endif - } - return test(pos); - } - constexpr reference at(const size_type pos) - { - if (pos >= size_) - { -#if __cpp_exceptions - throw std::out_of_range("VariableLengthArray::at"); -#else - std::abort(); -#endif - } - return this->operator[](pos); - } - - /// - /// STL-like access to a copy of the internal allocator. - /// - constexpr allocator_type get_allocator() const noexcept - { - return alloc_; - } - - /// - /// Provided to allow access to statically allocated buffers stored within - /// the allocator instance. - /// - constexpr const allocator_type& peek_allocator() const noexcept - { - return alloc_; - } - - // +----------------------------------------------------------------------+ - // | CAPACITY - // +----------------------------------------------------------------------+ - /// - /// The number of elements that can be stored in the array without further - /// allocations. This number will only grow through calls to {@code reserve} - /// and can shrink through calls to {@code shrink_to_fit}. This value shall - /// never exceed {@code max_size}. - /// - constexpr size_type capacity() const noexcept - { - return capacity_; - } - - /// - /// The current number of elements in the array. This number increases with each - /// successful call to {@code push_back} and decreases with each call to - /// {@code pop_back} (when size is > 0). - /// - constexpr size_type size() const noexcept - { - return size_; - } - - /// - /// Ensure enough memory is allocated to store at least the {@code desired_capacity} number of elements. - /// - /// @param desired_capacity The number of elements to allocate, but not initialize, memory for. - /// @return The new (or unchanged) capacity of this object. - /// - size_type reserve(const size_type desired_capacity) noexcept(noexcept(allocator_type().allocate(0)) && noexcept( - VariableLengthArray::move_and_free(nullptr, - nullptr, - 0, - 0, - std::declval()))) - { - const size_type no_shrink_capacity = std::max(std::min(round8(desired_capacity), type_max_size), size_); - Storage* new_data = nullptr; -#if __cpp_exceptions - try - { -#endif - new_data = alloc_.allocate(bits2bytes(no_shrink_capacity)); -#if __cpp_exceptions - } catch (const std::bad_alloc& e) - { - (void) e; - } -#endif - if (new_data != nullptr) - { - if (new_data != data_) - { - std::fill_n(new_data, bits2bytes(no_shrink_capacity), 0); - move_and_free(new_data, data_, size_, capacity_, alloc_); - } - else // the allocator was able to simply extend the reserved area for the same memory pointer. - { - std::fill_n(data_ + bits2bytes(capacity_), (no_shrink_capacity - capacity_) / 8U, 0); - } - data_ = new_data; - capacity_ = no_shrink_capacity; - } - return capacity_; - } - - /// - /// Deallocate or reallocate memory such that not more than {@code size()} elements can be stored in this object. - /// - /// @return True if nothing was done or if memory was successfully resized. False to indicate that the allocator - /// could not provide enough memory to move the existing objects to a smaller allocation. - /// - bool shrink_to_fit() noexcept( - noexcept(allocator_type().deallocate(nullptr, 0)) && noexcept(allocator_type().allocate(0)) && noexcept( - VariableLengthArray::move_and_free(nullptr, - nullptr, - 0, - 0, - std::declval()))) - { - if (size_ == capacity_) - { - return true; - } - if (size_ == 0) - { - alloc_.deallocate(data_, bits2bytes(capacity_)); - data_ = nullptr; - capacity_ = 0; - return true; - } - Storage* minimized_data = nullptr; -#if __cpp_exceptions - try - { -#endif - minimized_data = alloc_.allocate(bits2bytes(size_)); -#if __cpp_exceptions - } catch (const std::bad_alloc& e) - { - (void) e; - } -#endif - if (minimized_data == nullptr) - { - return false; - } - if (minimized_data != data_) - { - std::fill_n(minimized_data, bits2bytes(size_), 0); // Avoid uninitialized reads during modification - move_and_free(minimized_data, data_, size_, capacity_, alloc_); - } // else the allocator was able to simply shrink the reserved area for the same memory pointer. - data_ = minimized_data; - capacity_ = size_; - return true; - } - - // +----------------------------------------------------------------------+ - // | MODIFIERS - // +----------------------------------------------------------------------+ - /// - /// Construct a new element on to the back of the array. Grows size by 1 and may grow capacity. - /// - /// If exceptions are disabled the caller must check before and after to see if the size grew to determine success. - /// If using exceptions this method throws {@code std::length_error} if the size of this collection is at capacity - /// or {@code std::bad_alloc} if the allocator failed to provide enough memory. - /// - /// If exceptions are disabled use the following logic: - /// - /// const size_t size_before = my_array.size(); - /// my_array.push_back(); - /// if (size_before == my_array.size()) - /// { - /// // failure - /// if (size_before == my_array.max_size()) - /// { - /// // length_error: you probably should have checked this first. - /// } - /// else - /// { - /// // bad_alloc: out of memory - /// } - // } // else, success. - /// - /// @throw std::length_error if the size of this collection is at capacity. - /// @throw std::bad_alloc if memory was needed and none could be allocated. - /// - constexpr void push_back(const bool value = false) - { - if (!ensure_size_plus_one()) - { - return; - } - if (!push_back_impl(value)) - { -#if __cpp_exceptions - throw std::length_error("max_size is reached, the array cannot grow further"); -#endif - } - } - - /// - /// Remove and destroy the last item in the array. This reduces the array size by 1 unless - /// the array is already empty. - /// - constexpr void pop_back() noexcept - { - if (size_ > 0) - { - size_--; - } - } - -private: - constexpr bool ensure_size_plus_one() - { - if (size_ < capacity_) - { - return true; - } - if (capacity_ == MaxSize) - { -#if __cpp_exceptions - throw std::length_error("Already at max size. Cannot increase capacity."); -#else - return false; -#endif - } - // https://stackoverflow.com/questions/1100311/what-is-the-ideal-growth-rate-for-a-dynamically-allocated-array/20481237#20481237 - const size_type capacity_before = capacity_; - const size_type half_capacity = capacity_before / 2U; - // That is, instead of a capacity sequence of 1, 2, 3, 4, 6, 9 we start from zero as 2, 4, 6, 9. The first - // opportunity for reusing previously freed memory comes when increasing to 19 from 13 since E(n-1) == 21. - const size_type new_capacity = capacity_before + ((half_capacity <= 1) ? 2 : half_capacity); - reserve(std::min(new_capacity, MaxSize)); - if (capacity_before == capacity_) - { -#if __cpp_exceptions - throw std::bad_alloc(); -#else - return false; -#endif - } - return true; - } - - constexpr bool push_back_impl(const bool value) noexcept - { - if (size_ < capacity_) - { - const size_type index = size_++; - set(index, value); - return true; - } - return false; - } - - static constexpr void fast_deallocate(Storage* const src, - const size_type src_size_count, - const size_type src_capacity_count, - allocator_type& alloc) noexcept(noexcept(allocator_type().deallocate(nullptr, - 0))) - { - (void) src_size_count; - alloc.deallocate(src, bits2bytes(src_capacity_count)); - } - - static constexpr void move_and_free( - Storage* const dst, - Storage* const src, - size_type src_len_count, - size_type src_capacity_count, - allocator_type& alloc) noexcept(noexcept(fast_deallocate(nullptr, 0, 0, std::declval()))) - { - if (src_len_count > 0) - { - std::memcpy(dst, src, bits2bytes(src_len_count)); // The allocator returned new memory. - } - fast_deallocate(src, src_len_count, src_capacity_count, alloc); - } - - constexpr static std::pair split_index(const size_type index) noexcept - { - return std::make_pair(index / 8U, index % 8U); - } - - constexpr static size_type round8(const size_type value) noexcept - { - return (value + 7U) & ~7U; - } - constexpr static size_type bits2bytes(const size_type value) noexcept - { - return round8(value) / 8U; - } - - // +----------------------------------------------------------------------+ - // | DATA MEMBERS - // +----------------------------------------------------------------------+ - Storage* data_; - size_type capacity_; - size_type size_; - allocator_type alloc_; -}; - -// required till C++ 17. Redundant but allowed after that. -template -const std::size_t VariableLengthArray::type_max_size; - -} // namespace support -} // namespace nunavut - -#endif // NUNAVUT_SUPPORT_VARIABLE_LENGTH_ARRAY_HPP_INCLUDED diff --git a/src/nunavut/lang/cpp/templates/_composite_type.j2 b/src/nunavut/lang/cpp/templates/_composite_type.j2 index 5ffcd640..be64bc94 100644 --- a/src/nunavut/lang/cpp/templates/_composite_type.j2 +++ b/src/nunavut/lang/cpp/templates/_composite_type.j2 @@ -31,8 +31,69 @@ {% if composite_type.deprecated -%} [[deprecated("{{ composite_type }} is reaching the end of its life; there may be a newer version available")]] {% endif -%} -struct {{composite_type|short_reference_name}} final +template > +struct {{composite_type|short_reference_name}}_Impl final { + // Default constructor + {{composite_type|short_reference_name}}_Impl(const Allocator& allocator = Allocator()){%if composite_type.fields_except_padding %} :{%endif%} + {%- for field in composite_type.fields_except_padding %} + {{ field | id }}{{ field | value_initializer(SpecialMethods.DefaultConstructorWithOptionalAllocator) }}{%if not loop.last %},{%endif %} + {%- endfor %} + { } + + // Destructor + ~{{composite_type|short_reference_name}}_Impl() = default; + + // Copy constructor + {{composite_type|short_reference_name}}_Impl(const {{composite_type|short_reference_name}}_Impl& rhs){%if composite_type.fields_except_padding %} :{%endif%} + {%- for field in composite_type.fields_except_padding %} + {{ field | id }}{{ field | value_initializer(SpecialMethods.CopyConstructor) }}{%if not loop.last %},{%endif %} + {%- endfor %} + { + } + + // Copy constructor with allocator + {{composite_type|short_reference_name}}_Impl(const {{composite_type|short_reference_name}}_Impl& rhs, const Allocator& allocator){%if composite_type.fields_except_padding %} :{%endif%} + {%- for field in composite_type.fields_except_padding %} + {{ field | id }}{{ field | value_initializer(SpecialMethods.CopyConstructorWithAllocator) }}{%if not loop.last %},{%endif %} + {%- endfor %} + { + } + + // Move constructor + {{composite_type|short_reference_name}}_Impl({{composite_type|short_reference_name}}_Impl&& rhs){%if composite_type.fields_except_padding %} :{%endif%} + {%- for field in composite_type.fields_except_padding %} + {{ field | id }}{{ field | value_initializer(SpecialMethods.MoveConstructor) }}{%if not loop.last %},{%endif %} + {%- endfor %} + { + } + + // Move constructor with allocator + {{composite_type|short_reference_name}}_Impl({{composite_type|short_reference_name}}_Impl&& rhs, const Allocator& allocator){%if composite_type.fields_except_padding %} :{%endif%} + {%- for field in composite_type.fields_except_padding %} + {{ field | id }}{{ field | value_initializer(SpecialMethods.MoveConstructorWithAllocator) }}{%if not loop.last %},{%endif %} + {%- endfor %} + { + } + + // Copy assignment + {{composite_type|short_reference_name}}_Impl& operator=(const {{composite_type|short_reference_name}}_Impl& rhs) + { + {%- for field in composite_type.fields_except_padding %} + {{ field | id }} = rhs.{{ field | id }}; + {%- endfor %} + return *this; + } + + // Move assignment + {{composite_type|short_reference_name}}_Impl& operator=({{composite_type|short_reference_name}}_Impl&& rhs) + { + {%- for field in composite_type.fields_except_padding %} + {{ field | id }} = std::move(rhs.{{ field | id }}); + {%- endfor %} + return *this; + } + struct _traits_ // The name is surrounded with underscores to avoid collisions with DSDL attributes. { _traits_() = delete; @@ -74,7 +135,7 @@ struct {{composite_type|short_reference_name}} final { TypeOf() = delete; {%- endif %} - using {{field.name|id}} = {{ field.data_type | declaration }}; + using {{field.name|id}} = {{ field.data_type | field_declaration }}; {%- if loop.last %} }; {%- endif %} @@ -129,15 +190,19 @@ struct {{composite_type|short_reference_name}} final {%- endif %} }; +using {{composite_type|short_reference_name}} = {{composite_type|short_reference_name}}_Impl>; + {% if not nunavut.support.omit %} -inline nunavut::support::SerializeResult serialize(const {{composite_type|short_reference_name}}& obj, +template +inline nunavut::support::SerializeResult serialize(const {{composite_type|short_reference_name}}_Impl& obj, nunavut::support::bitspan out_buffer) { {% from 'serialization.j2' import serialize -%} {{ serialize(composite_type) | trim | remove_blank_lines }} } -inline nunavut::support::SerializeResult deserialize({{composite_type|short_reference_name}}& obj, +template +inline nunavut::support::SerializeResult deserialize({{composite_type|short_reference_name}}_Impl& obj, nunavut::support::const_bitspan in_buffer) { {% from 'deserialization.j2' import deserialize -%} diff --git a/src/nunavut/lang/cpp/templates/_fields.j2 b/src/nunavut/lang/cpp/templates/_fields.j2 index efa60058..a6c0991b 100644 --- a/src/nunavut/lang/cpp/templates/_fields.j2 +++ b/src/nunavut/lang/cpp/templates/_fields.j2 @@ -10,5 +10,5 @@ // +----------------------------------------------------------------------+ {% endif -%} {{ field.doc | block_comment('cpp-doxygen', 4, 120) }} - _traits_::TypeOf::{{field.name|id}} {{ field | id }}{{ field.data_type | default_value_initializer }}; + typename _traits_::TypeOf::{{field.name|id}} {{ field | id }}; {%- endfor -%} diff --git a/src/nunavut/lang/properties.yaml b/src/nunavut/lang/properties.yaml index fdbafe53..8b9e5775 100644 --- a/src/nunavut/lang/properties.yaml +++ b/src/nunavut/lang/properties.yaml @@ -312,10 +312,11 @@ nunavut.lang.cpp: enable_override_variable_array_capacity: false std: c++14 # Provide non-empty values to override the type used for variable-length arrays in C++ types. - variable_array_type_template: "" - variable_array_type_include: "" + variable_array_type_include: "" + variable_array_type_template: "std::vector<{TYPE}, typename std::allocator_traits::template rebind_alloc<{TYPE}>>" + variable_array_type_init_args_template: "" + variable_array_type_allocator_type: "std::allocator" cast_format: "static_cast<{type}>({value})" - enable_allocator_support: false nunavut.lang.py: diff --git a/submodules/CETL b/submodules/CETL new file mode 160000 index 00000000..64209d50 --- /dev/null +++ b/submodules/CETL @@ -0,0 +1 @@ +Subproject commit 64209d50290b5060095554ef544a8ea7c3245e75 diff --git a/test/gettest_properties/test_properties.py b/test/gettest_properties/test_properties.py index dbd22ab2..3bfa547a 100644 --- a/test/gettest_properties/test_properties.py +++ b/test/gettest_properties/test_properties.py @@ -38,8 +38,8 @@ def test_issue_277(gen_paths, variable_array_type_template: str, variable_array_ vla_decl_pattern = re.compile(r"\b|^{}\B".format(variable_array_type_template.format(TYPE="std::uint8_t"))) vla_include_pattern = re.compile(r"\B#include\s+{}\B".format(variable_array_type_include)) else: - vla_decl_pattern = re.compile(r"\b|^nunavut::support::VariableLengthArray\B") - vla_include_pattern = re.compile(r"\B#include\s+\"nunavut/support/variable_length_array\.hpp\"\B") + vla_decl_pattern = re.compile(r"\b|^std::vectortypename std::allocator_traits::template rebind_alloc\B") + vla_include_pattern = re.compile(r"\B#include\s+\" + variable_array_type_template: "cetl::VariableLengthArray<{TYPE}, typename std::allocator_traits::template rebind_alloc<{TYPE}>>" + variable_array_type_init_args_template: "{SIZE}" + variable_array_type_allocator_type: "cetl::pf17::pmr::polymorphic_allocator" diff --git a/verification/cpp/suite/test_cetl_vla_pmr.cpp b/verification/cpp/suite/test_cetl_vla_pmr.cpp new file mode 100644 index 00000000..a8c22eda --- /dev/null +++ b/verification/cpp/suite/test_cetl_vla_pmr.cpp @@ -0,0 +1,101 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Authors: Skeets Norquist + * Sanity tests. + */ +#include "gmock/gmock.h" +#include "cetl/pf17/byte.hpp" +#include "cetl/pf17/memory_resource.hpp" +#include "cetl/pf17/sys/memory_resource.hpp" +#include "mymsgs/Inner_1_0.hpp" +#include "mymsgs/Outer_1_0.hpp" + +/** + * Inner message using cetl::pf17::pmr::polymorphic_allocator + */ +TEST(CetlVlaPmrTests, TestInner) { + std::array buffer{}; + cetl::pf17::pmr::monotonic_buffer_resource mbr{buffer.data(), buffer.size(), cetl::pf17::pmr::null_memory_resource()}; + cetl::pf17::pmr::polymorphic_allocator pa{&mbr}; + + mymsgs::Inner_1_0 inner{pa}; + inner.inner_items.reserve(5); + for (std::uint32_t i = 0; i < 5; i++) { + inner.inner_items.push_back(i); + } + for (std::uint32_t i = 0; i < 5; i++) { + ASSERT_EQ(i, *reinterpret_cast(buffer.data()+(sizeof(std::uint32_t)*i))); + } +} + +/** + * Outer message using cetl::pf17::pmr::polymorphic_allocator + */ +TEST(CetlVlaPmrTests, TestOuter) { + std::array buffer{}; + cetl::pf17::pmr::monotonic_buffer_resource mbr{buffer.data(), buffer.size(), cetl::pf17::pmr::null_memory_resource()}; + cetl::pf17::pmr::polymorphic_allocator pa{&mbr}; + + // Fill the data: inner first, then outer + mymsgs::Outer_1_0 outer{pa}; + outer.inner.inner_items.reserve(5); + for (std::uint32_t i = 0; i < 5; i++) { + outer.inner.inner_items.push_back(i); + } + outer.outer_items.reserve(8); + for (float i = 0; i < 8; i++) { + outer.outer_items.push_back(i + 5.0f); + } + + // Verify that the inner is followed by the outer data in the buffer + cetl::pf17::byte* data = buffer.data(); + for (std::uint32_t i = 0; i < 13; i++) { + if (i < 5) { + ASSERT_EQ(i, *reinterpret_cast(data)); + data += sizeof(std::uint32_t); + } else { + ASSERT_EQ(static_cast(i), *reinterpret_cast(data)); + data += sizeof(float); + } + } +} + +/** + * Serialization roundtrip using cetl::pf17::pmr::polymorphic_allocator + */ +TEST(CetlVlaPmrTests, SerializationRoundtrip) { + std::array buffer{}; + cetl::pf17::pmr::monotonic_buffer_resource mbr{buffer.data(), buffer.size(), cetl::pf17::pmr::null_memory_resource()}; + cetl::pf17::pmr::polymorphic_allocator pa{&mbr}; + + // Fill the data: inner first, then outer + mymsgs::Outer_1_0 outer1{pa}; + outer1.inner.inner_items.reserve(5); + for (std::uint32_t i = 0; i < 5; i++) { + outer1.inner.inner_items.push_back(i); + } + outer1.outer_items.reserve(8); + for (float i = 0; i < 8; i++) { + outer1.outer_items.push_back(i + 5.0f); + } + + // Serialize it + std::array roundtrip_buffer{}; + nunavut::support::bitspan ser_buffer(roundtrip_buffer); + const auto ser_result = serialize(outer1, ser_buffer); + ASSERT_TRUE(ser_result); + + // Deserialize it + mymsgs::Outer_1_0 outer2{pa}; + nunavut::support::const_bitspan des_buffer(static_cast(roundtrip_buffer.data()), roundtrip_buffer.size()); + const auto des_result = deserialize(outer2, des_buffer); + ASSERT_TRUE(des_result); + + // Verify that the messages match + for (std::uint32_t i = 0; i < 5; i++) { + ASSERT_EQ(outer1.inner.inner_items[i], outer2.inner.inner_items[i]); + } + for (std::uint32_t i = 0; i < 8; i++) { + ASSERT_EQ(outer1.outer_items[i], outer2.outer_items[i]); + } +} diff --git a/verification/cpp/suite/test_var_len_arr.cpp b/verification/cpp/suite/test_var_len_arr.cpp deleted file mode 100644 index fc705aa3..00000000 --- a/verification/cpp/suite/test_var_len_arr.cpp +++ /dev/null @@ -1,809 +0,0 @@ -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Validates the VariableLengthArray container type using various allocators. - */ - -#include "gmock/gmock.h" -#include "nunavut/support/variable_length_array.hpp" -#include -#include -#include -#include "o1heap/o1heap.h" -#include -#include -#include - -/** - * Used to test that destructors were called. - */ -class Doomed -{ -public: - Doomed(int* out_signal_dtor) - : out_signal_dtor_(out_signal_dtor) - , moved_(false) - { - } - Doomed(Doomed&& from) noexcept - : out_signal_dtor_(from.out_signal_dtor_) - , moved_(false) - { - from.moved_ = true; - } - Doomed(const Doomed&) = delete; - Doomed& operator=(const Doomed&) = delete; - Doomed& operator=(Doomed&&) = delete; - - ~Doomed() - { - if (!moved_) - { - (*out_signal_dtor_) += 1; - } - } - -private: - int* out_signal_dtor_; - bool moved_; -}; - -/** - * Pavel's O(1) Heap Allocator wrapped in an std::allocator concept. - * - * Note that this implementation probably wouldn't work in a real application - * because it is not copyable. - */ -template -class O1HeapAllocator -{ -public: - using value_type = T; - template struct rebind final { using other = O1HeapAllocator; }; - - O1HeapAllocator() - : heap_() - , heap_alloc_(o1heapInit(&heap_[0], SizeCount * sizeof(T), nullptr, nullptr)) - { - if (nullptr == heap_alloc_) - { - std::abort(); // Test environment is broken. Maybe the alignment is bad or arena too small? - } - } - - O1HeapAllocator(const O1HeapAllocator&) = delete; - O1HeapAllocator& operator=(const O1HeapAllocator&) = delete; - O1HeapAllocator(O1HeapAllocator&&) = delete; - O1HeapAllocator& operator=(O1HeapAllocator&&) = delete; - - T* allocate(std::size_t n) - { - return reinterpret_cast(o1heapAllocate(heap_alloc_, n * sizeof(T))); - } - - constexpr void deallocate(T* p, std::size_t n) - { - (void) n; - o1heapFree(heap_alloc_, p); - } - -private: - typename std::aligned_storage::type heap_[SizeCount]; - O1HeapInstance* heap_alloc_; -}; - -/** - * A Junky static allocator. - */ -template -class JunkyStaticAllocator -{ -public: - using value_type = T; - using array_constref_type = const T (&)[SizeCount]; - template struct rebind final { using other = JunkyStaticAllocator; }; - - JunkyStaticAllocator() - : data_() - , alloc_count_(0) - , last_alloc_size_(0) - , last_dealloc_size_(0) - { - } - - JunkyStaticAllocator(const JunkyStaticAllocator& rhs) - : data_() - , alloc_count_(rhs.alloc_count_) - , last_alloc_size_(rhs.last_alloc_size_) - , last_dealloc_size_(rhs.last_dealloc_size_) - { - } - - T* allocate(std::size_t n) - { - if (n <= SizeCount) - { - ++alloc_count_; - last_alloc_size_ = n; - return reinterpret_cast(&data_[0]); - } - else - { - return nullptr; - } - } - - constexpr void deallocate(T* p, std::size_t n) - { - // This allocator is junk. - if (p == reinterpret_cast(&data_[0])) - { - last_dealloc_size_ = n; - } - } - - std::size_t get_last_alloc_size() const - { - return last_alloc_size_; - } - - std::size_t get_alloc_count() const - { - return alloc_count_; - } - - std::size_t get_last_dealloc_size() const - { - return last_dealloc_size_; - } - - operator array_constref_type() const - { - return data_; - } - -private: - T data_[SizeCount]; - std::size_t alloc_count_; - std::size_t last_alloc_size_; - std::size_t last_dealloc_size_; -}; - -// +----------------------------------------------------------------------+ -/** - * Test suite for running multiple allocators against the variable length array type. - */ -template -class VLATestsGeneric : public ::testing::Test -{ -}; - -namespace -{ -static constexpr std::size_t VLATestsGeneric_MinMaxSize = 32; -static constexpr std::size_t VLATestsGeneric_O1HeapSize = O1HEAP_ALIGNMENT << 5; - -static_assert(VLATestsGeneric_O1HeapSize > VLATestsGeneric_MinMaxSize, "Unexpected test environment encountered."); - -} // end anonymous namespace - -using VLATestsGenericAllocators = ::testing::Types, - nunavut::support::MallocAllocator, - std::allocator, - std::allocator, - std::allocator, - O1HeapAllocator, - O1HeapAllocator, - JunkyStaticAllocator, - JunkyStaticAllocator>; -TYPED_TEST_SUITE(VLATestsGeneric, VLATestsGenericAllocators, ); - -TYPED_TEST(VLATestsGeneric, TestReserve) -{ - static_assert(10 < VLATestsGeneric_MinMaxSize, - "Test requires max size of array is less than max size of the smallest allocator"); - nunavut::support::VariableLengthArray subject; - ASSERT_EQ(0U, subject.capacity()); - ASSERT_EQ(0U, subject.size()); - ASSERT_EQ(10U, subject.max_size()); - - const auto reserved = subject.reserve(1); - ASSERT_LE(1U, reserved); - ASSERT_EQ(reserved, subject.capacity()); - ASSERT_EQ(0U, subject.size()); - ASSERT_EQ(10U, subject.max_size()); -} - -TYPED_TEST(VLATestsGeneric, TestPush) -{ - nunavut::support::VariableLengthArray - subject; - ASSERT_EQ(0U, subject.size()); - - typename TypeParam::value_type x = 0; - for (std::size_t i = 0; i < VLATestsGeneric_MinMaxSize; ++i) - { - subject.push_back(x); - - ASSERT_EQ(i + 1, subject.size()); - ASSERT_LE(subject.size(), subject.capacity()); - - ASSERT_EQ(x, subject[i]); - x = x + 1; - } -} - -TYPED_TEST(VLATestsGeneric, TestPop) -{ - static_assert(20 < VLATestsGeneric_MinMaxSize, - "Test requires max size of array is less than max size of the smallest allocator"); - nunavut::support::VariableLengthArray subject; - const auto reserved = subject.reserve(10); - ASSERT_LE(10U, reserved); - subject.push_back(1); - ASSERT_EQ(1U, subject.size()); - ASSERT_EQ(1, subject[0]); - ASSERT_EQ(1U, subject.size()); - subject.pop_back(); - ASSERT_EQ(0U, subject.size()); - ASSERT_EQ(reserved, subject.capacity()); -} - -TYPED_TEST(VLATestsGeneric, TestShrink) -{ - static_assert(20 < VLATestsGeneric_MinMaxSize, - "Test requires max size of array is less than max size of the smallest allocator"); - nunavut::support::VariableLengthArray subject; - const auto reserved = subject.reserve(10); - ASSERT_LE(10U, reserved); - subject.push_back(1); - ASSERT_EQ(1U, subject.size()); - ASSERT_EQ(1, subject[0]); - ASSERT_EQ(1U, subject.size()); - ASSERT_EQ(reserved, subject.capacity()); - subject.shrink_to_fit(); - ASSERT_EQ(1U, subject.capacity()); -} - -// +----------------------------------------------------------------------+ -/** - * Test suite for running static allocators against the variable length array type. - */ -template -class VLATestsStatic : public ::testing::Test -{ -}; -using VLATestsStaticAllocators = ::testing::Types< - O1HeapAllocator, - O1HeapAllocator, - JunkyStaticAllocator ->; -TYPED_TEST_SUITE(VLATestsStatic, VLATestsStaticAllocators, ); - -TYPED_TEST(VLATestsStatic, TestOutOfMemory) -{ - nunavut::support:: - VariableLengthArray::max(), TypeParam> - subject; - ASSERT_EQ(0U, subject.capacity()); - - bool did_run_out_of_memory = false; - std::size_t ran_out_of_memory_at = 0; - for (std::size_t i = 1; i <= 1024; ++i) - { - ASSERT_EQ(i - 1, subject.size()); - if (subject.reserve(i) < i) - { - did_run_out_of_memory = true; - ran_out_of_memory_at = i; - break; - } - ASSERT_LE(i, subject.capacity()); - subject.push_back(static_cast(i)); - ASSERT_EQ(i, subject.size()); - ASSERT_EQ(static_cast(i), subject[i - 1]); - } - ASSERT_TRUE(did_run_out_of_memory); - const std::size_t size_before = subject.size(); - try - { - subject.push_back(0); - } catch (const std::length_error& e) - { - std::cerr << e.what() << '\n'; - } catch (const std::bad_alloc& e) - { - std::cerr << e.what() << '\n'; - } - - ASSERT_EQ(size_before, subject.size()); - for (std::size_t i = 1; i < ran_out_of_memory_at; ++i) - { - ASSERT_EQ(static_cast(i), subject[i - 1]); - } -} - -TYPED_TEST(VLATestsStatic, TestOverMaxSize) -{ - static constexpr std::size_t MaxSize = 5; - static_assert(MaxSize > 0, "Test assumes MaxSize > 0"); - nunavut::support::VariableLengthArray subject; - ASSERT_EQ(0U, subject.capacity()); - - for (std::size_t i = 1; i <= MaxSize; ++i) - { - ASSERT_LE(i, subject.reserve(i)); - subject.push_back(static_cast(i)); - ASSERT_EQ(i, subject.size()); - ASSERT_EQ(static_cast(i), subject[i - 1]); - } - ASSERT_EQ(MaxSize, subject.reserve(MaxSize + 1)); - - ASSERT_EQ(MaxSize, subject.size()); - try - { - subject.push_back(0); - } catch (const std::length_error& e) - { - std::cerr << e.what() << '\n'; - } catch (const std::bad_alloc& e) - { - std::cerr << e.what() << '\n'; - } - - ASSERT_EQ(MaxSize, subject.size()); - for (std::size_t i = 0; i < MaxSize; ++i) - { - ASSERT_EQ(static_cast(i + 1), subject[i]); - } -} - -// +----------------------------------------------------------------------+ -/** - * Test suite to ensure non-trivial objects are properly handled. This one is both for bool and non-bool spec. - */ -template -class VLATestsNonTrivialCommon : public ::testing::Test -{ -}; -using VLATestsNonTrivialCommonTypes = ::testing::Types; -TYPED_TEST_SUITE(VLATestsNonTrivialCommon, VLATestsNonTrivialCommonTypes, ); - -TYPED_TEST(VLATestsNonTrivialCommon, TestMoveToVector) -{ - nunavut::support::VariableLengthArray> subject; - ASSERT_EQ(decltype(subject)::type_max_size, subject.reserve(decltype(subject)::type_max_size)); - for (std::size_t i = 0; i < decltype(subject)::type_max_size; ++i) - { - subject.push_back(static_cast(i % 3)); - ASSERT_EQ(i + 1, subject.size()); - } - std::vector a(subject.cbegin(), subject.cend()); - for (std::size_t i = 0; i < decltype(subject)::type_max_size; ++i) - { - ASSERT_EQ(static_cast(i % 3), a[i]); - } -} - -TYPED_TEST(VLATestsNonTrivialCommon, TestPushBackGrowsCapacity) -{ - static constexpr std::size_t MaxSize = 9; - nunavut::support::VariableLengthArray subject; - ASSERT_EQ(0U, subject.size()); - ASSERT_EQ(0U, subject.capacity()); - for (std::size_t i = 0; i < MaxSize; ++i) - { - ASSERT_EQ(i, subject.size()); - ASSERT_LE(i, subject.capacity()); - subject.push_back(static_cast(i)); - ASSERT_EQ(i + 1, subject.size()); - ASSERT_LE(i + 1, subject.capacity()); - } - ASSERT_EQ(MaxSize, subject.size()); - ASSERT_EQ(MaxSize, subject.capacity()); -} - -TYPED_TEST(VLATestsNonTrivialCommon, TestForEachConstIterators) -{ - static constexpr std::size_t MaxSize = 9; - nunavut::support::VariableLengthArray subject; - auto& const_subject = subject; - ASSERT_EQ(0U, const_subject.size()); - ASSERT_EQ(0U, const_subject.capacity()); - for (const auto& item: const_subject) // Requires begin() const, end() const. - { - (void) item; - FAIL(); - } - ASSERT_EQ(0U, const_subject.size()); - ASSERT_EQ(0U, const_subject.capacity()); - for (std::size_t i = 0; i < MaxSize; ++i) - { - ASSERT_EQ(i, const_subject.size()); - ASSERT_LE(i, const_subject.capacity()); - subject.push_back(static_cast(i)); - ASSERT_EQ(i + 1, const_subject.size()); - ASSERT_LE(i + 1, const_subject.capacity()); - } - ASSERT_EQ(MaxSize, const_subject.size()); - ASSERT_EQ(MaxSize, const_subject.capacity()); - std::size_t i = 0; - for (const auto& item : const_subject) // Requires begin() const, end() const. - { - ASSERT_EQ(static_cast(i), item); - ++i; - } - ASSERT_EQ(MaxSize, i); -} - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wself-assign-overloaded" -#endif - -TYPED_TEST(VLATestsNonTrivialCommon, SelfAssignment) -{ - nunavut::support::VariableLengthArray subject; - subject.push_back(0); - subject.push_back(1); - ASSERT_EQ(2U, subject.size()); - subject = subject; - ASSERT_EQ(2U, subject.size()); - ASSERT_EQ(0, subject[0]); - ASSERT_EQ(1, subject[1]); -} - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif - -// +----------------------------------------------------------------------+ -/** - * Test suite to ensure non-trivial objects are properly handled. This one contains non-generic cases. - */ - -TEST(VLATestsNonTrivialSpecific, TestBoolReference) -{ - nunavut::support::VariableLengthArray array; - ASSERT_EQ(0, array.size()); - array.push_back(true); - ASSERT_EQ(1, array.size()); - ASSERT_TRUE(array[0]); - array.push_back(false); - ASSERT_EQ(2, array.size()); - ASSERT_FALSE(array[1]); - array.push_back(true); - ASSERT_EQ(3, array.size()); - ASSERT_TRUE(array[2]); - ASSERT_FALSE(array[1]); - ASSERT_TRUE(array[0]); - ASSERT_TRUE(!array[1]); - ASSERT_FALSE(!array[0]); - ASSERT_TRUE(~array[1]); - ASSERT_FALSE(~array[0]); - ASSERT_TRUE(array[0] == array[2]); - ASSERT_TRUE(array[0] != array[1]); - array[0] = array[1]; - ASSERT_FALSE(array[0]); - ASSERT_FALSE(array[1]); -} - -TEST(VLATestsNonTrivialSpecific, TestBoolIterator) -{ - nunavut::support::VariableLengthArray foo{ - {false, true, false, false, true, true, false, true, true, false} - }; - ASSERT_EQ(+10, (foo.end() - foo.begin())); - ASSERT_EQ(-10, (foo.begin() - foo.end())); - auto a = foo.begin(); - auto b = foo.begin(); - // Comparison - ASSERT_TRUE(a == b); - ASSERT_FALSE(a != b); - ASSERT_TRUE(a <= b); - ASSERT_TRUE(a >= b); - ASSERT_FALSE(a < b); - ASSERT_FALSE(a > b); - ++a; - ASSERT_FALSE(a == b); - ASSERT_TRUE(a != b); - ASSERT_FALSE(a <= b); - ASSERT_TRUE(a >= b); - ASSERT_FALSE(a < b); - ASSERT_TRUE(a > b); - ++b; - ASSERT_TRUE(a == b); - ASSERT_FALSE(a != b); - ASSERT_TRUE(a <= b); - ASSERT_TRUE(a >= b); - ASSERT_FALSE(a < b); - ASSERT_FALSE(a > b); - // Test the iterator traits - ASSERT_TRUE((std::is_same::iterator_category, - std::random_access_iterator_tag>::value)); - ASSERT_TRUE((std::is_same::value_type, bool>::value)); - ASSERT_TRUE((std::is_same::difference_type, std::ptrdiff_t>::value)); - // Test the iterator operations - ASSERT_EQ(0, a - b); - ASSERT_EQ(0, b - a); - ASSERT_EQ(0, a - a); - ASSERT_EQ(0, b - b); - ASSERT_EQ(1, a - foo.begin()); - ASSERT_EQ(1, b - foo.begin()); - ASSERT_EQ(-1, foo.begin() - b); - ASSERT_EQ(-1, foo.begin() - a); - ASSERT_EQ(1, a - foo.begin()); - ASSERT_EQ(1, b - foo.begin()); - // Augmented assignment - a += 1; - ASSERT_EQ(1, a - b); - ASSERT_EQ(-1, b - a); - b -= 1; - ASSERT_EQ(2, a - b); - ASSERT_EQ(2, a - foo.begin()); - ASSERT_EQ(0, b - foo.begin()); - // Inc/dec - ASSERT_EQ(2, (a++) - b); - ASSERT_EQ(3, a - b); - ASSERT_EQ(3, (a--) - b); - ASSERT_EQ(2, a - b); - ASSERT_EQ(3, (++a) - b); - ASSERT_EQ(3, a - b); - ASSERT_EQ(2, (--a) - b); - ASSERT_EQ(2, a - b); - // Add/sub - ASSERT_EQ(4, (a + 2) - b); - ASSERT_EQ(0, (a - 2) - b); - // Value access - ASSERT_EQ(2, a - foo.begin()); - ASSERT_EQ(0, b - foo.begin()); - ASSERT_EQ(false, *a); - ASSERT_EQ(false, *b); - ASSERT_EQ(true, a[-1]); - ASSERT_EQ(true, b[5]); - *a = true; - b[5] = false; - ASSERT_EQ(true, *a); - ASSERT_EQ(false, b[5]); - // Flip bit. - ASSERT_EQ(false, a[7]); - ASSERT_EQ(true, foo[7]); - a[7].flip(); - foo[7].flip(); - ASSERT_EQ(true, a[7]); - ASSERT_EQ(false, foo[7]); - // Check the final state. - ASSERT_EQ(10, foo.size()); - ASSERT_EQ(10, foo.capacity()); - ASSERT_EQ(false, foo.at(0)); - ASSERT_EQ(true, foo.at(1)); - ASSERT_EQ(true, foo.at(2)); - ASSERT_EQ(false, foo.at(3)); - ASSERT_EQ(true, foo.at(4)); - ASSERT_EQ(false, foo.at(5)); - ASSERT_EQ(false, foo.at(6)); - ASSERT_EQ(false, foo.at(7)); - ASSERT_EQ(true, foo.at(8)); - ASSERT_EQ(true, foo.at(9)); - // Constant iterators. - ASSERT_EQ(false, *foo.cbegin()); - ASSERT_EQ(true, *(foo.cend() - 1)); - ASSERT_EQ(true, foo.cbegin()[2]); -} - -TEST(VLATestsNonTrivialSpecific, TestDeallocSizeNonBool) -{ - nunavut::support::VariableLengthArray> subject; - ASSERT_EQ(0U, subject.get_allocator().get_alloc_count()); - ASSERT_EQ(10, subject.reserve(10)); - ASSERT_EQ(1U, subject.get_allocator().get_alloc_count()); - ASSERT_EQ(10, subject.get_allocator().get_last_alloc_size()); - ASSERT_EQ(0U, subject.get_allocator().get_last_dealloc_size()); - subject.pop_back(); - subject.shrink_to_fit(); - ASSERT_EQ(10, subject.get_allocator().get_last_dealloc_size()); -} - -TEST(VLATestsNonTrivialSpecific, TestDeallocSizeBool) -{ - nunavut::support::VariableLengthArray> subject; - ASSERT_EQ(0U, subject.get_allocator().get_alloc_count()); - ASSERT_EQ(10, subject.reserve(10)); - ASSERT_EQ(1U, subject.get_allocator().get_alloc_count()); - ASSERT_EQ(2, subject.get_allocator().get_last_alloc_size()); // 2 bytes for 10 bools - ASSERT_EQ(0U, subject.get_allocator().get_last_dealloc_size()); - subject.pop_back(); - subject.shrink_to_fit(); - ASSERT_EQ(2, subject.get_allocator().get_last_dealloc_size()); -} - -TEST(VLATestsNonTrivialSpecific, TestPush) -{ - nunavut::support::VariableLengthArray subject; - ASSERT_EQ(nullptr, subject.data()); - ASSERT_EQ(0U, subject.size()); - int x = 0; - for (std::size_t i = 0; i < VLATestsGeneric_MinMaxSize; ++i) - { - subject.push_back(x); - ASSERT_EQ(i + 1, subject.size()); - ASSERT_LE(subject.size(), subject.capacity()); - const int* const pushed = &subject[i]; - ASSERT_EQ(*pushed, x); - ++x; - } -} - -TEST(VLATestsNonTrivialSpecific, TestDestroy) -{ - int dtor_called = 0; - - auto subject = std::make_shared>>(); - - ASSERT_EQ(10U, subject->reserve(10)); - subject->push_back(Doomed(&dtor_called)); - ASSERT_EQ(1U, subject->size()); - subject->push_back(Doomed(&dtor_called)); - ASSERT_EQ(2U, subject->size()); - ASSERT_EQ(0, dtor_called); - subject.reset(); - ASSERT_EQ(2, dtor_called); -} - -TEST(VLATestsNonTrivialSpecific, TestNonFundamental) -{ - int dtor_called = 0; - - nunavut::support::VariableLengthArray> subject; - ASSERT_EQ(10U, subject.reserve(10)); - subject.push_back(Doomed(&dtor_called)); - ASSERT_EQ(1U, subject.size()); - subject.pop_back(); - ASSERT_EQ(1, dtor_called); -} - -TEST(VLATestsNonTrivialSpecific, TestNotMovable) -{ - class NotMovable - { - public: - NotMovable() {} - NotMovable(NotMovable&&) = delete; - NotMovable(const NotMovable& rhs) noexcept - { - (void) rhs; - } - }; - nunavut::support::VariableLengthArray> subject; - ASSERT_EQ(10U, subject.reserve(10)); - NotMovable source; - subject.push_back(source); - ASSERT_EQ(1U, subject.size()); -} - -TEST(VLATestsNonTrivialSpecific, TestMovable) -{ - class Movable - { - public: - Movable(int data) - : data_(data) - { - } - Movable(const Movable&) = delete; - Movable(Movable&& move_from) noexcept - : data_(move_from.data_) - { - move_from.data_ = 0; - } - int get_data() const - { - return data_; - } - - private: - int data_; - }; - nunavut::support::VariableLengthArray> subject; - ASSERT_EQ(10U, subject.reserve(10)); - subject.push_back(Movable(1)); - ASSERT_EQ(1U, subject.size()); - Movable* pushed = &subject[0]; - ASSERT_NE(nullptr, pushed); - ASSERT_EQ(1, pushed->get_data()); -} - -TEST(VLATestsNonTrivialSpecific, TestInitializerArray) -{ - nunavut::support::VariableLengthArray subject{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; - ASSERT_EQ(10U, subject.size()); - for (std::size_t i = 0; i < subject.size(); ++i) - { - ASSERT_EQ(subject.size() - i, subject[i]); - } -} - -TEST(VLATestsNonTrivialSpecific, TestCopyContructor) -{ - nunavut::support::VariableLengthArray fixture{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; - nunavut::support::VariableLengthArray subject(fixture); - ASSERT_EQ(10U, subject.size()); - for (std::size_t i = 0; i < subject.size(); ++i) - { - ASSERT_EQ(subject.size() - i, subject[i]); - } -} - -TEST(VLATestsNonTrivialSpecific, TestMoveContructor) -{ - nunavut::support::VariableLengthArray fixture{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; - nunavut::support::VariableLengthArray subject(std::move(fixture)); - ASSERT_EQ(10U, subject.size()); - for (std::size_t i = 0; i < subject.size(); ++i) - { - ASSERT_EQ(subject.size() - i, subject[i]); - } - ASSERT_EQ(0U, fixture.size()); - ASSERT_EQ(0U, fixture.capacity()); -} - -TEST(VLATestsNonTrivialSpecific, TestCompare) -{ - nunavut::support::VariableLengthArray one{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; - nunavut::support::VariableLengthArray two{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; - nunavut::support::VariableLengthArray three{{9, 8, 7, 6, 5, 4, 3, 2, 1}}; - ASSERT_EQ(one, one); - ASSERT_EQ(one, two); - ASSERT_NE(one, three); -} - -TEST(VLATestsNonTrivialSpecific, TestFPCompare) -{ - nunavut::support::VariableLengthArray one{{1.00, 2.00}}; - nunavut::support::VariableLengthArray two{{1.00, 2.00}}; - const double epsilon_for_two_comparison = std::nextafter(4.00, INFINITY) - 4.00; - nunavut::support::VariableLengthArray three{ - {1.00, std::nextafter(2.00 + epsilon_for_two_comparison, INFINITY)}}; - ASSERT_EQ(one, one); - ASSERT_EQ(one, two); - ASSERT_NE(one, three); -} - -TEST(VLATestsNonTrivialSpecific, TestCompareBool) -{ - nunavut::support::VariableLengthArray one{{true, false, true}}; - nunavut::support::VariableLengthArray two{{true, false, true}}; - nunavut::support::VariableLengthArray three{{true, true, false}}; - ASSERT_EQ(one, one); - ASSERT_EQ(one, two); - ASSERT_NE(one, three); -} - -TEST(VLATestsNonTrivialSpecific, TestCopyAssignment) -{ - nunavut::support::VariableLengthArray lhs{1.00}; - nunavut::support::VariableLengthArray rhs{{2.00, 3.00}}; - ASSERT_EQ(1U, lhs.size()); - ASSERT_EQ(2U, rhs.size()); - ASSERT_NE(lhs, rhs); - lhs = rhs; - ASSERT_EQ(2U, lhs.size()); - ASSERT_EQ(2U, rhs.size()); - ASSERT_EQ(lhs, rhs); -} - -TEST(VLATestsNonTrivialSpecific, TestMoveAssignment) -{ - nunavut::support::VariableLengthArray lhs{{std::string("one"), std::string("two")}}; - nunavut::support::VariableLengthArray rhs{ - {std::string("three"), std::string("four"), std::string("five")}}; - ASSERT_EQ(2U, lhs.size()); - ASSERT_EQ(3U, rhs.size()); - ASSERT_NE(lhs, rhs); - lhs = std::move(rhs); - ASSERT_EQ(3U, lhs.size()); - ASSERT_EQ(0U, rhs.size()); - ASSERT_EQ(0U, rhs.capacity()); - ASSERT_NE(lhs, rhs); - ASSERT_EQ(std::string("three"), lhs[0]); -} diff --git a/verification/cpp/suite/test_var_len_arr_compiles.cpp b/verification/cpp/suite/test_var_len_arr_compiles.cpp deleted file mode 100644 index cec1d153..00000000 --- a/verification/cpp/suite/test_var_len_arr_compiles.cpp +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Ensures certain static contracts are adhered to for the VariableLengthArray - * type. - */ - -#include "gmock/gmock.h" -#include "nunavut/support/variable_length_array.hpp" - -template -class JunkyNoThrowAllocator -{ -public: - using value_type = T; - template struct rebind final { using other = JunkyNoThrowAllocator; }; - - JunkyNoThrowAllocator() noexcept - : data_() - { - } - - JunkyNoThrowAllocator(const JunkyNoThrowAllocator& rhs) noexcept - : data_() - { - (void) rhs; - } - - JunkyNoThrowAllocator(JunkyNoThrowAllocator&& rhs) noexcept - : data_() - { - (void) rhs; - } - - T* allocate(std::size_t n) noexcept - { - if (n <= SizeCount) - { - return reinterpret_cast(&data_[0]); - } - else - { - return nullptr; - } - } - - constexpr void deallocate(T* p, std::size_t n) noexcept - { - (void) p; - (void) n; - } - -private: - T data_[SizeCount]; -}; - -template -class JunkyThrowingAllocator -{ -public: - using value_type = T; - template struct rebind final { using other = JunkyThrowingAllocator; }; - - JunkyThrowingAllocator() - : data_() - { - } - - JunkyThrowingAllocator(const JunkyThrowingAllocator& rhs) - : data_() - { - (void) rhs; - } - - JunkyThrowingAllocator(JunkyThrowingAllocator&& rhs) - : data_() - { - (void) rhs; - } - - T* allocate(std::size_t n) - { - if (n <= SizeCount) - { - return reinterpret_cast(&data_[0]); - } - else - { - return nullptr; - } - } - - constexpr void deallocate(T* p, std::size_t n) - { - (void) p; - (void) n; - } - -private: - T data_[SizeCount]; -}; - -struct ThrowyThing -{ - ThrowyThing() {} - ThrowyThing(const ThrowyThing&) {} - ThrowyThing(ThrowyThing&&) {} -}; - -struct NotThrowyThing -{ - NotThrowyThing() noexcept {} - NotThrowyThing(const NotThrowyThing&) noexcept {} - NotThrowyThing(NotThrowyThing&&) noexcept {} -}; - -// --------------------------------------------------------------------------------------------------------------------- - -template -class TestDefaultAllocator : public testing::Test { }; -using DefaultAllocatorTypes = ::testing::Types< - nunavut::support::VariableLengthArray, - nunavut::support::VariableLengthArray ->; -TYPED_TEST_SUITE(TestDefaultAllocator, DefaultAllocatorTypes, ); -TYPED_TEST(TestDefaultAllocator, X) -{ - TypeParam subject; - - static_assert(std::is_nothrow_default_constructible::value, - "VariableLengthArray's default allocator must be no-throw default constructible"); - - static_assert(std::is_nothrow_constructible::value, - "VariableLengthArray's default allocator must be no-throw constructible."); - - static_assert(std::is_nothrow_destructible::value, - "VariableLengthArray's default allocator must be no-throw destructible.'."); - - static_assert(noexcept(subject.reserve(0)), - "VariableLengthArray.reserve must not throw when using the default allocator."); - - static_assert(noexcept(subject.shrink_to_fit()), - "VariableLengthArray.shrink_to_fit must not throw exceptions if using the default allocator"); - - // Use the subject to ensure it isn't elided. - ASSERT_EQ(0U, subject.size()); -} - -// --------------------------------------------------------------------------------------------------------------------- - -template -class TestNoThrowAllocator : public testing::Test { }; -using NoThrowAllocatorTypes = ::testing::Types< - nunavut::support::VariableLengthArray>, - nunavut::support::VariableLengthArray> ->; -TYPED_TEST_SUITE(TestNoThrowAllocator, NoThrowAllocatorTypes, ); -TYPED_TEST(TestNoThrowAllocator, X) -{ - TypeParam subject; - - static_assert(std::is_nothrow_default_constructible::value, - "VariableLengthArray must be no-throw default constructible if the allocator is."); - - static_assert(std::is_nothrow_constructible::value, - "VariableLengthArray must be no-throw constructible if the allocator is."); - - static_assert(std::is_nothrow_destructible::value, - "VariableLengthArray must be no-throw destructible if the allocator is."); - - static_assert(noexcept(subject.reserve(0)), - "VariableLengthArray.reserve must not throw exceptions if Allocator::allocate does not."); - - static_assert(noexcept(subject.shrink_to_fit()), - "VariableLengthArray.shrink_to_fit must not throw exceptions if Allocator::deallocate " - "and Allocate::allocate do not."); - - // Use the subject to ensure it isn't elided. - ASSERT_EQ(0U, subject.size()); -} - -// --------------------------------------------------------------------------------------------------------------------- - -template -class TestThrowingAllocator : public testing::Test { }; -using ThrowingAllocatorTypes = ::testing::Types< - nunavut::support::VariableLengthArray>, - nunavut::support::VariableLengthArray> ->; -TYPED_TEST_SUITE(TestThrowingAllocator, ThrowingAllocatorTypes, ); -TYPED_TEST(TestThrowingAllocator, X) -{ - TypeParam subject; - - static_assert(std::is_default_constructible::value && - !std::is_nothrow_default_constructible::value, - "VariableLengthArray must allow exceptions from the constructor if the allocator does."); - - static_assert(std::is_constructible::value && - !std::is_nothrow_constructible::value, - "VariableLengthArray must allow exceptions from the constructor if the allocator does."); - - static_assert(!std::is_nothrow_destructible::value, - "VariableLengthArray must be allow exceptions from the destructor if the allocator does."); - - static_assert(!noexcept(subject.reserve(0)), - "VariableLengthArray.reserve must allow exceptions if Allocator::allocate does."); - - static_assert(!noexcept(subject.shrink_to_fit()), - "VariableLengthArray.shrink_to_fit must allow exceptions if either Allocator::deallocate " - "or Allocate::allocate do."); - - // Use the subject to ensure it isn't elided. - ASSERT_EQ(0U, subject.size()); -} - -// --------------------------------------------------------------------------------------------------------------------- - -TEST(ValueThrowing, TestNotThrowingCopyCtor) -{ - using nothrowy_type = - nunavut::support::VariableLengthArray>; - static_assert(noexcept(nothrowy_type(std::declval::type>())), - "VariableLengthArray copy constructor should not throw if the value type doesn't."); - using nothrowy_type_throwy_allocator = - nunavut::support::VariableLengthArray>; - static_assert(!noexcept(nothrowy_type_throwy_allocator( - std::declval::type>())), - "VariableLengthArray copy constructor should throw if the allocator copy constructor throws even if " - "the value type doesn't."); -} - -TEST(ValueThrowing, TestThrowingCopyCtor) -{ - using throwy_type = nunavut::support::VariableLengthArray>; - static_assert(!noexcept(throwy_type(std::declval::type>())), - "VariableLengthArray copy constructor should throw if the value type does."); - using throwy_type_nothrowy_allocator = - nunavut::support::VariableLengthArray>; - static_assert(!noexcept(throwy_type_nothrowy_allocator( - std::declval::type>())), - "VariableLengthArray copy constructor should throw if the value type copy constructor throws even if " - "the allocator type doesn't."); -} diff --git a/verification/nunavut_test_types/cetl_vla_pmr_test_types/mymsgs/Inner.1.0.dsdl b/verification/nunavut_test_types/cetl_vla_pmr_test_types/mymsgs/Inner.1.0.dsdl new file mode 100644 index 00000000..4f4c2233 --- /dev/null +++ b/verification/nunavut_test_types/cetl_vla_pmr_test_types/mymsgs/Inner.1.0.dsdl @@ -0,0 +1,2 @@ +uint32[<=5] inner_items +@sealed diff --git a/verification/nunavut_test_types/cetl_vla_pmr_test_types/mymsgs/Outer.1.0.dsdl b/verification/nunavut_test_types/cetl_vla_pmr_test_types/mymsgs/Outer.1.0.dsdl new file mode 100644 index 00000000..b742a800 --- /dev/null +++ b/verification/nunavut_test_types/cetl_vla_pmr_test_types/mymsgs/Outer.1.0.dsdl @@ -0,0 +1,3 @@ +float32[<=8] outer_items +Inner.1.0 inner +@sealed