diff --git a/.github/workflows/code_testing.yaml b/.github/workflows/code_testing.yaml index 49dacd0..9f1ecfe 100644 --- a/.github/workflows/code_testing.yaml +++ b/.github/workflows/code_testing.yaml @@ -12,12 +12,12 @@ jobs: fail-fast: false matrix: config: - - os: ubuntu-22.04 - compiler: clang-14 - os: ubuntu-22.04 compiler: clang-15 - os: ubuntu-22.04 compiler: clang-16 + - os: ubuntu-22.04 + compiler: clang-17 - os: ubuntu-22.04 compiler: gcc-12 @@ -35,17 +35,22 @@ jobs: # gcc-13 sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y - # clang-16 source /etc/os-release + + # clang-16 echo "deb http://apt.llvm.org/${UBUNTU_CODENAME}/ llvm-toolchain-${UBUNTU_CODENAME}-16 main" | sudo tee /etc/apt/sources.list.d/llvm-16.list curl https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/llvm-16.gpg > /dev/null + # clang-17 + echo "deb http://apt.llvm.org/${UBUNTU_CODENAME}/ llvm-toolchain-${UBUNTU_CODENAME}-17 main" | sudo tee /etc/apt/sources.list.d/llvm-17.list + curl https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/llvm-17.gpg > /dev/null + sudo apt-get update -y - name: Install CMake - uses: lukka/get-cmake@v3.24.3 + uses: lukka/get-cmake@latest with: - cmakeVersion: 3.16.9 + cmakeVersion: 3.27.7 - name: Install compiler id: install_cc @@ -53,28 +58,23 @@ jobs: with: compiler: ${{ matrix.config.compiler }} - - name: Install linker - uses: rui314/setup-mold@v1 - - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: install conan + - name: Install conan shell: bash env: CC: ${{ steps.install_cc.outputs.cc }} CXX: ${{ steps.install_cc.outputs.cxx }} run: | - pip3 install "conan==1.60.1" + pip3 install "conan==1.62.0" conan profile new --detect default - conan profile update settings.compiler.libcxx=libstdc++11 default - conan profile update env.CXXFLAGS="${CXXFLAGS}" default - conan profile update env.CMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" default conan profile update env.CXX="${CXX}" default conan profile update env.CC="${CC}" default + conan profile update settings.compiler.libcxx=libstdc++11 default - name: Cache conan data id: cache-conan - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.conan/data key: tests-${{ matrix.config.os }}-${{ matrix.config.compiler }}-conan @@ -84,7 +84,6 @@ jobs: env: CC: ${{ steps.install_cc.outputs.cc }} CXX: ${{ steps.install_cc.outputs.cxx }} - CXXFLAGS: -fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls - name: Build tests and examples working-directory: build diff --git a/CMakeLists.txt b/CMakeLists.txt index 44c1a02..b83f1aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.22) project( dice-template-library - VERSION 1.4.0 + VERSION 1.5.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/") diff --git a/README.md b/README.md index 96b5782..d2cac6b 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,10 @@ Usage examples can be found [here](examples/examples_defer.cpp). ### `tuple algorthims` 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 + ### Further Examples Compilable code examples can be found in [examples](./examples). The example build requires the cmake @@ -86,7 +90,7 @@ add FetchContent_Declare( dice-template-library GIT_REPOSITORY "https://github.com/dice-group/dice-template-library.git" - GIT_TAG v1.4.0 + GIT_TAG v1.5.0 GIT_SHALLOW TRUE) FetchContent_MakeAvailable(dice-template-library) @@ -105,7 +109,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.4.0` to the `[requires]` section of your conan file. +To do so, you need to add `dice-template-library/1.5.0` to the `[requires]` section of your conan file. ## Build and Run Tests and Examples diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 896ec43..59328c2 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -50,4 +50,11 @@ add_executable(examples_tuple_algorithm target_link_libraries(examples_tuple_algorithm PRIVATE dice-template-library::dice-template-library -) \ No newline at end of file +) + +add_executable(example_flex_array + example_flex_array.cpp) +target_link_libraries(example_flex_array + PRIVATE + dice-template-library::dice-template-library +) diff --git a/examples/example_flex_array.cpp b/examples/example_flex_array.cpp new file mode 100644 index 0000000..29d4ca1 --- /dev/null +++ b/examples/example_flex_array.cpp @@ -0,0 +1,70 @@ +#include + +#include +#include +#include + +using namespace dice::template_library; + +// multidimensional-shape polymorphism without heap allocation + +static constexpr size_t shape_max_dim = 2; +using shape_extents = flex_array; + +struct point { + flex_array extent; + + [[nodiscard]] shape_extents get_extents() const noexcept { + return extent; + } +}; + +struct line { + flex_array length; + + [[nodiscard]] shape_extents get_extents() const noexcept { + return length; + } +}; + +struct square { + flex_array width_height; + + [[nodiscard]] shape_extents get_extents() const noexcept { + return width_height; + } +}; + +struct shape { + std::variant shape_; + + [[nodiscard]] shape_extents get_extents() const noexcept { + return std::visit([](auto const &sha) { + return sha.get_extents(); + }, shape_); + } +}; + +// minimal size of static-length flex_arrays +static_assert(sizeof(point) == 1); +static_assert(sizeof(line) == sizeof(size_t)); +static_assert(sizeof(square) == sizeof(size_t) * 2); + +// no heap allocations on shape_extents +static_assert(sizeof(shape_extents) == sizeof(size_t) * 2 + sizeof(size_t)); + +void print_extents(shape const &sha) { + for (auto const ext : sha.get_extents()) { + std::cout << ext << " "; + } +} + +int main() { + shape const point1{point{.extent = {}}}; + shape const line1{line{.length = {12}}}; + shape const square1{square{.width_height = {52, 15}}}; + + print_extents(point1); + print_extents(line1); + print_extents(square1); +} diff --git a/include/dice/template-library/flex_array.hpp b/include/dice/template-library/flex_array.hpp new file mode 100644 index 0000000..3bd5728 --- /dev/null +++ b/include/dice/template-library/flex_array.hpp @@ -0,0 +1,253 @@ +#ifndef DICE_TEMPLATELIBRARY_FLEXARRAY_HPP +#define DICE_TEMPLATELIBRARY_FLEXARRAY_HPP + +#include +#include +#include +#include +#include +#include + +namespace dice::template_library { + using std::dynamic_extent; + + namespace detail_flex_array { + template + struct flex_array_inner { + static constexpr size_t size_ = extent; + std::array data_; + + constexpr auto operator<=>(flex_array_inner const &) const noexcept = default; + }; + + template + struct flex_array_inner { + 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_}}; + } + + template + constexpr auto lex_compare_impl(flex_array_inner const &other) const noexcept { + auto const [self_s, other_s] = to_spans(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); + return std::ranges::equal(self_s, other_s, Cmp{}); + } + + // operator <=> is not defaulted + // so we need to provide all comparision 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); + 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); + } + }; + } // namespace detail_flex_array + + /** + * A combination of std::array and std::span. + * If extent_ is set to some integer value (!= dynamic_extent), flex_array behaves like std::array, i.e. has a fixed, statically known size, max_size/capacity. + * If extent_ is set to dynamic_extent, max_extent_ must be set to some integer value (!= dynamic_extent). In this case + * flex_array behaves similar to static_vector, i.e. it is a collection with a fixed, statically known max_size/capacity while the + * actual size is a runtime value. + * + * @tparam T value type + * @tparam extent_ extent of the flex array + * @tparam max_extent_ max extent of the flex array + */ + 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"); + + // extent_ == dynamic_extent -> max_extent_ != dynamic_extent + static_assert(extent_ != std::dynamic_extent || max_extent_ != std::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; + + public: + using value_type = T; + using reference = value_type &; + using const_reference = value_type const &; + using pointer = value_type *; + using const_pointer = value_type const *; + using size_type = size_t; + using iterator = value_type *; + using const_iterator = value_type const *; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + private: + inner_type inner_; + + public: + constexpr flex_array() noexcept = default; + constexpr flex_array(flex_array const &) noexcept = default; + constexpr flex_array(flex_array &&) noexcept = default; + constexpr flex_array &operator=(flex_array const &) noexcept = default; + constexpr flex_array &operator=(flex_array &&) noexcept = default; + constexpr ~flex_array() noexcept = default; + + /** + * Initializes the flex_array using an initializer_list + * + * @param init initializer list + * @throws std::length_error if extent == dynamic_extend and init.size() exceeds max_size() + * or extent != dynamic_extent and init.size() != extent + */ + constexpr flex_array(std::initializer_list const init) { + if constexpr (is_dynamic_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"}; + } + + std::ranges::copy(init, begin()); + } + + /** + * Initializes the flex_array using the range [first, last) + * + * @param first iterator to first element + * @param last sentinel of the range + * @throws std::length_error if distance(first, last) exceeds max_size() + */ + template Sent> + constexpr flex_array(Iter first, Sent last) { + size_t ix = 0; + while (first != last) { + if (ix >= max_size()) [[unlikely]] { + throw std::length_error{"flex_array::flex_array: maximum size exceeded"}; + } + + inner_.data_[ix++] = *first++; + } + + if constexpr (is_dynamic_extent) { + inner_.size_ = ix; + } + } + + /** + * Converts from a flex_array of dynamic extent to a flex_array of static extent + * + * @throws std::length_error if other.size() != extent + */ + template + explicit constexpr flex_array(flex_array const &other) requires (!is_dynamic_extent) { + if (other.size() != extent) [[unlikely]] { + throw std::length_error{"flex_array::flex_array: size mismatch"}; + } + + std::ranges::copy(other, begin()); + } + + /** + * 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) { + static_assert(other_extent <= max_extent, "extent of other is too large for this flex_array"); + + inner_.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() const noexcept { + if constexpr (is_dynamic_extent) { + return std::span{data(), size()}; + } else { + return std::span{inner_.data_}; + } + } + + [[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 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; + } + + [[nodiscard]] constexpr pointer data() noexcept { return inner_.data_.data(); } + [[nodiscard]] constexpr const_pointer data() const noexcept { return inner_.data_.data(); } + + constexpr iterator begin() noexcept { return inner_.data_.begin(); } + constexpr iterator end() noexcept { return std::next(begin(), size()); } + constexpr const_iterator begin() const noexcept { return inner_.data_.begin(); } + constexpr const_iterator end() const noexcept { return std::next(begin(), size()); } + constexpr const_iterator cbegin() const noexcept { return inner_.data_.cbegin(); } + constexpr const_iterator cend() const noexcept { return std::next(cbegin(), size()); } + constexpr reverse_iterator rbegin() noexcept { return inner_.data_.rbegin(); } + constexpr reverse_iterator rend() noexcept { return std::next(rbegin(), size()); } + constexpr const_reverse_iterator rbegin() const noexcept { return inner_.data_.rbegin(); } + constexpr const_reverse_iterator rend() const noexcept { return std::next(rbegin(), size()); } + constexpr const_reverse_iterator crbegin() const noexcept { return inner_.data_.crbegin(); } + constexpr const_reverse_iterator crend() const noexcept { return std::next(crbegin(), size()); } + + constexpr reference operator[](size_type const ix) noexcept { return inner_.data_[ix]; } + constexpr const_reference operator[](size_type const ix) const noexcept { return inner_.data_[ix]; } + + constexpr auto operator<=>(flex_array const &other) const noexcept = default; + + friend constexpr void swap(flex_array &lhs, flex_array &rhs) noexcept { + std::swap(lhs.inner_, rhs.inner_); + } + }; + +} // namespace dice::template_library + +#endif // DICE_TEMPLATELIBRARY_FLEXARRAY_HPP diff --git a/include/dice/template-library/polymorphic_allocator.hpp b/include/dice/template-library/polymorphic_allocator.hpp index 153dc67..96099d4 100644 --- a/include/dice/template-library/polymorphic_allocator.hpp +++ b/include/dice/template-library/polymorphic_allocator.hpp @@ -248,7 +248,7 @@ namespace dice::template_library { template constexpr polymorphic_allocator(polymorphic_allocator const &other) noexcept(std::is_nothrow_constructible_v, Allocator const &>) - : alloc_{other} { + : alloc_{other.alloc_} { } constexpr polymorphic_allocator(polymorphic_allocator const &other) noexcept(std::is_nothrow_copy_constructible_v>) = default; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bb7fb84..08b1fb2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -55,3 +55,6 @@ custom_add_test(tests_defer) add_executable(tests_tuple_algorithm tests_tuple_algorithm.cpp) custom_add_test(tests_tuple_algorithm) + +add_executable(tests_flex_array tests_flex_array.cpp) +custom_add_test(tests_flex_array) diff --git a/tests/tests_flex_array.cpp b/tests/tests_flex_array.cpp new file mode 100644 index 0000000..1803e93 --- /dev/null +++ b/tests/tests_flex_array.cpp @@ -0,0 +1,208 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + +#include +#include +#include + +namespace dice::template_library { + // extern template sometimes make problems if internal types are too eagerly instantiated + extern template struct flex_array; + extern template struct flex_array; + + template struct flex_array; + template struct flex_array; +} // namespace dice::template_library + +TEST_SUITE("flex_array") { + using namespace dice::template_library; + + template + void check_all_static(flex_array const &f) { + 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); + CHECK_EQ(std::distance(f.crbegin(), f.crend()), 5); + } + + template + void check_contents(flex_array const &f, size_t expected_size) { + std::vector ref; + ref.resize(expected_size); + std::iota(ref.begin(), ref.end(), 1); + + CHECK_EQ(std::accumulate(f.begin(), f.end(), 0), std::accumulate(ref.begin(), ref.end(), 0)); + + if (expected_size > 0) { + 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) { + 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(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); + CHECK_EQ(std::distance(f.crbegin(), f.crend()), expected_size); + + 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(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); + CHECK_EQ(std::distance(f.crbegin(), f.crend()), 5); + + f[0] = 1; + f[1] = 2; + f[2] = 3; + f[3] = 4; + f[4] = 5; + CHECK_EQ(std::accumulate(f.begin(), f.end(), 0), 15); + CHECK_EQ(*f.data(), 1); + CHECK_EQ(*(f.data() + f.size() - 1), 5); + } + + TEST_CASE("static size") { + static_assert(sizeof(flex_array) == sizeof(int)); + static_assert(alignof(flex_array) == alignof(int)); + + SUBCASE("default ctor") { + flex_array f; + check_all_static(f); + + f[0] = 1; + f[1] = 2; + f[2] = 3; + f[3] = 4; + f[4] = 5; + check_contents(f, 5); + } + + SUBCASE("init list ctor") { + flex_array f{1, 2, 3, 4, 5}; + check_all_static(f); + check_contents(f, 5); + } + + SUBCASE("iter ctor") { + std::array ref{1, 2, 3, 4, 5}; + flex_array f(ref.begin(), ref.end()); + check_all_static(f); + check_contents(f, 5); + } + + SUBCASE("swap") { + flex_array f{1, 2, 3, 4, 5}; + flex_array f2{6, 7, 8, 9, 10}; + + swap(f, f2); + CHECK(std::ranges::equal(f, std::array{6, 7, 8, 9, 10})); + CHECK(std::ranges::equal(f2, std::array{1, 2, 3, 4, 5})); + } + + SUBCASE("cmp") { + flex_array f{1, 2, 3, 4, 5}; + flex_array f2{6, 7, 8, 9, 10}; + + CHECK_EQ(f <=> f2, std::strong_ordering::less); + CHECK_EQ(f <=> f, std::strong_ordering::equal); + CHECK_EQ(f2 <=> f, std::strong_ordering::greater); + } + + SUBCASE("no-cmp") { + struct uncomparable {}; + flex_array f; // checking if this compiles + } + } + + TEST_CASE("dynamic size") { + static_assert(sizeof(flex_array) == 2*sizeof(int) + sizeof(size_t)); + static_assert(alignof(flex_array) == alignof(size_t)); + + SUBCASE("default ctor") { + flex_array f; + check_contents(f, 0); + check_all_dynamic(f, 0); + } + + SUBCASE("init list ctor") { + flex_array f{1, 2, 3}; + check_contents(f, 3); + check_all_dynamic(f, 3); + } + + SUBCASE("iter ctor") { + std::array ref{1, 2, 3}; + flex_array f(ref.begin(), ref.end()); + check_contents(f, 3); + check_all_dynamic(f, 3); + } + + SUBCASE("ctor exceptions") { + CHECK_THROWS_AS((flex_array{1, 2}), std::length_error); + + std::array ref{1, 2}; + CHECK_THROWS_AS((flex_array(ref.begin(), ref.end())), std::length_error); + } + + SUBCASE("swap") { + flex_array f{1, 2, 3, 4, 5}; + flex_array 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") { + flex_array f{1, 2, 3, 4, 5}; + flex_array f2{6, 7, 8}; + flex_array 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}; + + CHECK_EQ(d.size(), 5); + CHECK_EQ(d.max_size(), 6); + CHECK(std::ranges::equal(s, d)); + + d = s; // checking if this compiles + } + + SUBCASE("dynamic -> static") { + flex_array d{1, 2, 3}; + flex_array s{d}; + + CHECK(std::ranges::equal(s, d)); + + CHECK_THROWS_AS((flex_array{d}), std::length_error); + CHECK_THROWS_AS((flex_array{d}), std::length_error); + } + } +}