├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── appveyor.yml ├── soa_vector.hpp └── tests ├── catch.hpp └── tests.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /.vscode/ 3 | /build/ 4 | /local/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | dist: trusty 3 | language: cpp 4 | jobs: 5 | include: 6 | - os: linux 7 | addons: 8 | apt: 9 | sources: 10 | - ubuntu-toolchain-r-test 11 | - llvm-toolchain-trusty-5.0 12 | packages: 13 | - clang-5.0 14 | - g++-7 # For libstdc++ 15 | - cmake 16 | env: 17 | - MATRIX_EVAL="CC=clang-5.0 && CXX=clang++-5.0" 18 | - os: linux 19 | addons: 20 | apt: 21 | sources: 22 | - ubuntu-toolchain-r-test 23 | packages: 24 | - g++-7 25 | - cmake 26 | env: 27 | - MATRIX_EVAL="CC=gcc-7 && CXX=g++-7" 28 | before_install: 29 | - eval "${MATRIX_EVAL}" 30 | script: 31 | - mkdir build 32 | - cd build 33 | - cmake .. 34 | - cmake --build . 35 | - ctest -V 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project(soa-vector) 3 | cmake_minimum_required(VERSION 3.9) 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | enable_testing() 7 | add_executable(tests "tests/tests.cpp") 8 | add_test(NAME tests COMMAND tests) 9 | 10 | if (MSVC) 11 | target_compile_options(tests PUBLIC "/W3") 12 | else () 13 | target_compile_options(tests PUBLIC "-Wall" "-Wextra" "-Werror") 14 | endif() 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2018 Sidney Congard 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![GCC & Clang Build Status](https://travis-ci.org/Dwarfobserver/soa_vector.svg?branch=master)](https://travis-ci.org/Dwarfobserver/soa_vector) [![MSVC Build Status](https://ci.appveyor.com/api/projects/status/4p7srw0qe4bmshe8/branch/master?svg=true)](https://ci.appveyor.com/project/Dwarfobserver/soa-vector) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 3 | 4 | # SoA vector 5 | 6 | This project is an attempt to resolve the usability issues coming when working with structure of arrays rather than array of structures : 7 | 8 | This single-header library in C++17 implements a std::vector-like data structure which separates it's aggregate components into different arrays. It improves performance when there is different access patterns for the aggregate components, or when we want to perform vectorized operations on it's components. 9 | 10 | It works on MSVC-19.14, Clang-5.0 and GCC-7.2. 11 | 12 | This project is in early development and prone to change. I'd be happy to receive any feedback on it's design or performance ! 13 | 14 | Simple example usage : 15 | 16 | ```cpp 17 | 18 | #include 19 | #include 20 | 21 | // We define an aggregate type : a type with only public members, no virtual functions and no constructor. 22 | namespace user { 23 | struct person { 24 | std::string name; 25 | int age; 26 | }; 27 | } 28 | 29 | // We expose the aggregate so it can be used by soa::vector. 30 | // It defines spans contained in soa::vector and proxy types. 31 | SOA_DEFINE_TYPE(user::person, name, age); 32 | 33 | // We can now manipulate our soa::vector. 34 | soa::vector make_persons() { 35 | 36 | // The semantics are similar to std::vector. 37 | // Only one allocation is performed, so the soa::vector content looks like this in memory : 38 | // [name1, name2, ..., age1, age2, ...] 39 | auto persons = soa::vector{}; 40 | persons.reserve(2); 41 | persons.push_back({ "Jack", 35 }); 42 | // emplace_back takes components as arguments, or default-construct them. 43 | persons.emplace_back("New Born"); 44 | 45 | // Components are accessed with their name, through a range structure : 46 | // soa::vector stores internally a pointer for each component. 47 | for (auto& age : persons.age) 48 | age += 1; 49 | 50 | // You can also access the components like a classic vector through a proxy. 51 | std::for_each(persons.begin(), persons.end(), [] (auto&& p) { 52 | std::cout << "new person : " << p.name << '\n'; 53 | }); 54 | 55 | return persons; 56 | } 57 | 58 | ``` 59 | 60 | Tests can be built and launched with CMake. 61 | 62 | ```bash 63 | 64 | mkdir build 65 | cd build 66 | cmake .. 67 | cmake --build . 68 | ctest -V 69 | 70 | ``` 71 | 72 | Accessing components through the proxy (with vector iterators and accessors) instead of using the vector ranges (vector.xxx iterators and accessors) can be more restrictive in generic code due to the proxy, and lead to performance degradation for some compilers or use cases : 73 | 74 | ```cpp 75 | 76 | // With -O3, GCC and Clang compiles this function with memcpy, but not MSVC with /Ox. 77 | void copy_ages_with_proxy(soa::vector const& persons, int* __restrict dst) { 78 | for (int i = 0; i < persons.size(); ++i) { 79 | dst[i] = persons[i].age; 80 | } 81 | } 82 | // The three compilers use memcpy. 83 | void copy_ages_with_span(soa::vector const& persons, int* __restrict dst) { 84 | for (int i = 0; i < persons.size(); ++i) { 85 | dst[i] = persons.age[i]; 86 | } 87 | } 88 | 89 | ``` 90 | 91 | Project limitations : 92 | 93 | - The aggregate max size is limited (20 by default, it can be increased with more copy-pasta of the 'soa::detail::as_tuple' function). 94 | - It does not support aggregates with native arrays (eg. T[N], use std::array instead). 95 | - It does not support aggregates with base classes (they are detected as aggregates but can't be destructured). 96 | - It does not support over-aligned types from the aggregates. 97 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | 2 | version: 1.0.{build} 3 | image: Visual Studio 2017 4 | configuration: 5 | - Debug 6 | platform: 7 | - x64 8 | build_script: 9 | - mkdir build 10 | - cd build 11 | - cmake .. 12 | - cmake --build . --config Debug -- /v:m 13 | - ctest -V -C Debug 14 | -------------------------------------------------------------------------------- /soa_vector.hpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | soa_vector.hpp 4 | MIT license (2018) 5 | Header repository : https://github.com/Dwarfobserver/soa_vector 6 | You can contact me at sidney.congard@gmail.com 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #if __has_include() 20 | #include 21 | #endif 22 | 23 | namespace soa { 24 | 25 | // Holds arrays for each T component in a single allocation. 26 | // The allocator will be rebound to 'std::byte'. 27 | template > 28 | class vector; 29 | 30 | // Iterable object accessed in soa::vector through soa::member. 31 | template 32 | class vector_span; 33 | 34 | // Specialized for aggregates so soa::vector can be istanciated. 35 | // Specialization of non-template types can be done with the macro 36 | // 'SOA_DEFINE_TYPE(type, members...);' in the global namespace. 37 | template 38 | struct members {}; 39 | 40 | // These proxy types are defined with the macro. They are created when iterating on a 41 | // soa::vector and mimic the given aggregate members as references. 42 | template 43 | struct ref_proxy {}; 44 | template 45 | struct cref_proxy {}; 46 | 47 | // A trait allows to check if the three class above have been defined for the given type. 48 | template 49 | constexpr bool is_defined_v = 50 | !std::is_empty_v> && 51 | !std::is_empty_v> && 52 | !std::is_empty_v>; 53 | 54 | namespace detail { 55 | 56 | namespace impl { 57 | template 58 | using indexed_alias = T; 59 | 60 | template 61 | struct repeat_tuple {}; 62 | 63 | template 64 | struct repeat_tuple> { 65 | using type = std::tuple...>; 66 | }; 67 | } 68 | // Equivalent of std::tuple. 69 | template 70 | using repeat_tuple_t = typename impl::repeat_tuple>::type; 71 | 72 | namespace impl { 73 | template 74 | struct get {}; 75 | template 76 | struct get { 77 | using type = typename get::type; 78 | }; 79 | template 80 | struct get<0, T, Ts...> { 81 | using type = T; 82 | }; 83 | } 84 | // An empty type used to pass types. 85 | template 86 | struct type_tag { 87 | template 88 | using get = typename impl::get::type; 89 | using type = get<0>; 90 | }; 91 | 92 | namespace impl { 93 | template 94 | struct tuple_tag {}; 95 | template 96 | struct tuple_tag> { 97 | using type = type_tag; 98 | }; 99 | } 100 | // std::tuple gives type_tag. 101 | template 102 | using tuple_tag = typename impl::tuple_tag::type; 103 | 104 | // Base class of soa::vector. 105 | // Used to retrieve the size by soa::vector_span from members. 106 | template 107 | class members_with_size : public members { 108 | template 109 | friend class ::soa::vector_span; 110 | protected: 111 | int size_; 112 | }; 113 | 114 | template 115 | auto type_name() { 116 | auto const name = typeid(T).name(); 117 | #if __has_include() 118 | int errc; 119 | unsigned long size; 120 | auto const free_ptr = std::free; 121 | auto const demangled_name = std::unique_ptr{ 122 | abi::__cxa_demangle(name, nullptr, &size, &errc), 123 | free_ptr 124 | }; 125 | return errc 126 | ? std::string{ name } 127 | : std::string(demangled_name.get(), size); 128 | #else 129 | return std::string_view{ name }; 130 | #endif 131 | } 132 | template 133 | std::string concatene(Strings const&...str) { 134 | auto message = std::string{}; 135 | message.reserve((0 + ... + str.size())); 136 | (message.append(str), ...); 137 | return message; 138 | } 139 | template 140 | [[noreturn]] 141 | void throw_out_of_range(int index, int size) { 142 | using namespace std::literals; 143 | 144 | throw std::out_of_range{detail::concatene( 145 | "Out of bounds access when calling "sv, detail::type_name(), "::at("sv, 146 | std::to_string(index), ") while size = "sv, std::to_string(size) 147 | )}; 148 | } 149 | 150 | } // ::detail 151 | 152 | template 153 | class vector_span { 154 | template 155 | friend class vector; 156 | template 157 | friend struct members; 158 | public: 159 | using value_type = T; 160 | 161 | // Informations 162 | T * data() noexcept { return ptr_; } 163 | T const* data() const noexcept { return ptr_; } 164 | int size() const noexcept; 165 | 166 | // Accessors 167 | T & operator[](int i) noexcept { return ptr_[i]; } 168 | T const& operator[](int i) const noexcept { return ptr_[i]; } 169 | T & at(int i) { check_at(i); return ptr_[i]; } 170 | T const& at(int i) const { check_at(i); return ptr_[i]; } 171 | 172 | T & front() noexcept { return ptr_[0]; } 173 | T const& front() const noexcept { return ptr_[0]; } 174 | T & back() noexcept { return ptr_[size() - 1]; } 175 | T const& back() const noexcept { return ptr_[size() - 1]; } 176 | 177 | // Iterators 178 | T * begin() noexcept { return ptr_; } 179 | T const* begin() const noexcept { return ptr_; } 180 | T * end() noexcept { return ptr_ + size(); } 181 | T const* end() const noexcept { return ptr_ + size(); } 182 | private: 183 | void check_at(int i) const { 184 | if (i >= size()) detail::throw_out_of_range>(i, size()); 185 | } 186 | 187 | vector_span() = default; 188 | vector_span(vector_span const&) = default; 189 | vector_span& operator=(vector_span const&) = default; 190 | 191 | vector_span(std::byte * ptr) noexcept : 192 | ptr_{ reinterpret_cast(ptr) } 193 | {} 194 | 195 | T * ptr_; 196 | }; 197 | 198 | // Template arguments are used to retrieve the size from detail::members_with_size. 199 | template 200 | int vector_span::size() const noexcept { 201 | auto const mem_ptr = reinterpret_cast const*>(this - Pos); 202 | auto const mws_ptr = static_cast const*>(mem_ptr); 203 | return mws_ptr->size_; 204 | } 205 | 206 | namespace detail { 207 | // Aggregate to tuple implementation, only for soa::member. 208 | 209 | template 210 | auto as_tuple(T & agg, std::integral_constant) { 211 | auto & [v1] = agg; 212 | return std::forward_as_tuple(v1); 213 | } 214 | template 215 | auto as_tuple(T & agg, std::integral_constant) { 216 | auto & [v1, v2] = agg; 217 | return std::forward_as_tuple(v1, v2); 218 | } 219 | template 220 | auto as_tuple(T & agg, std::integral_constant) { 221 | auto & [v1, v2, v3] = agg; 222 | return std::forward_as_tuple(v1, v2, v3); 223 | } 224 | template 225 | auto as_tuple(T & agg, std::integral_constant) { 226 | auto & [v1, v2, v3, v4] = agg; 227 | return std::forward_as_tuple(v1, v2, v3, v4); 228 | } 229 | template 230 | auto as_tuple(T & agg, std::integral_constant) { 231 | auto & [v1, v2, v3, v4, v5] = agg; 232 | return std::forward_as_tuple(v1, v2, v3, v4, v5); 233 | } 234 | template 235 | auto as_tuple(T & agg, std::integral_constant) { 236 | auto & [v1, v2, v3, v4, v5, v6] = agg; 237 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6); 238 | } 239 | template 240 | auto as_tuple(T & agg, std::integral_constant) { 241 | auto & [v1, v2, v3, v4, v5, v6, v7] = agg; 242 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7); 243 | } 244 | template 245 | auto as_tuple(T & agg, std::integral_constant) { 246 | auto & [v1, v2, v3, v4, v5, v6, v7, v8] = agg; 247 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7, v8); 248 | } 249 | template 250 | auto as_tuple(T & agg, std::integral_constant) { 251 | auto & [v1, v2, v3, v4, v5, v6, v7, v8, v9] = agg; 252 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7, v8, v9); 253 | } 254 | template 255 | auto as_tuple(T & agg, std::integral_constant) { 256 | auto & [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10] = agg; 257 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10); 258 | } 259 | template 260 | auto as_tuple(T & agg, std::integral_constant) { 261 | auto & [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11] = agg; 262 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); 263 | } 264 | template 265 | auto as_tuple(T & agg, std::integral_constant) { 266 | auto & [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12] = agg; 267 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12); 268 | } 269 | template 270 | auto as_tuple(T & agg, std::integral_constant) { 271 | auto & [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13] = agg; 272 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13); 273 | } 274 | template 275 | auto as_tuple(T & agg, std::integral_constant) { 276 | auto & [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14] = agg; 277 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14); 278 | } 279 | template 280 | auto as_tuple(T & agg, std::integral_constant) { 281 | auto & [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15] = agg; 282 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15); 283 | } 284 | template 285 | auto as_tuple(T & agg, std::integral_constant) { 286 | auto & [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16] = agg; 287 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16); 288 | } 289 | template 290 | auto as_tuple(T & agg, std::integral_constant) { 291 | auto & [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17] = agg; 292 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17); 293 | } 294 | template 295 | auto as_tuple(T & agg, std::integral_constant) { 296 | auto & [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18] = agg; 297 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18); 298 | } 299 | template 300 | auto as_tuple(T & agg, std::integral_constant) { 301 | auto & [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19] = agg; 302 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19); 303 | } 304 | template 305 | auto as_tuple(T & agg, std::integral_constant) { 306 | auto & [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20] = agg; 307 | return std::forward_as_tuple(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20); 308 | } 309 | 310 | // The arity is the number of members of a well-formed soa::member. 311 | template 312 | constexpr int arity_v = sizeof(Members) / sizeof(vector_span<0, vector, char>); 313 | 314 | // Continue the overloads above to increase the max_arity. 315 | constexpr int max_arity = 10; 316 | 317 | // Converts a well-formed soa::member to a tuple with references on each member of the class. 318 | template 319 | auto as_tuple(members const& agg) { 320 | return as_tuple(agg, std::integral_constant>>{}); 321 | } 322 | template 323 | auto as_tuple(members & agg) { 324 | return as_tuple(agg, std::integral_constant>>{}); 325 | } 326 | 327 | // Allows to converts any aggregate to a tuple given it's arity. 328 | template 329 | auto as_tuple(T && agg) { 330 | return as_tuple(agg, std::integral_constant{}); 331 | } 332 | 333 | // for_each loops takes a function object to operate on one or two tuples of references. 334 | // Note : C++20 template lambdas would be cleaner to retrieve the type. 335 | 336 | template 337 | constexpr void for_each(std::tuple const& tuple, F && f, std::index_sequence) { 338 | (f(std::get(tuple), type_tag{}), ...); 339 | } 340 | template 341 | constexpr void for_each(std::tuple const& tuple, F && f) { 342 | using seq = std::make_index_sequence; 343 | detail::for_each(tuple, f, seq{}); 344 | } 345 | 346 | template 347 | constexpr void for_each(std::tuple const& t1, std::tuple const& t2, F && f, std::index_sequence) { 348 | (f(std::get(t1), std::get(t2), type_tag{}), ...); 349 | } 350 | template 351 | constexpr void for_each(std::tuple const& t1, std::tuple const& t2, F && f) { 352 | static_assert(sizeof...(Ts1) == sizeof...(Ts2)); 353 | using seq = std::make_index_sequence; 354 | detail::for_each(t1, t2, f, seq{}); 355 | } 356 | 357 | // Apply a function for every object in two ranges. 358 | // Used for soa::vector copy/move assignments and constructors. 359 | template 360 | constexpr void apply_two_arrays(T const* __restrict src, T * dst, SizeT size, F&& f) { 361 | for (SizeT i = 0; i < size; ++i) { 362 | f(src[i], dst[i]); 363 | } 364 | } 365 | template 366 | constexpr void apply_two_arrays(T * __restrict src, T * dst, SizeT size, F&& f) { 367 | for (SizeT i = 0; i < size; ++i) { 368 | f(src[i], dst[i]); 369 | } 370 | } 371 | 372 | // Iterator used by soa::vector to return new proxies with references to the elements. 373 | template 374 | class proxy_iterator { 375 | friend Vector; 376 | 377 | using vector_pointer_type = std::conditional_t; 380 | 381 | vector_pointer_type vec_; 382 | int index_; 383 | 384 | proxy_iterator(vector_pointer_type vec, int index) noexcept : 385 | vec_{vec}, index_{index} {} 386 | public: 387 | using iterator_category = std::random_access_iterator_tag; 388 | 389 | using value_type = std::conditional_t; 392 | 393 | using reference = value_type; 394 | using pointer = void; 395 | using difference_type = int; 396 | private: 397 | template 398 | value_type make_proxy(std::index_sequence) const noexcept { 399 | return { vec_->template get_span()[index_] ... }; 400 | } 401 | public: 402 | value_type operator*() const noexcept { return make_proxy(typename Vector::sequence_type{}); } 403 | 404 | bool operator==(proxy_iterator const& rhs) const noexcept { return index_ == rhs.index_; } 405 | bool operator!=(proxy_iterator const& rhs) const noexcept { return !(*this == rhs); } 406 | 407 | bool operator<(proxy_iterator const& rhs) const noexcept { return index_ < rhs.index_; } 408 | bool operator>(proxy_iterator const& rhs) const noexcept { return rhs < *this; } 409 | bool operator<=(proxy_iterator const& rhs) const noexcept { return !(rhs < *this); } 410 | bool operator>=(proxy_iterator const& rhs) const noexcept { return !(*this < rhs); } 411 | 412 | proxy_iterator & operator++() noexcept { return ++index_, *this; } 413 | proxy_iterator & operator--() noexcept { return --index_, *this; } 414 | proxy_iterator & operator++(int) noexcept { const auto old = *this; return ++index_, old; } 415 | proxy_iterator & operator--(int) noexcept { const auto old = *this; return --index_, old; } 416 | 417 | proxy_iterator & operator+=(int shift) noexcept { return index_ += shift, *this; } 418 | proxy_iterator & operator-=(int shift) noexcept { return index_ -= shift, *this; } 419 | 420 | proxy_iterator operator+(int shift) const noexcept { return { vec_, index_ + shift }; } 421 | proxy_iterator operator-(int shift) const noexcept { return { vec_, index_ - shift }; } 422 | 423 | int operator-(proxy_iterator const& rhs) const noexcept { return index_ - rhs.index_; } 424 | }; 425 | 426 | } // ::detail 427 | 428 | // Stores components of the aggregate T (given by the specialization soa::member) 429 | // in successives arrays from an unique continuous allocation. 430 | // It increases the performance when the access patterns are differents for the 431 | // aggregate's members. 432 | // Over-aligned types are not supported. 433 | template 434 | class vector : public detail::members_with_size { 435 | public: 436 | static_assert(is_defined_v, 437 | "soa::vector can't be instancied because the required types 'soa::members', " 438 | "'soa::ref_proxy' or 'soa::cref_proxy' haven't been defined. " 439 | "Did you forget to call the macro SOA_DEFINE_TYPE(T, members...) ?"); 440 | 441 | // The given allocator is reboud to std::byte to store the different member types. 442 | using allocator_type = typename std::allocator_traits::template rebind_alloc; 443 | 444 | using value_type = T; 445 | using reference_type = ref_proxy; 446 | using const_reference_type = cref_proxy; 447 | 448 | using iterator = detail::proxy_iterator; 449 | using const_iterator = detail::proxy_iterator; 450 | 451 | // The number of T members. 452 | static constexpr int components_count = detail::arity_v>; 453 | 454 | // Constructors. 455 | vector(Allocator allocator = Allocator{}) noexcept; 456 | vector(vector && rhs) noexcept; 457 | vector(vector const& rhs); 458 | 459 | // Assignments. 460 | vector& operator=(vector && rhs) noexcept; 461 | vector& operator=(vector const& rhs); 462 | 463 | // Destructor. 464 | ~vector(); 465 | 466 | // Size or capacity modifiers. 467 | void clear() noexcept; 468 | void reserve(int capacity); 469 | void resize(int size); 470 | void resize(int size, T const& value); 471 | void shrink_to_fit(); 472 | 473 | // Add and remove an element. 474 | template 475 | void emplace_back(Ts &&...components); 476 | void push_back(T const& value); 477 | void push_back(T && value); 478 | void pop_back() noexcept; 479 | 480 | // Informations. 481 | int size() const noexcept { return this->size_; } 482 | int capacity() const noexcept { return capacity_; } 483 | bool empty() const noexcept { return size() == 0; } 484 | 485 | // Accessors. 486 | reference_type operator[](int i) noexcept { return *(begin() + i); } 487 | const_reference_type operator[](int i) const noexcept { return *(begin() + i); } 488 | reference_type at(int i) { check_at(i); return *(begin() + i); } 489 | const_reference_type at(int i) const { check_at(i); return *(begin() + i); } 490 | 491 | reference_type front() noexcept { return *begin(); } 492 | const_reference_type front() const noexcept { return *begin(); } 493 | reference_type back() noexcept { return *(end() - 1); } 494 | const_reference_type back() const noexcept { return *(end() - 1); } 495 | 496 | // Iterators. 497 | iterator begin() noexcept { return { this, 0 }; } 498 | const_iterator begin() const noexcept { return { this, 0 }; } 499 | const_iterator cbegin() const noexcept { return begin(); } 500 | iterator end() noexcept { return { this, size() }; } 501 | const_iterator end() const noexcept { return { this, size() }; } 502 | const_iterator cend() const noexcept { return end(); } 503 | 504 | // Components accessors. 505 | template 506 | auto & get_span() noexcept; 507 | template 508 | auto const & get_span() const noexcept; 509 | private: 510 | friend iterator; 511 | friend const_iterator; 512 | 513 | // Some static asserts on the soa::member type. 514 | // Workaround MSVC : must returns a value to be constexpr. 515 | static constexpr int check_members(); 516 | static constexpr int check_members_trigger = check_members(); 517 | 518 | // Explicit cast to base class. 519 | members& base() noexcept { return *this; } 520 | membersconst& base() const noexcept { return *this; } 521 | 522 | detail::members_with_size& base_with_size() noexcept { return *this; } 523 | detail::members_with_size const& base_with_size() const noexcept { return *this; } 524 | 525 | using sequence_type = std::make_index_sequence; 526 | 527 | // components_tag = detail::type_tag. 528 | template 529 | struct components_tag_impl; 530 | template 531 | struct components_tag_impl> { 532 | using type = detail::type_tag; 533 | }; 534 | using components_tag = typename components_tag_impl>()) 536 | )>::type; 537 | 538 | using allocator_traits = std::allocator_traits; 539 | 540 | // Functions implementations. 541 | 542 | void check_at(int index) const; 543 | 544 | template 545 | void push_back_copy(Tuple const& tuple, std::index_sequence); 546 | template 547 | void push_back_move(Tuple& tuple, std::index_sequence); 548 | 549 | template 550 | void emplace_back_impl(std::tuple const& members, T1&& component, Ts&&...nexts); 551 | template 552 | void emplace_back_impl(std::tuple const& members); 553 | 554 | // Computes the bytes padding for each component, 555 | // assuming we start with a 8-bytes aligned address. 556 | template 557 | static void update_shift(std::tuple& shifts, int nb, int acc); 558 | 559 | // Creates vector_spans based on the data allocated 560 | // and the computed shift for each component. 561 | template 562 | static members create_members(std::byte* ptr, Tuple const& shift, std::index_sequence); 563 | 564 | struct alloc_result { 565 | members new_members; 566 | int nb_bytes; 567 | }; 568 | // Allocates unitialized array of 'nb' elements. 569 | alloc_result allocate(int nb); 570 | 571 | static void construct_copy_array(members const& src, members& dst, int nb); 572 | static void construct_move_array(members & src, members& dst, int nb); 573 | 574 | template 575 | static void apply_on_arrays(members const& mem_src, members & mem_dst, int nb, F && f); 576 | template 577 | static void apply_on_arrays(members & mem_src, members & mem_dst, int nb, F && f); 578 | 579 | void destroy() noexcept; 580 | void destroy(int begin, int end) noexcept; 581 | void deallocate() noexcept; 582 | 583 | // Sets the vector fields (size, capacity, ...) according to an empty vector. 584 | void to_zero() noexcept; 585 | 586 | int capacity_; 587 | allocator_type allocator_; 588 | int nb_bytes_; 589 | }; 590 | 591 | // soa::vector implementation. 592 | 593 | // The check function returns an arbitrary value to be executed at compile-time : 594 | // The msvc version used don't support constexpr void functions. 595 | template 596 | constexpr int vector::check_members() { 597 | 598 | static_assert(!std::is_empty_v>, 599 | "soa::members must be specialized to hold " 600 | "an soa::vector_span for each member of T"); 601 | 602 | static_assert(detail::arity_v> <= detail::max_arity, 603 | "soa::members must have less than 'max_arity' members. " 604 | "This limit can be increased by writing more overloads of 'as_tuple'."); 605 | 606 | return 0; 607 | } 608 | 609 | // Constructors. 610 | 611 | template 612 | vector::vector(Allocator allocator) noexcept : 613 | detail::members_with_size{}, 614 | capacity_ { 0 }, 615 | allocator_{ allocator }, 616 | nb_bytes_ { 0 } 617 | {} 618 | 619 | template 620 | vector::vector(vector&& rhs) noexcept : 621 | detail::members_with_size{ rhs.base_with_size() }, 622 | capacity_ { rhs.capacity() }, 623 | allocator_{ rhs.allocator_ }, 624 | nb_bytes_ { rhs.nb_bytes_ } 625 | { 626 | rhs.to_zero(); 627 | } 628 | 629 | template 630 | vector::vector(vector const& rhs) : 631 | detail::members_with_size{ rhs.base_with_size() }, 632 | capacity_ { rhs.size() }, 633 | allocator_{ rhs.allocator_ }, 634 | nb_bytes_ { rhs.nb_bytes_ } 635 | { 636 | if (rhs.empty()) return; 637 | 638 | auto [new_members, nb_bytes] = allocate(size()); 639 | construct_copy_array(rhs.base(), new_members, size()); 640 | base() = new_members; 641 | nb_bytes_ = nb_bytes; 642 | } 643 | 644 | // Assignments. 645 | 646 | template 647 | vector& vector::operator=(vector&& rhs) noexcept { 648 | destroy(); 649 | deallocate(); 650 | base_with_size() = rhs.base_with_size(); 651 | capacity_ = rhs.capacity(); 652 | allocator_ = rhs.allocator_; 653 | nb_bytes_ = rhs.nb_bytes_; 654 | rhs.to_zero(); 655 | return *this; 656 | } 657 | 658 | template 659 | vector& vector::operator=(vector const& rhs) { 660 | destroy(); 661 | this->size_ = rhs.size(); 662 | if (capacity() < size()) { 663 | deallocate(); 664 | auto [new_members, nb_bytes] = allocate(size()); 665 | base() = new_members; 666 | nb_bytes_ = nb_bytes; 667 | capacity_ = size(); 668 | } 669 | construct_copy_array(rhs.base(), base(), size()); 670 | return *this; 671 | } 672 | 673 | // Destructor. 674 | template 675 | vector::~vector() { 676 | destroy(); 677 | deallocate(); 678 | } 679 | 680 | // Size & capacity modifiers. 681 | 682 | template 683 | void vector::clear() noexcept { 684 | destroy(); 685 | this->size_ = 0; 686 | } 687 | 688 | template 689 | void vector::reserve(int capacity) { 690 | if (capacity <= this->capacity()) return; 691 | 692 | auto [new_members, nb_bytes] = allocate(capacity); 693 | construct_move_array(base(), new_members, size()); 694 | base() = new_members; 695 | nb_bytes_ = nb_bytes; 696 | capacity_ = capacity; 697 | } 698 | 699 | template 700 | void vector::resize(int size) { 701 | if (size <= this->size()) { 702 | destroy(size, this->size()); 703 | this->size_ = size; 704 | return; 705 | } 706 | reserve(size); 707 | detail::for_each(detail::as_tuple(base()), [this, size] (auto& span, auto tag) { 708 | using type = typename decltype(tag)::type; 709 | auto it = span.begin() + this->size(); 710 | auto const end = span.begin() + size; 711 | for (; it < end; ++it) { 712 | new (it) type(); 713 | } 714 | }); 715 | this->size_ = size; 716 | } 717 | 718 | template 719 | void vector::resize(int size, T const& value) { 720 | if (size <= this->size()) { 721 | destroy(size, this->size()); 722 | this->size_ = size; 723 | return; 724 | } 725 | reserve(size); 726 | auto const tuple = detail::as_tuple(value); 727 | detail::for_each(detail::as_tuple(base()), tuple, [this, size] (auto& span, auto& val, auto tag) { 728 | using type = typename decltype(tag)::type; 729 | auto it = span.begin() + this->size(); 730 | auto const end = span.begin() + size; 731 | while (it < end) { 732 | new (it) type(val); ++it; 733 | } 734 | }); 735 | this->size_ = size; 736 | } 737 | 738 | template 739 | void vector::shrink_to_fit() { 740 | if (size() == capacity()) return; 741 | reallocate(size()); 742 | } 743 | 744 | // Add and remove an element. 745 | 746 | template 747 | void vector::push_back(T const& value) { 748 | auto const tuple = detail::as_tuple(value); 749 | push_back_copy(tuple, sequence_type{}); 750 | } 751 | 752 | template 753 | void vector::push_back(T&& value) { 754 | auto tuple = detail::as_tuple(value); 755 | push_back_move(tuple, sequence_type{}); 756 | } 757 | 758 | template 759 | template 760 | void vector::emplace_back(Ts&&...components) { 761 | if (size() == capacity()) { 762 | auto const new_capacity = size() == 0 ? 1 : capacity() * 2; 763 | reserve(new_capacity); 764 | } 765 | emplace_back_impl<0>(detail::as_tuple(base()), std::forward(components)...); 766 | ++this->size_; 767 | } 768 | 769 | template 770 | void vector::pop_back() noexcept { 771 | --this->size_; 772 | detail::for_each(detail::as_tuple(base()), [this] (auto& span, auto tag) { 773 | using type = typename decltype(tag)::type; 774 | span[size()].~type(); 775 | }); 776 | } 777 | 778 | // Components accessors. 779 | 780 | template 781 | template 782 | auto& vector::get_span() noexcept { 783 | static_assert(I < components_count); 784 | return std::get(detail::as_tuple(base())); 785 | } 786 | 787 | template 788 | template 789 | auto const& vector::get_span() const noexcept { 790 | static_assert(I < components_count); 791 | return std::get(detail::as_tuple(base())); 792 | } 793 | 794 | // Private functions. 795 | 796 | template 797 | void vector::check_at(int i) const { 798 | if (i >= size()) detail::throw_out_of_range>(i, size()); 799 | } 800 | 801 | template 802 | void vector::construct_copy_array(members const& mem_src, members& mem_dst, int nb) { 803 | apply_on_arrays(mem_src, mem_dst, nb, [] (auto src, auto & dst) { 804 | using type = decltype(src); 805 | new (&dst) type(src); 806 | }); 807 | } 808 | template 809 | void vector::construct_move_array(members & mem_src, members & mem_dst, int nb) { 810 | apply_on_arrays(mem_src, mem_dst, nb, [] (auto & src, auto & dst) { 811 | using type = std::remove_reference_t; 812 | new (&dst) type(std::move(src)); 813 | }); 814 | } 815 | 816 | template 817 | template 818 | void vector::apply_on_arrays(members const& mem_src, members & mem_dst, int nb, F && f) { 819 | auto const t1 = detail::as_tuple(mem_src); 820 | auto const t2 = detail::as_tuple(mem_dst); 821 | detail::for_each(t1, t2, [f, nb] (auto const& span_src, auto & span_dst, auto) { 822 | detail::apply_two_arrays(span_src.data(), span_dst.data(), nb, f); 823 | }); 824 | } 825 | template 826 | template 827 | void vector::apply_on_arrays(members & mem_src, members & mem_dst, int nb, F && f) { 828 | auto const t1 = detail::as_tuple(mem_src); 829 | auto const t2 = detail::as_tuple(mem_dst); 830 | detail::for_each(t1, t2, [f, nb] (auto & span_src, auto & span_dst, auto) { 831 | detail::apply_two_arrays(span_src.data(), span_dst.data(), nb, f); 832 | }); 833 | } 834 | 835 | template 836 | template 837 | void vector::push_back_copy(Tuple const& tuple, std::index_sequence) { 838 | emplace_back(std::get(tuple)...); 839 | } 840 | template 841 | template 842 | void vector::push_back_move(Tuple& tuple, std::index_sequence) { 843 | emplace_back(std::move(std::get(tuple))...); 844 | } 845 | 846 | template 847 | template 848 | void vector::emplace_back_impl(std::tuple const& tuple, T1&& component, Ts&&...nexts) { 849 | if constexpr (I < sizeof...(Members)) { 850 | using type = typename components_tag::template get; 851 | auto const it = std::get(tuple).ptr_ + size(); 852 | new (it) type(std::forward(component)); 853 | emplace_back_impl(tuple, std::forward(nexts)...); 854 | } 855 | } 856 | template 857 | template 858 | void vector::emplace_back_impl(std::tuple const& tuple) { 859 | if constexpr (I < sizeof...(Members)) { 860 | using type = typename components_tag::template get; 861 | auto const it = std::get(tuple).ptr_ + size(); 862 | new (it) type(); 863 | emplace_back_impl(tuple); 864 | } 865 | } 866 | 867 | template 868 | template 869 | void vector::update_shift(std::tuple& tuple, int nb, int shift) { 870 | using prev = typename components_tag::template get; 871 | if constexpr (I == sizeof...(Ints) - 1) { 872 | std::get(tuple) = shift + nb * sizeof(prev); 873 | } 874 | else { 875 | using type = typename components_tag::template get; 876 | constexpr auto align = alignof(type) - 1; 877 | shift += (nb * sizeof(prev) + align) & ~align; 878 | std::get(tuple) = shift; 879 | update_shift(tuple, nb, shift); 880 | } 881 | } 882 | 883 | template 884 | template 885 | members vector::create_members(std::byte* ptr, Tuple const& shift, std::index_sequence) { 886 | return { (ptr + std::get(shift))... }; 887 | } 888 | 889 | template 890 | typename vector::alloc_result 891 | vector::allocate(int nb) { 892 | constexpr int arity = detail::arity_v>; 893 | auto shift = detail::repeat_tuple_t{}; 894 | update_shift<1>(shift, nb, 0); 895 | 896 | auto const nb_bytes = std::get(shift); 897 | auto const ptr = allocator_traits::allocate(allocator_, nb_bytes); 898 | return { create_members(ptr, shift, sequence_type{}), nb_bytes }; 899 | } 900 | 901 | template 902 | void vector::destroy() noexcept { 903 | detail::for_each(detail::as_tuple(base()), [] (auto& span, auto tag) { 904 | using type = typename decltype(tag)::type; 905 | for (auto& val : span) val.~type(); 906 | }); 907 | } 908 | 909 | template 910 | void vector::destroy(int begin, int end) noexcept { 911 | detail::for_each(detail::as_tuple(base()), [min = begin, max = end] (auto& span, auto tag) { 912 | using type = typename decltype(tag)::type; 913 | auto it = span.begin() + min; 914 | auto const end = span.begin() + max; 915 | for (; it < end; ++it) it->~type(); 916 | }); 917 | } 918 | 919 | template 920 | void vector::deallocate() noexcept { 921 | if (capacity() == 0) return; 922 | auto const data = reinterpret_cast(get_span<0>().ptr_); 923 | allocator_traits::deallocate(allocator_, data, nb_bytes_); 924 | } 925 | 926 | template 927 | void vector::to_zero() noexcept { 928 | base_with_size() = {}; 929 | capacity_ = 0; 930 | nb_bytes_ = 0; 931 | } 932 | 933 | } // namespace soa 934 | 935 | // Private macros. 936 | 937 | #define SOA_PP_EMPTY 938 | #define SOA_PP_EMPTY_ARGS(...) 939 | 940 | #define SOA_PP_EVAL0(...) __VA_ARGS__ 941 | #define SOA_PP_EVAL1(...) SOA_PP_EVAL0 (SOA_PP_EVAL0 (SOA_PP_EVAL0 (__VA_ARGS__))) 942 | #define SOA_PP_EVAL2(...) SOA_PP_EVAL1 (SOA_PP_EVAL1 (SOA_PP_EVAL1 (__VA_ARGS__))) 943 | #define SOA_PP_EVAL3(...) SOA_PP_EVAL2 (SOA_PP_EVAL2 (SOA_PP_EVAL2 (__VA_ARGS__))) 944 | #define SOA_PP_EVAL4(...) SOA_PP_EVAL3 (SOA_PP_EVAL3 (SOA_PP_EVAL3 (__VA_ARGS__))) 945 | #define SOA_PP_EVAL(...) SOA_PP_EVAL4 (SOA_PP_EVAL4 (SOA_PP_EVAL4 (__VA_ARGS__))) 946 | 947 | #define SOA_PP_MAP_GET_END() 0, SOA_PP_EMPTY_ARGS 948 | 949 | #define SOA_PP_MAP_NEXT0(item, next, ...) next SOA_PP_EMPTY 950 | #if defined(_MSC_VER) 951 | #define SOA_PP_MAP_NEXT1(item, next) SOA_PP_EVAL0(SOA_PP_MAP_NEXT0 (item, next, 0)) 952 | #else 953 | #define SOA_PP_MAP_NEXT1(item, next) SOA_PP_MAP_NEXT0 (item, next, 0) 954 | #endif 955 | #define SOA_PP_MAP_NEXT(item, next) SOA_PP_MAP_NEXT1 (SOA_PP_MAP_GET_END item, next) 956 | 957 | #define SOA_PP_MAP0(f, n, t, x, peek, ...) f(n, t, x) SOA_PP_MAP_NEXT (peek, SOA_PP_MAP1) (f, n+1, t, peek, __VA_ARGS__) 958 | #define SOA_PP_MAP1(f, n, t, x, peek, ...) f(n, t, x) SOA_PP_MAP_NEXT (peek, SOA_PP_MAP0) (f, n+1, t, peek, __VA_ARGS__) 959 | #define SOA_PP_MAP(f, t, ...) SOA_PP_EVAL (SOA_PP_MAP1 (f, 0, t, __VA_ARGS__, (), 0)) 960 | 961 | #define SOA_PP_MEMBER(nb, type, name) \ 962 | vector_span().name)> name; 963 | 964 | #define SOA_PP_REF(nb, type, name) \ 965 | decltype(std::declval().name) & name; 966 | 967 | #define SOA_PP_CREF(nb, type, name) \ 968 | decltype(std::declval().name) const& name; 969 | 970 | #define SOA_PP_COPY(nb, type, name) \ 971 | name = rhs.name; 972 | 973 | #define SOA_PP_MOVE(nb, type, name) \ 974 | name = std::move(rhs.name); 975 | 976 | #define SOA_PP_ENABLE_FOR_COPYABLE(type, alias) \ 977 | template && \ 979 | std::is_copy_constructible_v \ 980 | >> 981 | 982 | // Shortcut to specialize soa::member, by listing all the members 983 | // in their declaration order. It must be used in the global namespace. 984 | // Usage exemple : 985 | // 986 | // namespace user { 987 | // struct person { 988 | // std::string name; 989 | // int age; 990 | // }; 991 | // } 992 | // 993 | // SOA_DEFINE_TYPE(user::person, name, age); 994 | // 995 | // This is equivalent to typing : 996 | // 997 | // namespace soa { 998 | // template <> 999 | // struct members { 1000 | // vector_span<0, user::person, std::string> name; 1001 | // vector_span<1, user::person, int> age; 1002 | // }; 1003 | // template <> 1004 | // struct ref_proxy { 1005 | // std::string & name; 1006 | // int & age; 1007 | // 1008 | // operator user::person() const { 1009 | // return { name, age }; 1010 | // } 1011 | // 1012 | // ref_proxy& operator=(user::person const& rhs) { 1013 | // name = rhs.name; 1014 | // age = rhs.age; 1015 | // return *this; 1016 | // } 1017 | // ref_proxy& operator=(user::person && rhs) noexcept { 1018 | // name = std::move(rhs.name); 1019 | // age = std::move(rhs.age); 1020 | // return *this; 1021 | // } 1022 | // }; 1023 | // template <> 1024 | // struct cref_proxy { 1025 | // std::string const& name; 1026 | // int const& age; 1027 | // 1028 | // operator user::person() const { 1029 | // return { name, age }; 1030 | // } 1031 | // }; 1032 | // } 1033 | #define SOA_DEFINE_TYPE(type, ...) \ 1034 | namespace soa { \ 1035 | template <> \ 1036 | struct members<::type> { \ 1037 | SOA_PP_MAP(SOA_PP_MEMBER, ::type, __VA_ARGS__) \ 1038 | }; \ 1039 | template <> \ 1040 | struct ref_proxy<::type> { \ 1041 | SOA_PP_MAP(SOA_PP_REF, ::type, __VA_ARGS__) \ 1042 | \ 1043 | ref_proxy& operator=(::type && rhs) noexcept { \ 1044 | SOA_PP_MAP(SOA_PP_MOVE, ::type, __VA_ARGS__) \ 1045 | return *this; \ 1046 | } \ 1047 | SOA_PP_ENABLE_FOR_COPYABLE(::type, _type) \ 1048 | ref_proxy& operator=(_type const& rhs) { \ 1049 | SOA_PP_MAP(SOA_PP_COPY, ::type, __VA_ARGS__) \ 1050 | return *this; \ 1051 | } \ 1052 | SOA_PP_ENABLE_FOR_COPYABLE(::type, _type) \ 1053 | operator _type() const { \ 1054 | return { __VA_ARGS__ }; \ 1055 | } \ 1056 | \ 1057 | }; \ 1058 | template <> \ 1059 | struct cref_proxy<::type> { \ 1060 | SOA_PP_MAP(SOA_PP_CREF, ::type, __VA_ARGS__) \ 1061 | \ 1062 | SOA_PP_ENABLE_FOR_COPYABLE(::type, _type) \ 1063 | operator _type() const { \ 1064 | return { __VA_ARGS__ }; \ 1065 | } \ 1066 | }; \ 1067 | } \ 1068 | struct _soa__force_semicolon_ 1069 | -------------------------------------------------------------------------------- /tests/tests.cpp: -------------------------------------------------------------------------------- 1 | 2 | #define CATCH_CONFIG_MAIN 3 | #include "catch.hpp" 4 | #include "../soa_vector.hpp" 5 | 6 | // Utility functions. 7 | // Scroll to the bottom to have friendly code. 8 | 9 | // Get iterator on component I/N of values from std::vector. 10 | template 11 | auto make_component_view(std::vector const& vec) { 12 | struct iterator { 13 | T const* ptr; 14 | 15 | bool operator==(iterator const& it) const { return ptr == it.ptr; } 16 | bool operator!=(iterator const& it) const { return ptr != it.ptr; } 17 | 18 | iterator& operator++() { ++ptr; return *this; } 19 | 20 | auto const& operator*() const { 21 | auto const tuple = soa::detail::as_tuple(*ptr); 22 | return std::get(tuple); 23 | } 24 | auto const* operator->() const { 25 | auto const tuple = soa::detail::as_tuple(*ptr); 26 | return &std::get(tuple); 27 | } 28 | }; 29 | struct range { 30 | iterator begin_; 31 | iterator end_; 32 | 33 | int size() const { return end_.ptr - begin_.ptr; } 34 | iterator begin() const { return begin_; } 35 | iterator end() const { return end_; } 36 | }; 37 | return range{ iterator{ vec.data() }, iterator{ vec.data() + vec.size() } }; 38 | } 39 | 40 | // Compare size and values of a specific component from both vectors. 41 | template 42 | void check_vector_integrity(Span const& span, Span2 const& span_copy) { 43 | REQUIRE(span.size() == span_copy.size()); 44 | auto it = span.begin(); 45 | auto it_copy = span_copy.begin(); 46 | for (; it != span.end(); ++it, ++it_copy) { 47 | REQUIRE(*it == *it_copy); 48 | } 49 | REQUIRE(it_copy == span_copy.end()); 50 | } 51 | 52 | // Compare size, capacity and values from both vectors. 53 | template 54 | void check_vector_integrity(soa::vector const& vec, std::vector const& vec_copy, std::index_sequence) { 55 | REQUIRE(vec.empty() == vec_copy.empty()); 56 | REQUIRE(vec.size() == static_cast(vec_copy.size())); 57 | REQUIRE(vec.capacity() == static_cast(vec_copy.capacity())); 58 | (check_vector_integrity( 59 | vec.template get_span(), 60 | make_component_view(vec_copy) 61 | ), ...); 62 | } 63 | 64 | template 65 | struct vector_interface { 66 | soa::vector v1; 67 | std::vector v2; 68 | 69 | void check_integrity() const { 70 | constexpr auto size = soa::vector::components_count; 71 | using seq = std::make_index_sequence; 72 | check_vector_integrity(v1, v2, seq{}); 73 | } 74 | }; 75 | 76 | template 77 | void test_vector(T const& value) { 78 | #define SOA_TEST(n, ...) n.v1.__VA_ARGS__; n.v2.__VA_ARGS__; n.check_integrity() 79 | #define SOA_CHECK(n) n.check_integrity() 80 | 81 | auto i1 = vector_interface{}; 82 | SOA_CHECK(i1); 83 | SOA_TEST(i1, reserve(4)); 84 | SOA_TEST(i1, resize(1)); 85 | SOA_TEST(i1, push_back(value)); 86 | 87 | auto const j = i1; 88 | SOA_CHECK(j); 89 | 90 | auto i2 = std::move(i1); 91 | SOA_CHECK(i2); 92 | SOA_TEST(i2, emplace_back()); 93 | SOA_TEST(i2, pop_back()); 94 | SOA_TEST(i2, clear()); 95 | 96 | #undef SOA_TEST 97 | #undef SOA_CHECK 98 | } 99 | 100 | // Test data 101 | 102 | namespace user { 103 | struct physics { 104 | float pos; 105 | float speed; 106 | float acc; 107 | int id; 108 | }; 109 | } 110 | SOA_DEFINE_TYPE(user::physics, pos, speed, acc, id); 111 | 112 | struct person { 113 | std::string name; 114 | int age; 115 | bool likes_cpp; 116 | }; 117 | SOA_DEFINE_TYPE(person, name, age, likes_cpp); 118 | 119 | struct movable { 120 | std::unique_ptr ptr; 121 | }; 122 | SOA_DEFINE_TYPE(movable, ptr); 123 | 124 | // Tests 125 | 126 | TEST_CASE("generic comparisons against std::vector") { 127 | test_vector(user::physics{ true, 2.0, 3.f, 42 }); 128 | test_vector(person{ "Sid", 22, true }); 129 | } 130 | 131 | TEST_CASE("move-only types") { 132 | auto v2 = soa::vector{}; 133 | auto v1 = std::move(v2); 134 | 135 | v1.emplace_back(std::make_unique()); 136 | REQUIRE(v1.capacity() == 1); 137 | 138 | v1.emplace_back(std::make_unique()); 139 | REQUIRE(v1.capacity() > 1); 140 | 141 | v2 = std::move(v1); 142 | REQUIRE(v2.size() == 2); 143 | } 144 | 145 | TEST_CASE("proxy objects") { 146 | auto persons = soa::vector{}; 147 | persons.emplace_back("Bob", 12); 148 | persons.emplace_back("Alice", 13); 149 | 150 | person const bob = persons[0]; 151 | REQUIRE(bob.name == persons.name[0]); 152 | REQUIRE(bob.age == persons.age[0]); 153 | 154 | persons[1] = { "Chuck", 15, true }; 155 | REQUIRE(persons[1].name == "Chuck"); 156 | REQUIRE(persons[1].age == 15); 157 | 158 | auto challenger = person{"My name is too long to fit in std::string SBO", 16, true }; 159 | persons.back() = challenger; 160 | REQUIRE(persons.back().name == challenger.name); 161 | REQUIRE(persons.back().age == challenger.age); 162 | 163 | persons.front() = std::move(challenger); 164 | REQUIRE(persons.front().name != challenger.name); 165 | REQUIRE(persons.front().name == persons.name.back()); 166 | REQUIRE(persons.front().age == persons.age.back()); 167 | } 168 | 169 | TEST_CASE("iteration on proxies") { 170 | auto persons = soa::vector{}; 171 | persons.emplace_back("Bob", 12); 172 | persons.emplace_back("Alice", 13); 173 | 174 | auto const ages = persons.age[0] + persons.age[1]; 175 | 176 | auto ages_1 = 0; 177 | for (int i = 0; i < persons.size(); ++i) { 178 | ages_1 += persons[i].age; 179 | } 180 | REQUIRE(ages == ages_1); 181 | 182 | auto ages_2 = 0; 183 | for (auto p : persons) ages_2 += p.age; 184 | REQUIRE(ages == ages_2); 185 | 186 | auto ages_3 = 0; 187 | std::for_each(persons.cbegin(), persons.cend(), [&] (auto const& p) { 188 | ages_3 += p.age; 189 | }); 190 | REQUIRE(ages == ages_3); 191 | } 192 | 193 | TEST_CASE("'at(index)' throws correctly") { 194 | auto persons = soa::vector{}; 195 | persons.emplace_back("Bob", 12); 196 | persons.emplace_back("Alice", 13); 197 | 198 | CHECK_THROWS_AS(persons.at(2), std::out_of_range); 199 | CHECK_THROWS_AS(persons.age.at(2), std::out_of_range); 200 | 201 | CHECK_NOTHROW(persons.at(1)); 202 | CHECK_NOTHROW(persons.name.at(1)); 203 | } 204 | 205 | TEST_CASE("conditions not covered previously") { 206 | auto v1 = soa::vector{}; 207 | auto v2 = v1; 208 | auto v3 = std::move(v1); 209 | v2 = std::move(v3); 210 | v3 = v2; 211 | REQUIRE(v1.empty()); 212 | REQUIRE(v2.empty()); 213 | REQUIRE(v3.empty()); 214 | REQUIRE(v1.capacity() == 0); 215 | REQUIRE(v2.capacity() == 0); 216 | REQUIRE(v3.capacity() == 0); 217 | 218 | v1.emplace_back(); 219 | REQUIRE(v1.size() == 1); 220 | v1 = std::move(v2); 221 | REQUIRE(v1.empty()); 222 | 223 | v1.reserve(2); 224 | REQUIRE(v1.empty()); 225 | REQUIRE(v1.capacity() == 2); 226 | 227 | v1.resize(3); 228 | REQUIRE(v1.size() == 3); 229 | REQUIRE(v1.capacity() == 3); 230 | 231 | v1.resize(2); 232 | REQUIRE(v1.size() == 2); 233 | REQUIRE(v1.capacity() == 3); 234 | } 235 | --------------------------------------------------------------------------------