diff --git a/.github/workflows/code_testing.yaml b/.github/workflows/code_testing.yaml index 452cf4e..519e90e 100644 --- a/.github/workflows/code_testing.yaml +++ b/.github/workflows/code_testing.yaml @@ -66,7 +66,7 @@ jobs: uses: dice-group/cpp-conan-release-reusable-workflow/.github/actions/add_conan_provider@main - name: Configure CMake - run: cmake -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=On -DBUILD_EXAMPLES=On -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -G Ninja -B build . + run: cmake -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" -DCMAKE_BUILD_TYPE=Debug -DWITH_SVECTOR=ON -DWITH_BOOST=ON -DBUILD_TESTING=On -DBUILD_EXAMPLES=On -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -G Ninja -B build . env: CC: ${{ steps.install_cc.outputs.cc }} CXX: ${{ steps.install_cc.outputs.cxx }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 26cf6e7..23e0f59 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.24) project( dice-template-library - VERSION 1.7.0 + VERSION 1.8.0 DESCRIPTION "This template library is a collection of template-oriented code that we, the Data Science Group at UPB, found pretty handy. It contains: `switch_cases` (Use runtime values in compile-time context), `integral_template_tuple` (Create a tuple-like structure that instantiates a template for a range of values), `integral_template_variant` (A wrapper type for `std::variant` guarantees to only contain variants of the form `T` and `for_{types,values,range}` (Compile time for loops for types, values or ranges))." HOMEPAGE_URL "https://dice-research.org/") @@ -12,13 +12,21 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.hpp.in ${CMAKE_CURRENT_ option(BUILD_TESTING "build tests" OFF) option(BUILD_EXAMPLES "build examples" OFF) +option(WITH_SVECTOR "use ankerl/svector to provide flex_array with flex_array_implementation::sbo_vector" OFF) +option(WITH_BOOST "use boost to provide offset_ptr_stl_allocator in polymorphic_allocator.hpp" OFF) if (PROJECT_IS_TOP_LEVEL) - set(CONAN_INSTALL_ARGS "${CONAN_INSTALL_ARGS};-o=boost/*:header_only=True") - - if (BUILD_TESTING) + if (BUILD_TESTING OR BUILD_EXAMPLES) set(CONAN_INSTALL_ARGS "${CONAN_INSTALL_ARGS};-o=&:with_test_deps=True") endif () + + if (WITH_SVECTOR) + set(CONAN_INSTALL_ARGS "${CONAN_INSTALL_ARGS};-o=&:with_svector=True") + endif () + + if (WITH_BOOST) + set(CONAN_INSTALL_ARGS "${CONAN_INSTALL_ARGS};-o=&:with_boost=True;-o=boost/*:header_only=True") + endif () endif () # conan requires cmake build type to be specified and it is generally a good idea @@ -35,6 +43,22 @@ target_include_directories(${PROJECT_NAME} INTERFACE $ ) +if (WITH_SVECTOR) + find_package(svector REQUIRED) + + target_link_libraries(${PROJECT_NAME} INTERFACE + svector::svector + ) +endif () + +if (WITH_BOOST) + find_package(Boost REQUIRED COMPONENTS) + + target_link_libraries(${PROJECT_NAME} INTERFACE + Boost::headers + ) +endif () + set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20 CXX_EXTENSIONS OFF @@ -44,12 +68,12 @@ set_target_properties(${PROJECT_NAME} PROPERTIES include(cmake/install_interface_library.cmake) install_interface_library(${PROJECT_NAME} ${PROJECT_NAME} ${PROJECT_NAME} "include") -if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING) +if(PROJECT_IS_TOP_LEVEL AND BUILD_TESTING) include(CTest) enable_testing() add_subdirectory(tests) endif() -if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_EXAMPLES) +if(PROJECT_IS_TOP_LEVEL AND BUILD_EXAMPLES) add_subdirectory(examples) endif() diff --git a/README.md b/README.md index 9bd0609..c2658f0 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ It contains: - `polymorphic_allocator`: Like `std::pmr::polymorphic_allocator` but with static dispatch - `DICE_DEFER`/`DICE_DEFER_TO_SUCCES`/`DICE_DEFER_TO_FAIL`: On-the-fly RAII for types that do not support it natively (similar to go's `defer` keyword) - `overloaded`: Composition for `std::variant` visitor lambdas -- `flex_array`: A combination of `std::array` and `std::span` +- `flex_array`: A combination of `std::array`, `std::span` and a `vector` with small buffer optimization - `tuple_algorithms`: Some algorithms for iterating tuples - `generator`: The reference implementation of `std::generator` from [P2502R2](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2502r2.pdf) - `channel`: A single producer, single consumer queue @@ -73,8 +73,8 @@ Usage examples can be found [here](examples/examples_defer.cpp). Some algorithms for iterating tuples, for example `tuple_fold` a fold/reduce implementation for tuples. ### `flex_array` -A combination of `std::array` and `std::span` where the size is either statically known or a runtime variable -depending on the `extent` template parameter +A combination of `std::array`, `std::span` and a `vector` with small buffer optimization where the size is either +statically known or a runtime variable depending on the `extent`/`max_extent` template parameters ### `generator` The reference implementation of `std::generator` from [P2502R2](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2502r2.pdf). @@ -106,7 +106,7 @@ add FetchContent_Declare( dice-template-library GIT_REPOSITORY "https://github.com/dice-group/dice-template-library.git" - GIT_TAG v1.7.0 + GIT_TAG v1.8.0 GIT_SHALLOW TRUE) FetchContent_MakeAvailable(dice-template-library) @@ -125,7 +125,7 @@ target_link_libraries(your_target ### conan You can use it with [conan](https://conan.io/). -To do so, you need to add `dice-template-library/1.7.0` to the `[requires]` section of your conan file. +To do so, you need to add `dice-template-library/1.8.0` to the `[requires]` section of your conan file. ## Build and Run Tests and Examples diff --git a/conanfile.py b/conanfile.py index 438e234..1095017 100644 --- a/conanfile.py +++ b/conanfile.py @@ -20,14 +20,23 @@ class DiceTemplateLibrary(ConanFile): no_copy_source = True options = { "with_test_deps": [True, False], + "with_svector": [True, False], + "with_boost": [True, False], } default_options = { "with_test_deps": False, + "with_svector": False, + "with_boost": False, } def requirements(self): + if self.options.with_svector: + self.requires("svector/1.0.3", transitive_headers=True) + + if self.options.with_boost: + self.requires("boost/1.84.0", transitive_headers=True) + if self.options.with_test_deps: - self.test_requires("boost/1.83.0") self.test_requires("doctest/2.4.11") def layout(self): @@ -36,7 +45,7 @@ def layout(self): def build(self): if not self.conf.get("tools.build:skip_test", default=False): cmake = CMake(self) - cmake.configure() + cmake.configure(variables={"WITH_SVECTOR": self.options.with_svector, "WITH_BOOST": self.options.with_boost}) cmake.build() def package_id(self): @@ -67,6 +76,13 @@ def package(self): def package_info(self): self.cpp_info.bindirs = [] self.cpp_info.libdirs = [] + self.cpp_info.requires = [] + + if self.options.with_svector: + self.cpp_info.requires += ["svector::svector"] + + if self.options.with_boost: + self.cpp_info.requires += ["boost::headers"] self.cpp_info.set_property("cmake_find_mode", "both") self.cpp_info.set_property("cmake_target_name", f"{self.name}::{self.name}") diff --git a/examples/example_flex_array.cpp b/examples/example_flex_array.cpp index 29d4ca1..17b67c2 100644 --- a/examples/example_flex_array.cpp +++ b/examples/example_flex_array.cpp @@ -35,6 +35,12 @@ struct square { } }; +#if __has_include() +struct arbitrary_high_dimensional_thing { + flex_array extents; +}; +#endif // __has_include + struct shape { std::variant shape_; @@ -67,4 +73,11 @@ int main() { print_extents(point1); print_extents(line1); print_extents(square1); + +#if __has_include() + arbitrary_high_dimensional_thing thing{.extents = {1, 2, 3, 4, 5, 6, 7, 8}}; + for (auto const ext : thing.extents) { + std::cout << ext << " "; + } +#endif // __has_include } diff --git a/include/dice/template-library/channel.hpp b/include/dice/template-library/channel.hpp index 7e605c9..2d8d236 100644 --- a/include/dice/template-library/channel.hpp +++ b/include/dice/template-library/channel.hpp @@ -67,6 +67,7 @@ namespace dice::template_library { closed_.test_and_set(std::memory_order_release); } queue_not_empty_.notify_one(); // notify pop() so that it does not get stuck + queue_not_full_.notify_all(); // notify emplace() } /** @@ -90,7 +91,14 @@ namespace dice::template_library { { std::unique_lock lock{queue_mutex_}; - queue_not_full_.wait(lock, [this]() noexcept { return queue_.size() < max_cap_; }); + queue_not_full_.wait(lock, [this]() noexcept { return queue_.size() < max_cap_ || closed_.test(std::memory_order_relaxed); }); + + if (closed_.test(std::memory_order_relaxed)) [[unlikely]] { + // relaxed is enough because we hold the lock + // wait was exited because closed_ was true (channel closed) + return false; + } + queue_.emplace_back(std::forward(args)...); } @@ -112,7 +120,8 @@ namespace dice::template_library { { std::unique_lock lock{queue_mutex_}; - if (queue_.size() >= max_cap_) { + if (queue_.size() >= max_cap_ || closed_.test(std::memory_order_relaxed)) { + // relaxed is enough because we hold the lock return false; } @@ -171,7 +180,7 @@ namespace dice::template_library { */ [[nodiscard]] std::optional pop() noexcept(std::is_nothrow_move_constructible_v) { std::unique_lock lock{queue_mutex_}; - queue_not_empty_.wait(lock, [this]() noexcept { return !queue_.empty() || closed_.test(std::memory_order_acquire); }); + queue_not_empty_.wait(lock, [this]() noexcept { return !queue_.empty() || closed_.test(std::memory_order_relaxed); }); if (queue_.empty()) [[unlikely]] { // implies closed_ == true diff --git a/include/dice/template-library/flex_array.hpp b/include/dice/template-library/flex_array.hpp index 3bd5728..4e7ef0d 100644 --- a/include/dice/template-library/flex_array.hpp +++ b/include/dice/template-library/flex_array.hpp @@ -8,45 +8,95 @@ #include #include +#if __has_include() +#include +#endif // __has_include + namespace dice::template_library { using std::dynamic_extent; + /** + * The underlying implementation of a flex array + */ + enum struct flex_array_mode { + direct_static_size, ///< size is static and flex array is stack allocated + direct_dynamic_limited_size, ///< size is dynamic but limited by max_size, flex array is stack allocated and has at most max_size elements + sbo_dynamic_size, ///< small buffer optimized vector + }; + namespace detail_flex_array { template - struct flex_array_inner { + struct flex_array_inner; + + + template requires (extent != dynamic_extent) + struct flex_array_inner { + // fully fixed size + static constexpr flex_array_mode mode = flex_array_mode::direct_static_size; + static constexpr size_t size_ = extent; std::array data_; + static constexpr size_t size() noexcept { + return size_; + } + + operator std::span() noexcept { + return data_; + } + + operator std::span() const noexcept { + return data_; + } + constexpr auto operator<=>(flex_array_inner const &) const noexcept = default; }; template struct flex_array_inner { + // fixed max size, dynamic actual size + static constexpr flex_array_mode mode = flex_array_mode::direct_dynamic_limited_size; + size_t size_ = 0; std::array data_; - constexpr std::pair, std::span> to_spans(flex_array_inner const &other) const noexcept { - return {std::span{data_.data(), size_}, - std::span{other.data_.data(), other.size_}}; + [[nodiscard]] constexpr size_t size() const noexcept { + return size_; + } + + constexpr void set_size(size_t size) noexcept { + assert(size <= max_extent); + size_ = size; + } + + operator std::span() noexcept { + return {data_.data(), size_}; + } + + operator std::span() const noexcept { + return {data_.data(), size_}; } template constexpr auto lex_compare_impl(flex_array_inner const &other) const noexcept { - auto const [self_s, other_s] = to_spans(other); + std::span const self_s{*this}; + std::span const other_s{other}; return std::ranges::lexicographical_compare(self_s, other_s, Cmp{}); } template constexpr auto eq_compare_impl(flex_array_inner const &other) const noexcept { - auto const [self_s, other_s] = to_spans(other); + std::span const self_s{*this}; + std::span const other_s{other}; return std::ranges::equal(self_s, other_s, Cmp{}); } // operator <=> is not defaulted - // so we need to provide all comparision operators manually + // so we need to provide all comparison operators manually constexpr auto operator<=>(flex_array_inner const &other) const noexcept requires requires (T x) { x <=> x; } { - auto const [self_s, other_s] = to_spans(other); + std::span const self_s{*this}; + std::span const other_s{other}; return std::lexicographical_compare_three_way(self_s.begin(), self_s.end(), other_s.begin(), other_s.end()); } @@ -74,6 +124,90 @@ namespace dice::template_library { return lex_compare_impl>(other); } }; + +#if __has_include() + template requires (extent != dynamic_extent) + struct flex_array_inner { + // dynamic max size, fixed small buffer size + static constexpr flex_array_mode mode = flex_array_mode::sbo_dynamic_size; + + ::ankerl::svector data_; + + [[nodiscard]] size_t size() const noexcept { + return data_.size(); + } + + operator std::span() noexcept { + return data_; + } + + operator std::span() const noexcept { + return data_; + } + + void set_size(size_t size) { + data_.resize(size); + } + + template + constexpr auto lex_compare_impl(flex_array_inner const &other) const noexcept { + std::span const self_s{*this}; + std::span const other_s{other}; + return std::ranges::lexicographical_compare(self_s, other_s, Cmp{}); + } + + template + constexpr auto eq_compare_impl(flex_array_inner const &other) const noexcept { + std::span const self_s{*this}; + std::span const other_s{other}; + return std::ranges::equal(self_s, other_s, Cmp{}); + } + + // operator <=> is not defaulted + // so we need to provide all comparison operators manually + + constexpr auto operator<=>(flex_array_inner const &other) const noexcept requires requires (T x) { x <=> x; } { + std::span const self_s{*this}; + std::span const other_s{other}; + return std::lexicographical_compare_three_way(self_s.begin(), self_s.end(), other_s.begin(), other_s.end()); + } + + constexpr bool operator==(flex_array_inner const &other) const noexcept requires requires (T x) { x == x; } { + return eq_compare_impl>(other); + } + + constexpr bool operator!=(flex_array_inner const &other) const noexcept requires requires (T x) { x != x; } { + return eq_compare_impl>(other); + } + + constexpr bool operator<(flex_array_inner const &other) const noexcept requires requires (T x) { x < x; } { + return lex_compare_impl>(other); + } + + constexpr bool operator<=(flex_array_inner const &other) const noexcept requires requires (T x) { x <= x; } { + return lex_compare_impl>(other); + } + + constexpr bool operator>(flex_array_inner const &other) const noexcept requires requires (T x) { x > x; } { + return lex_compare_impl>(other); + } + + constexpr bool operator>=(flex_array_inner const &other) const noexcept requires requires (T x) { x >= x; } { + return lex_compare_impl>(other); + } + }; +#else // __has_include + template requires (extent != dynamic_extent) + struct flex_array_inner { + template + static constexpr bool always_false() { + // workaround for static_assert(false) always asserting + return false; + } + + static_assert(always_false(), "Could not find , flex_array_implementation::sbo_vector mode is not available."); + }; +#endif // __has_include } // namespace detail_flex_array /** @@ -89,22 +223,25 @@ namespace dice::template_library { */ template struct flex_array { - // extent_ != dynamic_extent -> extent_ == max_extent_ - static_assert(extent_ == dynamic_extent || extent_ == max_extent_, - "If extent is not dynamic_extent, extent must be equal to max_extent"); + private: + using inner_type = detail_flex_array::flex_array_inner; + public: // extent_ == dynamic_extent -> max_extent_ != dynamic_extent - static_assert(extent_ != std::dynamic_extent || max_extent_ != std::dynamic_extent, + static_assert(extent_ != dynamic_extent || max_extent_ != dynamic_extent, + "If extent is not dynamic_extent, extent must be equal to max_extent"); + + // max_extent_ == dynamic_extent -> extent_ != dynamic_extent + static_assert(max_extent_ != dynamic_extent || extent_ != dynamic_extent, "If extent is dynamic_extent, max_extent must not be dynamic_extent"); static constexpr size_t extent = extent_; static constexpr size_t max_extent = max_extent_; - static constexpr bool is_dynamic_extent = extent == dynamic_extent; - private: - using inner_type = detail_flex_array::flex_array_inner; + static constexpr bool has_max_extent = max_extent != dynamic_extent; + static constexpr bool has_dynamic_extent = extent == dynamic_extent || max_extent == dynamic_extent; + static constexpr flex_array_mode mode = inner_type::mode; - public: using value_type = T; using reference = value_type &; using const_reference = value_type const &; @@ -135,14 +272,18 @@ namespace dice::template_library { * or extent != dynamic_extent and init.size() != extent */ constexpr flex_array(std::initializer_list const init) { - if constexpr (is_dynamic_extent) { + if constexpr (has_max_extent) { if (init.size() > max_size()) [[unlikely]] { throw std::length_error{"flex_array::flex_array: maximum size exceeded"}; } + } - inner_.size_ = init.size(); - } else if (init.size() != extent) [[unlikely]] { - throw std::length_error{"flex_array::flex_array: size mismatch"}; + if constexpr (has_dynamic_extent) { + inner_.set_size(init.size()); + } else { + if (init.size() != extent) [[unlikely]] { + throw std::length_error{"flex_array::flex_array: size mismatch"}; + } } std::ranges::copy(init, begin()); @@ -157,17 +298,30 @@ namespace dice::template_library { */ template Sent> constexpr flex_array(Iter first, Sent last) { - size_t ix = 0; - while (first != last) { - if (ix >= max_size()) [[unlikely]] { + if constexpr (has_max_extent && std::random_access_iterator) { + auto const range_size = std::distance(first, last); + if (static_cast(range_size) > max_size()) [[unlikely]] { throw std::length_error{"flex_array::flex_array: maximum size exceeded"}; } + } - inner_.data_[ix++] = *first++; + size_t ix = 0; + while (first != last) { + if constexpr (has_max_extent) { + if constexpr (!std::random_access_iterator) { + if (ix >= max_size()) [[unlikely]] { + throw std::length_error{"flex_array::flex_array: maximum size exceeded"}; + } + } + + inner_.data_[ix++] = *first++; + } else { + inner_.data_.push_back(*first++); + } } - if constexpr (is_dynamic_extent) { - inner_.size_ = ix; + if constexpr (extent == dynamic_extent && max_extent != dynamic_extent) { + inner_.set_size(ix); } } @@ -176,8 +330,9 @@ namespace dice::template_library { * * @throws std::length_error if other.size() != extent */ - template - explicit constexpr flex_array(flex_array const &other) requires (!is_dynamic_extent) { + template + explicit constexpr flex_array(flex_array const &other) + requires (std::remove_cvref_t::has_dynamic_extent && !has_dynamic_extent) { if (other.size() != extent) [[unlikely]] { throw std::length_error{"flex_array::flex_array: size mismatch"}; } @@ -189,37 +344,33 @@ namespace dice::template_library { * Converts from a flex_array of static extent to a flex_array of dynamic extent */ template - constexpr flex_array(flex_array const &other) noexcept requires (is_dynamic_extent && other_extent != dynamic_extent) { + constexpr flex_array(flex_array const &other) noexcept requires (has_dynamic_extent && other_extent != dynamic_extent) { static_assert(other_extent <= max_extent, "extent of other is too large for this flex_array"); - inner_.size_ = other.size(); + inner_.set_size(other.size()); std::ranges::copy(other, begin()); } - constexpr operator std::span() noexcept { - if constexpr (is_dynamic_extent) { - return std::span{data(), size()}; - } else { - return std::span{inner_.data_}; - } + constexpr operator std::span() noexcept { + return inner_; } - constexpr operator std::span() const noexcept { - if constexpr (is_dynamic_extent) { - return std::span{data(), size()}; - } else { - return std::span{inner_.data_}; - } + constexpr operator std::span() const noexcept { + return inner_; } [[nodiscard]] static constexpr size_type max_size() noexcept { return max_extent; } - [[nodiscard]] static constexpr size_type capacity() noexcept { return max_extent; } - [[nodiscard]] constexpr size_type size() const noexcept { return inner_.size_; } + [[nodiscard]] constexpr size_type size() const noexcept { return inner_.size(); } [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; } - constexpr void resize(size_type new_size) noexcept requires (is_dynamic_extent) { - assert(new_size <= max_extent); - inner_.size_ = new_size; + void resize(size_type new_size) requires (has_dynamic_extent) { + if constexpr (has_max_extent) { + if (new_size > max_extent) [[unlikely]] { + throw std::invalid_argument{"flex_array::resize: new_size exceeds max_extent"}; + } + } + + inner_.set_size(new_size); } [[nodiscard]] constexpr pointer data() noexcept { return inner_.data_.data(); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 096b8cf..13ac499 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,7 +3,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_EXTENSIONS OFF) find_package(Boost REQUIRED COMPONENTS) - find_package(DocTest REQUIRED) add_custom_target(build_tests) diff --git a/tests/tests_flex_array.cpp b/tests/tests_flex_array.cpp index 1803e93..72ba2d5 100644 --- a/tests/tests_flex_array.cpp +++ b/tests/tests_flex_array.cpp @@ -22,7 +22,6 @@ TEST_SUITE("flex_array") { CHECK_FALSE(f.empty()); CHECK_EQ(f.size(), 5); CHECK_EQ(f.max_size(), 5); - CHECK_EQ(f.capacity(), 5); CHECK_EQ(std::distance(f.begin(), f.end()), 5); CHECK_EQ(std::distance(f.cbegin(), f.cend()), 5); CHECK_EQ(std::distance(f.rbegin(), f.rend()), 5); @@ -41,15 +40,14 @@ TEST_SUITE("flex_array") { CHECK_EQ(*f.data(), 1); } - CHECK_EQ(*(f.data() + f.size() - 1), expected_size); CHECK(std::ranges::equal(ref, std::span{f})); } - void check_all_dynamic(flex_array &f, size_t expected_size) { + template + void check_all_dynamic(flex_array &f, size_t expected_size) { CHECK_EQ(f.empty(), expected_size == 0); CHECK_EQ(f.size(), expected_size); - CHECK_EQ(f.max_size(), 5); - CHECK_EQ(f.capacity(), 5); + CHECK_EQ(f.max_size(), max_extent); CHECK_EQ(std::distance(f.begin(), f.end()), expected_size); CHECK_EQ(std::distance(f.cbegin(), f.cend()), expected_size); CHECK_EQ(std::distance(f.rbegin(), f.rend()), expected_size); @@ -58,8 +56,7 @@ TEST_SUITE("flex_array") { f.resize(5); CHECK_FALSE(f.empty()); CHECK_EQ(f.size(), 5); - CHECK_EQ(f.max_size(), 5); - CHECK_EQ(f.capacity(), 5); + CHECK_EQ(f.max_size(), max_extent); CHECK_EQ(std::distance(f.begin(), f.end()), 5); CHECK_EQ(std::distance(f.cbegin(), f.cend()), 5); CHECK_EQ(std::distance(f.rbegin(), f.rend()), 5); @@ -78,6 +75,7 @@ TEST_SUITE("flex_array") { TEST_CASE("static size") { static_assert(sizeof(flex_array) == sizeof(int)); static_assert(alignof(flex_array) == alignof(int)); + static_assert(flex_array::mode == flex_array_mode::direct_static_size); SUBCASE("default ctor") { flex_array f; @@ -128,9 +126,10 @@ TEST_SUITE("flex_array") { } } - TEST_CASE("dynamic size") { + TEST_CASE("dynamic size but bounded") { static_assert(sizeof(flex_array) == 2*sizeof(int) + sizeof(size_t)); static_assert(alignof(flex_array) == alignof(size_t)); + static_assert(flex_array::mode == flex_array_mode::direct_dynamic_limited_size); SUBCASE("default ctor") { flex_array f; @@ -183,26 +182,94 @@ TEST_SUITE("flex_array") { } } +#if __has_include() + TEST_CASE("dynamic size not bounded") { + static_assert(sizeof(flex_array) == 2*sizeof(int) + sizeof(size_t)); + static_assert(alignof(flex_array) == alignof(size_t)); + static_assert(flex_array::mode == flex_array_mode::sbo_dynamic_size); + + using farray = flex_array; + + SUBCASE("default ctor") { + farray f; + check_contents(f, 0); + check_all_dynamic(f, 0); + } + + SUBCASE("init list ctor") { + farray f{1, 2, 3}; + check_contents(f, 3); + check_all_dynamic(f, 3); + } + + SUBCASE("iter ctor") { + std::array ref{1, 2, 3}; + farray f(ref.begin(), ref.end()); + check_contents(f, 3); + check_all_dynamic(f, 3); + } + + SUBCASE("ctor larger than size") { + farray f{1, 2, 3, 4, 5, 6}; + std::array ref{1, 2, 3, 4, 5, 6}; + CHECK(std::ranges::equal(f, ref)); + } + + SUBCASE("swap") { + farray f{1, 2, 3, 4, 5}; + farray f2{6, 7, 8}; + + swap(f, f2); + CHECK(std::ranges::equal(f, std::array{6, 7, 8})); + CHECK(std::ranges::equal(f2, std::array{1, 2, 3, 4, 5})); + } + + SUBCASE("cmp") { + farray f{1, 2, 3, 4, 5}; + farray f2{6, 7, 8}; + farray f3{5, 6, 7, 8, 9}; + + CHECK_EQ(f <=> f2, std::strong_ordering::less); + CHECK_EQ(f <=> f, std::strong_ordering::equal); + CHECK_EQ(f3 <=> f, std::strong_ordering::greater); + } + + SUBCASE("no-cmp") { + struct uncomparable {}; + flex_array f; // checking if this compiles + } + } + TEST_CASE("converting ctors") { SUBCASE("static -> dynamic") { flex_array s{1, 2, 3, 4, 5}; flex_array d{s}; + flex_array d2{s}; CHECK_EQ(d.size(), 5); CHECK_EQ(d.max_size(), 6); CHECK(std::ranges::equal(s, d)); + CHECK(std::ranges::equal(s, d2)); d = s; // checking if this compiles + d2 = s; } SUBCASE("dynamic -> static") { flex_array d{1, 2, 3}; + flex_array d2{1, 2, 3}; + flex_array s{d}; + flex_array s2{d2}; CHECK(std::ranges::equal(s, d)); + CHECK(std::ranges::equal(s2, d2)); CHECK_THROWS_AS((flex_array{d}), std::length_error); CHECK_THROWS_AS((flex_array{d}), std::length_error); + CHECK_THROWS_AS((flex_array{d2}), std::length_error); + CHECK_THROWS_AS((flex_array{d2}), std::length_error); } } +#endif // __has_include } diff --git a/tests/tests_polymorphic_allocator.cpp b/tests/tests_polymorphic_allocator.cpp index 25ca10e..fe69991 100644 --- a/tests/tests_polymorphic_allocator.cpp +++ b/tests/tests_polymorphic_allocator.cpp @@ -72,6 +72,8 @@ struct mallocator2 { }; TEST_SUITE("polymorphic_allocator") { + +#if __has_include() TEST_CASE("offset_ptr_stl_allocator") { using alloc_t = dice::template_library::offset_ptr_stl_allocator; @@ -81,6 +83,7 @@ TEST_SUITE("polymorphic_allocator") { CHECK(*ptr == 5); std::allocator_traits::deallocate(alloc, ptr, 1); } +#endif // __has_include template using poly_alloc2_t = dice::template_library::polymorphic_allocator;