Upgrade to Pro — share decks privately, control downloads, hide ads and more …

NDCTechTown - Cache friendly data + functional ...

NDCTechTown - Cache friendly data + functional + ranges = ❤️

Experience has shown that when working with data in bulk, or when the data sets are small, structs of vectors is the way to go for performance. Working with structs of vectors can cumbersome, however. Let's have a look at what can be done with the recent additions to the C++ language and standard library.

Björn Fahller

September 11, 2024
Tweet

More Decks by Björn Fahller

Other Decks in Programming

Transcript

  1. position velocity acceleration mass ← obj1 ← obj2 Björn Fahller

    Cache Friendly + Functional + Ranges = ❤️
  2. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 6/184 Inspiration https://youtu.be/rX0ItVEVjHc Mike Acton – CppCon Keynote 2014 Data Oriented Design https://youtu.be/xm4AQj5PHT4 Mathieu Ropert – ACCU++ 2024 Data Oriented Design and Entity Component System
  3. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 7/184 struct S { int x; int y; int z; int d }; y x z d y x z d y x z d y x z d y x z d y x z d y x z d
  4. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 8/184 struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d
  5. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 9/184 struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access
  6. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 10/184 struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access access
  7. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 11/184 struct S { int x; int y; int z; int d }; read read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access access
  8. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 12/184 struct S { int x; int y; int z; int d }; read read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access access access
  9. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 13/184 struct S { int x; int y; int z; int d }; read read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access access access access
  10. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 14/184 struct S { int x; int y; int z; int d }; read read read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access access access access
  11. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 15/184 struct S { int x; int y; int z; int d }; read read read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access access access access access
  12. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 16/184 struct S { int x; int y; int z; int d }; read read read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access access access access access access
  13. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 17/184 struct S { int x; int y; int z; int d }; read read read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access access access access access access access
  14. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 18/184 struct S { int x; int y; int z; int d }; read read read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access access access access access access access access
  15. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 19/184 vector of structs / structs of vectors struct S { int x; int y; int z; int d }; y x z d y x z d y x z d y x z d y x z d y x z d y x z d x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d
  16. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 20/184 vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d
  17. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 21/184 vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d
  18. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 22/184 vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access
  19. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 23/184 vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access
  20. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 24/184 vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access access
  21. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 25/184 vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access access access
  22. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 26/184 vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access access access access
  23. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 27/184 vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access access access access access
  24. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 28/184 vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access access access access access access
  25. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 29/184 read vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access access access access access access
  26. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 30/184 read vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access access access access access access access
  27. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 31/184 read vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access access access access access access access access
  28. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 32/184 read vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access access access access access access access access access
  29. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 33/184 read vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access access access access access access access access access
  30. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 34/184 read vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access access access access access access access access access
  31. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 35/184 read vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access access access access access access access access access
  32. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 36/184 read vector of structs / structs of vectors struct S { int x; int y; int z; int d }; read y x z d y x z d y x z d y x z d y x z d y x z d y x z d access x x x x x x x x x x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y y y y y y y y y y z z z z z z z z z z z z z z z z z z z z z z z z d d d d d d d d d d d d d d d d d d d d d d d d access access access access access access access access access access
  33. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 37/184 Live Demo vector_of_structs vs struct_of_vectors
  34. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 38/184 Observations In this exampe, struct of vectors...
  35. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 39/184 Observations In this exampe, struct of vectors... … has way fewer memory accesses
  36. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 40/184 Observations In this exampe, struct of vectors... … has way fewer memory accesses … has way fewer cache misses
  37. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 41/184 Observations In this exampe, struct of vectors... … has way fewer memory accesses … has way fewer cache misses … has slightly fewer instructions
  38. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 42/184 Observations In this exampe, struct of vectors... … has way fewer memory accesses … has way fewer cache misses … has slightly fewer instructions … is faster
  39. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 43/184 Observations In this exampe, struct of vectors... … has way fewer memory accesses … has way fewer cache misses … has slightly fewer instructions … is faster … is horrible code to work with
  40. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 44/184 Observations In this exampe, struct of vectors... … has way fewer memory accesses … has way fewer cache misses … has slightly fewer instructions … is faster … is horrible code to work with Can we make it easier to work with and keep the performance?
  41. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 45/184 std::tuple<> is cool! template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; row operator[](size_t i) { auto access = [&]<size_t ... Cs>(std::index_sequence<Cs...>) { return row{std::get<Cs>(data_)[i]...}; }; return std::invoke(access, columns); } ... private: static constexpr std::index_sequence_for<Ts...> columns{}; std::tuple<std::vector<Ts>...> data_; };
  42. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 46/184 std::tuple<> is cool! template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; row operator[](size_t i) { auto access = [&]<size_t ... Cs>(std::index_sequence<Cs...>) { return row{std::get<Cs>(data_)[i]...}; }; return std::invoke(access, columns); } ... private: static constexpr std::index_sequence_for<Ts...> columns{}; std::tuple<std::vector<Ts>...> data_; }; Storage is a tuple of vectors of Ts
  43. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 47/184 std::tuple<> is cool! template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; row operator[](size_t i) { auto access = [&]<size_t ... Cs>(std::index_sequence<Cs...>) { return row{std::get<Cs>(data_)[i]...}; }; return std::invoke(access, columns); } ... private: static constexpr std::index_sequence_for<Ts...> columns{}; std::tuple<std::vector<Ts>...> data_; }; Access a row as a tuple of references to the elements
  44. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 48/184 std::tuple<> is cool! template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; row operator[](size_t i) { auto access = [&]<size_t ... Cs>(std::index_sequence<Cs...>) { return row{std::get<Cs>(data_)[i]...}; }; return std::invoke(access, columns); } ... private: static constexpr std::index_sequence_for<Ts...> columns{}; std::tuple<std::vector<Ts>...> data_; }; Build by dereferencing the same offset in each vector in the tuple
  45. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 49/184 table<Ts...>
  46. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 50/184 template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; void push_back(Ts... ts); void pop_back(); row operator[](size_t i); row back(); void reserve(size_t size); bool empty() const; size_t size() const private: ... }; table<Ts...>
  47. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 51/184 template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; void push_back(Ts... ts); void pop_back(); row operator[](size_t i); row back(); void reserve(size_t size); bool empty() const; size_t size() const private: ... }; template <typename Table, typename P> void drop_if(Table& t, P predicate) { size_t i = 0; while (i < t.size()) { if (predicate(t[i])) { t[i] = std::move(v.back()); t.pop_back(); }  else { ++i; } } } table<Ts...>
  48. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 52/184 template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; void push_back(Ts... ts); void pop_back(); row operator[](size_t i); row back(); void reserve(size_t size); bool empty() const; size_t size() const private: ... }; template <typename Table, typename P> void drop_if(Table& t, P predicate) { size_t i = 0; while (i < t.size()) { if (predicate(t[i])) { t[i] = std::move(v.back()); t.pop_back(); }  else { ++i; } } } while (!values.empty()) { drop_if(values, [&](auto r) { auto [x,y,z,d] = r; return z < start; });         ++iter;         start += 10;     } } table<Ts...>
  49. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 53/184 Live Demo templated
  50. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 54/184 Observations In this exampe...
  51. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 55/184 Observations In this exampe... … the ergonomics is roughly the same as with vector of structs
  52. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 56/184 Observations In this exampe... … the ergonomics is roughly the same as with vector of structs … performance is the same or better than with struct of vectors
  53. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 57/184 Observations In this exampe... … the ergonomics is roughly the same as with vector of structs … performance is the same or better than with struct of vectors But, we also need…
  54. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 58/184 Observations In this exampe... … the ergonomics is roughly the same as with vector of structs … performance is the same or better than with struct of vectors But, we also need… … a stable row-ID to lookup objects
  55. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 59/184 Observations In this exampe... … the ergonomics is roughly the same as with vector of structs … performance is the same or better than with struct of vectors But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges
  56. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 60/184 Observations In this exampe... … the ergonomics is roughly the same as with vector of structs … performance is the same or better than with struct of vectors But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want...
  57. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 61/184 Observations In this exampe... … the ergonomics is roughly the same as with vector of structs … performance is the same or better than with struct of vectors But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns
  58. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 62/184 Observations In this exampe... … the ergonomics is roughly the same as with vector of structs … performance is the same or better than with struct of vectors But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns … a convenient way to call functions with selected elements
  59. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 63/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c d e f h g A B C D E F H G i I 0 1 2 3 4 5 7 6 8 data index first_free = 9
  60. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 64/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c d e f h g A B C D E F H G i I 0 1 2 3 4 5 7 6 8 data index erase first_free = 9
  61. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 65/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c d e f h g A B C D E F H G i I 0 1 2 3 4 5 7 6 8 data index erase first_free = 9
  62. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 66/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c d e f h g A B C D E F H G 0 1 2 3 4 5 7 6 8 data index erase i I first_free = 9
  63. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 67/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c d e f h g A B C D E F H G 0 1 2 3 4 5 7 6 8 data index erase i I 3 first_free = 9
  64. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 68/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c d e f h g A B C D E F H G 0 1 2 3 4 5 7 6 8 data index erase i I 3 first_free = 9
  65. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 69/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c d e f h g A B C D E F H G 0 1 2 3 4 5 7 6 8 data index erase i I 3 first_free = 9 9
  66. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 70/184 first_free = 3 Stable row ID 0 1 2 3 4 5 7 6 8 a b c d e f h g A B C D E F H G 0 1 2 3 4 5 7 6 8 data index erase i I 3 9
  67. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 71/184 first_free = 3 Stable row ID 0 1 2 3 4 5 7 6 8 a b c d e f h g A B C D E F H G 0 1 2 3 4 5 7 6 8 data index erase i I 3 Lookup row_id 8 9
  68. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 72/184 first_free = 3 Stable row ID 0 1 2 3 4 5 7 6 8 a b c d e f h g A B C D E F H G 0 1 2 3 4 5 7 6 8 data index erase i I 3 Lookup row_id 8 9
  69. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 73/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C I E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index first_free = 3
  70. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 74/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C I E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index find ‘i’ first_free = 3
  71. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 75/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C I E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index find ‘i’ first_free = 3
  72. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 76/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C I E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index find ‘i’ first_free = 3
  73. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 77/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C I E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index find ‘i’ ‘i’ is at row_id 8, which is stored at offset 3 in the columns. first_free = 3
  74. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 78/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C J E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index first_free = 3
  75. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 79/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C J E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index insert ‘k’, ‘K’ first_free = 3
  76. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 80/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C J E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index insert ‘k’, ‘K’ first_free = 3
  77. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 81/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C J E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index insert ‘k’, ‘K’ first_free = 3 k K 3
  78. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 82/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C J E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index insert ‘k’, ‘K’ first_free = 3 k K 3
  79. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 83/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C J E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index insert ‘k’, ‘K’ first_free = 3 k K 3
  80. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 84/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C J E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index insert ‘k’, ‘K’ first_free = 9 k K 3
  81. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 85/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C J E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index insert ‘k’, ‘K’ first_free = 9 k K 3 8
  82. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 86/184 Stable row ID 0 1 2 3 4 5 7 6 8 a b c i e f h g A B C J E F H G 0 1 2 9 4 5 7 6 3 data index 0 1 2 8 4 5 7 6 reverse index insert ‘k’, ‘K’ first_free = 9 k K 3 8 Return row_id == 3, which refers to the new element
  83. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 87/184 Stable row ID template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; struct row_id { size_t offset; }; row operator[](row_id id) { auto offset = index_[id.offset].offset; auto access = [&]<size_t ... Cs>(std::index_sequence<Cs...>) { return row{std::get<Cs>(data_)[offset]...}; }; return std::invoke(access, columns); } private: static constexpr auto columns = std::index_sequence_for<Ts...>{}; std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; row_id first_free_ = {0}; };
  84. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 88/184 Stable row ID template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; struct row_id { size_t offset; }; row operator[](row_id id) { auto offset = index_[id.offset].offset; auto access = [&]<size_t ... Cs>(std::index_sequence<Cs...>) { return row{std::get<Cs>(data_)[offset]...}; }; return std::invoke(access, columns); } private: static constexpr auto columns = std::index_sequence_for<Ts...>{}; std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; row_id first_free_ = {0}; }; Translate row_id to offset in the vectors
  85. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 89/184 Stable row ID template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; struct row_id { size_t offset; }; row operator[](row_id id) { auto offset = index_[id.offset].offset; auto access = [&]<size_t ... Cs>(std::index_sequence<Cs...>) { return row{std::get<Cs>(data_)[offset]...}; }; return std::invoke(access, columns); } private: static constexpr auto columns = std::index_sequence_for<Ts...>{}; std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; row_id first_free_ = {0}; }; The rest of the access as before
  86. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 90/184 template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; struct row_id { size_t offset; }; row operator[](row_id id) { auto offset = index_[id.offset].offset; auto access = [&]<size_t ... Cs>(std::index_sequence<Cs...>) { return row{std::get<Cs>(data_)[offset]...}; }; return std::invoke(access, columns); } private: static constexpr auto columns = std::index_sequence_for<Ts...>{}; std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; row_id first_free_ = {0}; }; Stable row ID
  87. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 91/184 template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; struct row_id { size_t offset; }; row operator[](row_id id) { auto offset = index_[id.offset].offset; auto access = [&]<size_t ... Cs>(std::index_sequence<Cs...>) { return row{std::get<Cs>(data_)[offset]...}; }; return std::invoke(access, columns); } private: static constexpr auto columns = std::index_sequence_for<Ts...>{}; std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; row_id first_free_ = {0}; }; Stable row ID template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; struct row_id { size_t offset; }; row_id push_back(Ts... ts); void pop_back(); row operator[](row_id i); row back(); void erase(row_id i); void reserve(size_t size); bool empty() const; size_t size() const; bool has_row(row_id i) const; private: ... };
  88. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 92/184 template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; struct row_id { size_t offset; }; row operator[](row_id id) { auto offset = index_[id.offset].offset; auto access = [&]<size_t ... Cs>(std::index_sequence<Cs...>) { return row{std::get<Cs>(data_)[offset]...}; }; return std::invoke(access, columns); } private: static constexpr auto columns = std::index_sequence_for<Ts...>{}; std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; row_id first_free_ = {0}; }; Stable row ID template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; struct row_id { size_t offset; }; row_id push_back(Ts... ts); void pop_back(); row operator[](row_id i); row back(); void erase(row_id i); void reserve(size_t size); bool empty() const; size_t size() const; bool has_row(row_id i) const; private: ... }; table<int,int,int,int> t; auto i1 = t.push_back(1,2,3,4); auto i2 = t.push_back(2,3,4,5); auto i3 = t.push_back(3,4,5,6); auto i4 = t.push_back(4,5,6,7); t.erase(i2); t.erase(i3); assert(t[i1] == std::tuple(1,2,3,4)); assert(t[i4] == std::tuple(4,5,6,7)); assert(! t.has_row(i2)); assert(! t.has_row(i3));
  89. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 93/184 template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; struct row_id { size_t offset; }; row operator[](row_id id) { auto offset = index_[id.offset].offset; auto access = [&]<size_t ... Cs>(std::index_sequence<Cs...>) { return row{std::get<Cs>(data_)[offset]...}; }; return std::invoke(access, columns); } private: static constexpr auto columns = std::index_sequence_for<Ts...>{}; std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; row_id first_free_ = {0}; }; Stable row ID template <typename ... Ts> class table { public: using row = std::tuple<Ts&...>; struct row_id { size_t offset; }; row_id push_back(Ts... ts); void pop_back(); row operator[](row_id i); row back(); void erase(row_id i); void reserve(size_t size); bool empty() const; size_t size() const; bool has_row(row_id i) const; private: ... }; table<int,int,int,int> t; auto i1 = t.push_back(1,2,3,4); auto i2 = t.push_back(2,3,4,5); auto i3 = t.push_back(3,4,5,6); auto i4 = t.push_back(4,5,6,7); t.erase(i2); t.erase(i3); assert(t[i1] == std::tuple(1,2,3,4)); assert(t[i4] == std::tuple(4,5,6,7)); assert(! t.has_row(i2)); assert(! t.has_row(i3));
  90. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 94/184 Live Demo row_id
  91. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 95/184 Status But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns … a convenient way to call functions with selected elements
  92. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 96/184 Status But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns … a convenient way to call functions with selected elements ✔️
  93. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 97/184 Status But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns … a convenient way to call functions with selected elements ✔️
  94. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 98/184 Iterators • Iterating in storage order is fast! • The prefetcher loves predictable access patterns in contiguous memory • Let the value type be a tuple of references to the elements of each column • Like table<Ts...>::operator[] • Compare with ranges::zip_view
  95. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 99/184 Iterators struct sentinel {}; struct iterator { using value_type = row; using difference_type = ssize_t; value_type operator*() const { return std::invoke([&]<size_t ... Cs>(std::index_sequence<Cs...>){ return value_type{std::get<Cs>(t->data_)[offset]...}; }, table::columns); } iterator& operator++() { ++offset; return *this; } iterator operator++(int) { auto copy = *this; ++*this; return copy; } bool operator==(const iterator&) const = default; bool operator==(sentinel) const { return offset == t->size(); } table* t; size_t offset; };
  96. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 100/ Iterators struct sentinel {}; struct iterator { using value_type = row; using difference_type = ssize_t; value_type operator*() const { return std::invoke([&]<size_t ... Cs>(std::index_sequence<Cs...>){ return value_type{std::get<Cs>(t->data_)[offset]...}; }, table::columns); } iterator& operator++() { ++offset; return *this; } iterator operator++(int) { auto copy = *this; ++*this; return copy; } bool operator==(const iterator&) const = default; bool operator==(sentinel) const { return offset == t->size(); } table* t; size_t offset; }; Instead of one iterator per column, use the table and the column offset.
  97. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 101/ Iterators struct sentinel {}; struct iterator { using value_type = row; using difference_type = ssize_t; value_type operator*() const { return std::invoke([&]<size_t ... Cs>(std::index_sequence<Cs...>){ return value_type{std::get<Cs>(t->data_)[offset]...}; }, table::columns); } iterator& operator++() { ++offset; return *this; } iterator operator++(int) { auto copy = *this; ++*this; return copy; } bool operator==(const iterator&) const = default; bool operator==(sentinel) const { return offset == t->size(); } table* t; size_t offset; }; Dereferencing becomes the same as the old table::operator[] on offset.
  98. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 102/ Iterators struct sentinel {}; struct iterator { using value_type = row; using difference_type = ssize_t; value_type operator*() const { return std::invoke([&]<size_t ... Cs>(std::index_sequence<Cs...>){ return value_type{std::get<Cs>(t->data_)[offset]...}; }, table::columns); } iterator& operator++() { ++offset; return *this; } iterator operator++(int) { auto copy = *this; ++*this; return copy; } bool operator==(const iterator&) const = default; bool operator==(sentinel) const { return offset == t->size(); } table* t; size_t offset; }; Comparing with the end-of-range sentinel becomes checking if the one-past-the-end offset of the table has been reached.
  99. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 103/ template <typename ... Ts> class table { public: struct sentinel {}; struct iterator { ... }; friend class iterator; iterator begin() { return { this, 0 }; } sentinel end() const { return {}; }    size_t size() const { return std::get<0>(data_).size(); } private: static constexpr auto columns = std::index_sequence_for<Ts...>{}; std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; row_id first_free_ = {0}; }; Iterators
  100. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 104/ template <typename ... Ts> class table { public: struct sentinel {}; struct iterator { ... }; friend class iterator; iterator begin() { return { this, 0 }; } sentinel end() const { return {}; }    size_t size() const { return std::get<0>(data_).size(); } private: static constexpr auto columns = std::index_sequence_for<Ts...>{}; std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; row_id first_free_ = {0}; }; begin() and end() becomes trivially simple. Iterators
  101. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 105/ Live Demo filter_iterators
  102. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 106/ row_id It would be nice to be able to get the row_id from *iterator, but how? std::tuple<types&...> does not naturally lend itself to that.
  103. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 107/ row_id It would be nice to be able to get the row_id from *iterator, but how? std::tuple<types&...> does not naturally lend itself to that. Inheritance as row : std::tuple<types&...> { ... }; could take care of it, but then we’d lose destructuring as auto [a,b,c] = *iterator; or auto [a,b,c] = table[id];
  104. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 108/ row_id It would be nice to be able to get the row_id from *iterator, but how? std::tuple<types&...> does not naturally lend itself to that. Inheritance as row : std::tuple<types&...> { ... }; could take care of it, but then we’d lose destructuring as auto [a,b,c] = *iterator; or auto [a,b,c] = table[id]; The standard offers a way to implement destructuring for your own types by:
  105. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 109/ row_id It would be nice to be able to get the row_id from *iterator, but how? std::tuple<types&...> does not naturally lend itself to that. Inheritance as row : std::tuple<types&...> { ... }; could take care of it, but then we’d lose destructuring as auto [a,b,c] = *iterator; or auto [a,b,c] = table[id]; The standard offers a way to implement destructuring for your own types by: Specializing std::tuple_size<T>
  106. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 110/ row_id It would be nice to be able to get the row_id from *iterator, but how? std::tuple<types&...> does not naturally lend itself to that. Inheritance as row : std::tuple<types&...> { ... }; could take care of it, but then we’d lose destructuring as auto [a,b,c] = *iterator; or auto [a,b,c] = table[id]; The standard offers a way to implement destructuring for your own types by: Specializing std::tuple_size<T> Specializing std::tuple_element<idx, T>
  107. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 111/ row_id It would be nice to be able to get the row_id from *iterator, but how? std::tuple<types&...> does not naturally lend itself to that. Inheritance as row : std::tuple<types&...> { ... }; could take care of it, but then we’d lose destructuring as auto [a,b,c] = *iterator; or auto [a,b,c] = table[id]; The standard offers a way to implement destructuring for your own types by: Specializing std::tuple_size<T> Specializing std::tuple_element<idx, T> Implementing your own get<Idx>(T&&) or T::get<Idx>()
  108. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 112/ template <typename ... Ts> class table { public: struct row_id { size_t offset; }; row operator[](row_id id) { return { this, index_[id.offset].offset; } } private: std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; ... }; template <typename ... Ts> struct row { template <size_t I> friend Ts...[I]& get(row& r) { return std::get<I>(r.t->data_)[offset]; } row_id id() const { return t->reverse_index_[offset]; } table<Ts...>* t; size_t offset; }; Our own row type
  109. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 113/ template <typename ... Ts> class table { public: struct row_id { size_t offset; }; row operator[](row_id id) { return { this, index_[id.offset].offset; } } private: std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; ... }; template <typename ... Ts> struct row { template <size_t I> friend Ts...[I]& get(row& r) { return std::get<I>(r.t->data_)[offset]; } row_id id() const { return t->reverse_index_[offset]; } table<Ts...>* t; size_t offset; }; Our own row type Which row in the table
  110. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 114/ template <typename ... Ts> class table { public: struct row_id { size_t offset; }; row operator[](row_id id) { return { this, index_[id.offset].offset; } } private: std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; ... }; template <typename ... Ts> struct row { template <size_t I> friend Ts...[I]& get(row& r) { return std::get<I>(r.t->data_)[offset]; } row_id id() const { return t->reverse_index_[offset]; } table<Ts...>* t; size_t offset; }; Our own row type Get the row offset by lookup in the index
  111. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 115/ template <typename ... Ts> class table { public: struct row_id { size_t offset; }; row operator[](row_id id) { return { this, index_[id.offset].offset; } } private: std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; ... }; template <typename ... Ts> struct row { template <size_t I> friend Ts...[I]& get(row& r) { return std::get<I>(r.t->data_)[offset]; } row_id id() const { return t->reverse_index_[offset]; } table<Ts...>* t; size_t offset; }; Our own row type Getting the row_id back is trivial
  112. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 116/ template <typename ... Ts> class table { public: struct row_id { size_t offset; }; row operator[](row_id id) { return { this, index_[id.offset].offset; } } private: std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; ... }; template <typename ... Ts> struct row { template <size_t I> friend Ts...[I]& get(row& r) { return std::get<I>(r.t->data_)[offset]; } row_id id() const { return t->reverse_index_[offset]; } table<Ts...>* t; size_t offset; }; Our own row type Reference the desired column with tuple lookup.
  113. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 117/ template <typename ... Ts> class table { public: struct row_id { size_t offset; }; row operator[](row_id id) { return { this, index_[id.offset].offset; } } private: std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; ... }; template <typename ... Ts> struct row { template <size_t I> friend Ts...[I]& get(row& r) { return std::get<I>(r.t->data_)[offset]; } row_id id() const { return t->reverse_index_[offset]; } table<Ts...>* t; size_t offset; }; Our own row type C++26 parameter pack indexing magic!
  114. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 118/ Our own row type template <typename ... Ts> class table { public: struct row_id { size_t offset; }; row operator[](row_id id) { return { this, index_[id.offset].offset; } } private: std::tuple<std::vector<Ts>...> data_; std::vector<row_id> reverse_index_; std::vector<row_id> index_; ... }; template <typename ... Ts> struct row<table<Ts...>> { template <size_t I> friend Ts...[I]& get(row& r) { return std::get<I>(r.t->data_)[offset]; } row_id id() const { return t->reverse_index_[offset]; } table<Ts...>* t; size_t offset; }; template <size_t I, typename ... Ts> struct std::tuple_element<I, row<Ts...> { using type = Ts...[I]&; }; template <typename ... Ts> struct std::tuple_size<row<Ts...>> : std::integral_constant<size_t, sizeof...(Ts)> {}; Opt in to destructuring
  115. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 119/ Live Demo filter_iterators_row
  116. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 120/ Status But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns … a convenient way to call functions with selected elements ✔️
  117. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 121/ Status But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns … a convenient way to call functions with selected elements ✔️ ✔️
  118. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 122/ Status But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns … a convenient way to call functions with selected elements ✔️ ✔️
  119. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 123/ Selecting columns
  120. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 124/ Selecting columns With the current row type, we can select columns by explicitly using get<I>(row).
  121. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 125/ Selecting columns With the current row type, we can select columns by explicitly using get<I>(row). void func(const table<int, int, float, std::string>& data) { for (auto r : data) { std::println(“{} {}”, get<0>(r), get<3>(r)); } }
  122. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 126/ Selecting columns With the current row type, we can select columns by explicitly using get<I>(row). void func(const table<int, int, float, std::string>& data) { for (auto r : data) { std::println(“{} {}”, get<0>(r), get<3>(r)); } }
  123. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 127/ Selecting columns With the current row type, we can select columns by explicitly using get<I>(row). void func(const table<int, int, float, std::string>& data) { for (auto r : data) { std::println(“{} {}”, get<0>(r), get<3>(r)); } } Not exactly convenient, is it?
  124. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 128/ Different row type template <typename ...> struct table; template <typename, typename> struct row; template <typename ... Ts, size_t ... Cs> struct row<table<Ts...>, std::index_sequence<Cs...>> { using row_id = typename table<Ts...>::row_id; template <size_t I> friend auto& get(const row& r) { static constexpr std::array columns{Cs...}; return std::get<columns[I]>(r.t->data_)[r.offset]; } ... table<Ts...>* t; size_t offset; };
  125. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 129/ Different row type template <typename ...> struct table; template <typename, typename> struct row; template <typename ... Ts, size_t ... Cs> struct row<table<Ts...>, std::index_sequence<Cs...>> { using row_id = typename table<Ts...>::row_id; template <size_t I> friend auto& get(const row& r) { static constexpr std::array columns{Cs...}; return std::get<columns[I]>(r.t->data_)[r.offset]; } ... table<Ts...>* t; size_t offset; }; The columns in the table that are referenced by this row type
  126. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 130/ Different row type template <typename ...> struct table; template <typename, typename> struct row; template <typename ... Ts, size_t ... Cs> struct row<table<Ts...>, std::index_sequence<Cs...>> { using row_id = typename table<Ts...>::row_id; template <size_t I> friend auto& get(const row& r) { static constexpr std::array columns{Cs...}; return std::get<columns[I]>(r.t->data_)[r.offset]; } ... table<Ts...>* t; size_t offset; }; The columns in the table that are referenced by this row type row<table<int, char, double, long>, std::index_sequence<0,2,3>> r; get<1>(r);
  127. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 131/ Different row type template <typename ...> struct table; template <typename, typename> struct row; template <typename ... Ts, size_t ... Cs> struct row<table<Ts...>, std::index_sequence<Cs...>> { using row_id = typename table<Ts...>::row_id; template <size_t I> friend auto& get(const row& r) { static constexpr std::array columns{Cs...}; return std::get<columns[I]>(r.t->data_)[r.offset]; } ... table<Ts...>* t; size_t offset; }; The columns in the table that are referenced by this row type row<table<int, char, double, long>, std::index_sequence<0,2,3>> r; get<1>(r);
  128. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 132/ Different row type template <typename ...> struct table; template <typename, typename> struct row; template <typename ... Ts, size_t ... Cs> struct row<table<Ts...>, std::index_sequence<Cs...>> { using row_id = typename table<Ts...>::row_id; template <size_t I> friend auto& get(const row& r) { static constexpr std::array columns{Cs...}; return std::get<columns[I]>(r.t->data_)[r.offset]; } ... table<Ts...>* t; size_t offset; }; The columns in the table that are referenced by this row type row<table<int, char, double, long>, std::index_sequence<0,2,3>> r; get<1>(r);
  129. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 133/ Different row type template <typename ...> struct table; template <typename, typename> struct row; template <typename ... Ts, size_t ... Cs> struct row<table<Ts...>, std::index_sequence<Cs...>> { using row_id = typename table<Ts...>::row_id; template <size_t I> friend auto& get(const row& r) { static constexpr std::array columns{Cs...}; return std::get<columns[I]>(r.t->data_)[r.offset]; } ... table<Ts...>* t; size_t offset; }; The columns in the table that are referenced by this row type row<table<int, char, double, long>, std::index_sequence<0,2,3>> r; get<1>(r);
  130. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 134/ Different row type template <typename ...> struct table; template <typename, typename> struct row; template <typename ... Ts, size_t ... Cs> struct row<table<Ts...>, std::index_sequence<Cs...>> { using row_id = typename table<Ts...>::row_id; template <size_t I> friend auto& get(const row& r) { static constexpr std::array columns{Cs...}; return std::get<columns[I]>(r.t->data_)[r.offset]; } ... table<Ts...>* t; size_t offset; };
  131. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 135/ Different row type template <typename ...> struct table; template <typename, typename> struct row; template <typename ... Ts, size_t ... Cs> struct row<table<Ts...>, std::index_sequence<Cs...>> { using row_id = typename table<Ts...>::row_id; template <size_t I> friend auto& get(const row& r) { static constexpr std::array columns{Cs...}; return std::get<columns[I]>(r.t->data_)[r.offset]; } ... table<Ts...>* t; size_t offset; }; Get the I-th of the columns
  132. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 136/ Different row type template <typename ...> struct table; template <typename, typename> struct row; template <typename ... Ts, size_t ... Cs> struct row<table<Ts...>, std::index_sequence<Cs...>> { using row_id = typename table<Ts...>::row_id; template <size_t I> friend auto& get(const row& r) { static constexpr std::array columns{Cs...}; return std::get<columns[I]>(r.t->data_)[r.offset]; } ... table<Ts...>* t; size_t offset; }; template <typename Table, size_t ... Cs> struct std::tuple_size<row<Table, std::index_sequence<Cs...>>> : std::integral_constant<size_t, sizeof...(Cs)> {}; template <size_t I, typename ... Ts, size_t ... Cs> struct std::tuple_element<I, row<table<Ts...>, std::index_sequence<Cs...>>> {     static constexpr std::array columns = { Cs... };     using type = Ts...[columns[I]]&; };
  133. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 137/ Different row type template <typename ...> struct table; template <typename, typename> struct row; template <typename ... Ts, size_t ... Cs> struct row<table<Ts...>, std::index_sequence<Cs...>> { using row_id = typename table<Ts...>::row_id; template <size_t I> friend auto& get(const row& r) { static constexpr std::array columns{Cs...}; return std::get<columns[I]>(r.t->data_)[r.offset]; } ... table<Ts...>* t; size_t offset; }; template <typename Table, size_t ... Cs> struct std::tuple_size<row<Table, std::index_sequence<Cs...>>> : std::integral_constant<size_t, sizeof...(Cs)> {}; template <size_t I, typename ... Ts, size_t ... Cs> struct std::tuple_element<I, row<table<Ts...>, std::index_sequence<Cs...>>> {     static constexpr std::array columns = { Cs... };     using type = Ts...[columns[I]]&; }; The size is the number of columns referenced
  134. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 138/ Different row type template <typename ...> struct table; template <typename, typename> struct row; template <typename ... Ts, size_t ... Cs> struct row<table<Ts...>, std::index_sequence<Cs...>> { using row_id = typename table<Ts...>::row_id; template <size_t I> friend auto& get(const row& r) { static constexpr std::array columns{Cs...}; return std::get<columns[I]>(r.t->data_)[r.offset]; } ... table<Ts...>* t; size_t offset; }; template <typename Table, size_t ... Cs> struct std::tuple_size<row<Table, std::index_sequence<Cs...>>> : std::integral_constant<size_t, sizeof...(Cs)> {}; template <size_t I, typename ... Ts, size_t ... Cs> struct std::tuple_element<I, row<table<Ts...>, std::index_sequence<Cs...>>> {     static constexpr std::array columns = { Cs... };     using type = Ts...[columns[I]]&; }; The type of the I-th element is the type of the I-th column referenced
  135. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 139/ Select columns in a row template <size_t ... Is, typename Table, size_t ... Cs> auto select(const row<Table, std::index_sequence<Cs...>>& r) {     static constexpr size_t columns[] { Cs... };     return row<Table, std::index_sequence<columns[Is]...>>(r); }
  136. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 140/ Select columns in a row template <size_t ... Is, typename Table, size_t ... Cs> auto select(const row<Table, std::index_sequence<Cs...>>& r) {     static constexpr size_t columns[] { Cs... };     return row<Table, std::index_sequence<columns[Is]...>>(r); } Select a subset of columns to reference
  137. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 141/ Select columns in a row template <size_t ... Is, typename Table, size_t ... Cs> auto select(const row<Table, std::index_sequence<Cs...>>& r) {     static constexpr size_t columns[] { Cs... };     return row<Table, std::index_sequence<columns[Is]...>>(r); } Return a row that references that subset
  138. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 142/ Select columns in a row template <size_t ... Is, typename Table, size_t ... Cs> auto select(const row<Table, std::index_sequence<Cs...>>& r) {     static constexpr size_t columns[] { Cs... };     return row<Table, std::index_sequence<columns[Is]...>>(r); } drop_if(values, [](auto r) { auto [x,z] = select<0,2>(r); return x < z; });
  139. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 143/ Select columns in a row template <size_t ... Is, typename Table, size_t ... Cs> auto select(const row<Table, std::index_sequence<Cs...>>& r) {     static constexpr size_t columns[] { Cs... };     return row<Table, std::index_sequence<columns[Is]...>>(r); } drop_if(values, [](auto r) { auto [x,z] = select<0,2>(r); return x < z; }); Select only the columns we’re interested in for the expression
  140. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 144/ Select columns in a row template <size_t ... Is, typename Table, size_t ... Cs> auto select(const row<Table, std::index_sequence<Cs...>>& r) {     static constexpr size_t columns[] { Cs... };     return row<Table, std::index_sequence<columns[Is]...>>(r); } drop_if(values, [](auto r) { auto [x,z] = select<0,2>(r); return x < z; }); Select only the columns we’re interested in for the expression
  141. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 145/ Select columns in an iteration template <typename R, size_t ... Cs> struct range_selector { using r_iterator = decltype(std::declval<R&>().begin()); struct iterator : r_iterator { using difference_type = ssize_t; using value_type = decltype(select<Cs...>(*std::declval<r_iterator>())); iterator(const r_iterator& i) : r_iterator(i) {} auto operator*() const { const r_iterator& i = *this; return select<Cs...>(*i); } }; iterator begin() { return iterator{ r.begin() }; } auto end() { return r.end(); } R& r; };
  142. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 146/ Select columns in an iteration template <typename R, size_t ... Cs> struct range_selector { using r_iterator = decltype(std::declval<R&>().begin()); struct iterator : r_iterator { using difference_type = ssize_t; using value_type = decltype(select<Cs...>(*std::declval<r_iterator>())); iterator(const r_iterator& i) : r_iterator(i) {} auto operator*() const { const r_iterator& i = *this; return select<Cs...>(*i); } }; iterator begin() { return iterator{ r.begin() }; } auto end() { return r.end(); } R& r; }; range_selector refers to another range which has row<> as its value type. range_selector refers to another range which has row<> as its value type.
  143. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 147/ Select columns in an iteration template <typename R, size_t ... Cs> struct range_selector { using r_iterator = decltype(std::declval<R&>().begin()); struct iterator : r_iterator { using difference_type = ssize_t; using value_type = decltype(select<Cs...>(*std::declval<r_iterator>())); iterator(const r_iterator& i) : r_iterator(i) {} auto operator*() const { const r_iterator& i = *this; return select<Cs...>(*i); } }; iterator begin() { return iterator{ r.begin() }; } auto end() { return r.end(); } R& r; };
  144. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 148/ Select columns in an iteration template <typename R, size_t ... Cs> struct range_selector { using r_iterator = decltype(std::declval<R&>().begin()); struct iterator : r_iterator { using difference_type = ssize_t; using value_type = decltype(select<Cs...>(*std::declval<r_iterator>())); iterator(const r_iterator& i) : r_iterator(i) {} auto operator*() const { const r_iterator& i = *this; return select<Cs...>(*i); } }; iterator begin() { return iterator{ r.begin() }; } auto end() { return r.end(); } R& r; }; Find the iterator type of the referenced range
  145. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 149/ Select columns in an iteration template <typename R, size_t ... Cs> struct range_selector { using r_iterator = decltype(std::declval<R&>().begin()); struct iterator : r_iterator { using difference_type = ssize_t; using value_type = decltype(select<Cs...>(*std::declval<r_iterator>())); iterator(const r_iterator& i) : r_iterator(i) {} auto operator*() const { const r_iterator& i = *this; return select<Cs...>(*i); } }; iterator begin() { return iterator{ r.begin() }; } auto end() { return r.end(); } R& r; }; Create a new iterator type via inheritance
  146. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 150/ Select columns in an iteration template <typename R, size_t ... Cs> struct range_selector { using r_iterator = decltype(std::declval<R&>().begin()); struct iterator : r_iterator { using difference_type = ssize_t; using value_type = decltype(select<Cs...>(*std::declval<r_iterator>())); iterator(const r_iterator& i) : r_iterator(i) {} auto operator*() const { const r_iterator& i = *this; return select<Cs...>(*i); } }; iterator begin() { return iterator{ r.begin() }; } auto end() { return r.end(); } R& r; }; The new value type is the result of select<> on the underlying iterator’s value type
  147. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 151/ Select columns in an iteration template <typename R, size_t ... Cs> struct range_selector { using r_iterator = decltype(std::declval<R&>().begin()); struct iterator : r_iterator { using difference_type = ssize_t; using value_type = decltype(select<Cs...>(*std::declval<r_iterator>())); iterator(const r_iterator& i) : r_iterator(i) {} auto operator*() const { const r_iterator& i = *this; return select<Cs...>(*i); } }; iterator begin() { return iterator{ r.begin() }; } auto end() { return r.end(); } R& r; }; Return a row with the selected columns
  148. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 152/ template <typename R, size_t ... Cs> struct range_selector { using r_iterator = decltype(std::declval<R&>().begin()); struct iterator : r_iterator { using difference_type = ssize_t; using value_type = decltype(select<Cs...>(*std::declval<r_iterator>())); iterator(const r_iterator& i) : r_iterator(i) {} auto operator*() const { const r_iterator& i = *this; return select<Cs...>(*i); } }; iterator begin() { return iterator{ r.begin() }; } auto end() { return r.end(); } R& r; }; Select columns in an iteration template <size_t... Cs> struct range_selector_maker { template <typename R> friend range_selector<R, Cs...> operator|(R& r, range_selector_maker) {     return { r }; } }; template <size_t ... Cs> range_selector_maker<Cs...> select() { return {}; }
  149. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 153/ template <typename R, size_t ... Cs> struct range_selector { using r_iterator = decltype(std::declval<R&>().begin()); struct iterator : r_iterator { using difference_type = ssize_t; using value_type = decltype(select<Cs...>(*std::declval<r_iterator>())); iterator(const r_iterator& i) : r_iterator(i) {} auto operator*() const { const r_iterator& i = *this; return select<Cs...>(*i); } }; iterator begin() { return iterator{ r.begin() }; } auto end() { return r.end(); } R& r; }; Select columns in an iteration template <size_t... Cs> struct range_selector_maker { template <typename R> friend range_selector<R, Cs...> operator|(R& r, range_selector_maker) {     return { r }; } }; template <size_t ... Cs> range_selector_maker<Cs...> select() { return {}; } Create a range_selector<> by piping a range to the result of select<>()
  150. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 154/ template <typename R, size_t ... Cs> struct range_selector { using r_iterator = decltype(std::declval<R&>().begin()); struct iterator : r_iterator { using difference_type = ssize_t; using value_type = decltype(select<Cs...>(*std::declval<r_iterator>())); iterator(const r_iterator& i) : r_iterator(i) {} auto operator*() const { const r_iterator& i = *this; return select<Cs...>(*i); } }; iterator begin() { return iterator{ r.begin() }; } auto end() { return r.end(); } R& r; }; Select columns in an iteration template <size_t... Cs> struct range_selector_maker { template <typename R> friend range_selector<R, Cs...> operator|(R& r, range_selector_maker) {     return { r }; } }; template <size_t ... Cs> range_selector_maker<Cs...> select() { return {}; } for (auto [x,d] : values | select<0,3>()) {     std::println("d={} x={}", d, x); }
  151. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 155/ template <typename R, size_t ... Cs> struct range_selector { using r_iterator = decltype(std::declval<R&>().begin()); struct iterator : r_iterator { using difference_type = ssize_t; using value_type = decltype(select<Cs...>(*std::declval<r_iterator>())); iterator(const r_iterator& i) : r_iterator(i) {} auto operator*() const { const r_iterator& i = *this; return select<Cs...>(*i); } }; iterator begin() { return iterator{ r.begin() }; } auto end() { return r.end(); } R& r; }; Select columns in an iteration template <size_t... Cs> struct range_selector_maker { template <typename R> friend range_selector<R, Cs...> operator|(R& r, range_selector_maker) {     return { r }; } }; template <size_t ... Cs> range_selector_maker<Cs...> select() { return {}; } for (auto [x,d] : values | select<0,3>()) {     std::println("d={} x={}", d, x); }
  152. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 156/ Live Demo filter_row_select
  153. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 157/ Status But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns … a convenient way to call functions with selected elements ✔️ ✔️
  154. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 158/ Status But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns … a convenient way to call functions with selected elements ✔️ ✔️ ✔️
  155. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 159/ Status But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns … a convenient way to call functions with selected elements ✔️ ✔️ ✔️
  156. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 160/ drop_if(values, [](auto r) { auto [x,z] = select<0,2>(r); return x < z; }); Apply functions to selected columns
  157. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 161/ drop_if(values, [](auto r) { auto [x,z] = select<0,2>(r); return x < z; }); Apply functions to selected columns template <size_t ... Cs, typename F> auto select(F&& f) requires (! row_type<std::remove_cvref_t<F>>) { return [f = std::forward<F>(f)]<typename T, typename Is>(row<T, Is> r) { return f(select<Cs...>(r)); }; }
  158. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 162/ drop_if(values, [](auto r) { auto [x,z] = select<0,2>(r); return x < z; }); Apply functions to selected columns template <size_t ... Cs, typename F> auto select(F&& f) requires (! row_type<std::remove_cvref_t<F>>) { return [f = std::forward<F>(f)]<typename T, typename Is>(row<T, Is> r) { return f(select<Cs...>(r)); }; } Conflicts with template <size_t ... Is, typename Table, size_t ... Cs> auto select(const row<Table, std::index_sequence<Cs...>>& r);
  159. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 163/ drop_if(values, [](auto r) { auto [x,z] = select<0,2>(r); return x < z; }); Apply functions to selected columns template <size_t ... Cs, typename F> auto select(F&& f) requires (! row_type<std::remove_cvref_t<F>>) { return [f = std::forward<F>(f)]<typename T, typename Is>(row<T, Is> r) { return f(select<Cs...>(r)); }; } Conflicts with template <size_t ... Is, typename Table, size_t ... Cs> auto select(const row<Table, std::index_sequence<Cs...>>& r);
  160. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 164/ drop_if(values, [](auto r) { auto [x,z] = select<0,2>(r); return x < z; }); Apply functions to selected columns template <size_t ... Cs, typename F> auto select(F&& f) requires (! row_type<std::remove_cvref_t<F>>) { return [f = std::forward<F>(f)]<typename T, typename Is>(row<T, Is> r) { return f(select<Cs...>(r)); }; } template <typename> inline constexpr bool is_row_type_v = false; template <typename T, typename Cs> inline constexpr bool is_row_type_v<row<T,Cs>> = true; template <typename T> concept row_type = is_row_type_v<T>;
  161. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 165/ drop_if(values, [](auto r) { auto [x,z] = select<0,2>(r); return x < z; }); Apply functions to selected columns template <size_t ... Cs, typename F> auto select(F&& f) requires (! row_type<std::remove_cvref_t<F>>) { return [f = std::forward<F>(f)]<typename T, typename Is>(row<T, Is> r) { return f(select<Cs...>(r)); }; } template <typename> inline constexpr bool is_row_type_v = false; template <typename T, typename Cs> inline constexpr bool is_row_type_v<row<T,Cs>> = true; template <typename T> concept row_type = is_row_type_v<T>;
  162. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 166/ drop_if(values, [](auto r) { auto [x,z] = select<0,2>(r); return x < z; }); Apply functions to selected columns template <size_t ... Cs, typename F> auto select(F&& f) requires (! row_type<std::remove_cvref_t<F>>) { return [f = std::forward<F>(f)]<typename T, typename Is>(row<T, Is> r) { return f(select<Cs...>(r)); }; }
  163. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 167/ drop_if(values, [](auto r) { auto [x,z] = select<0,2>(r); return x < z; }); drop_if(values, select<0,2>([](auto r) { auto [x,z] = r; return x < z; })); Apply functions to selected columns template <size_t ... Cs, typename F> auto select(F&& f) requires (! row_type<std::remove_cvref_t<F>>) { return [f = std::forward<F>(f)]<typename T, typename Is>(row<T, Is> r) { return f(select<Cs...>(r)); }; }
  164. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 168/ drop_if(values, [](auto r) { auto [x,z] = select<0,2>(r); return x < z; }); drop_if(values, select<0,2>([](auto r) { auto [x,z] = r; return x < z; })); Apply functions to selected columns template <size_t ... Cs, typename F> auto select(F&& f) requires (! row_type<std::remove_cvref_t<F>>) { return [f = std::forward<F>(f)]<typename T, typename Is>(row<T, Is> r) { return f(select<Cs...>(r)); }; }
  165. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 169/ drop_if(values, [](auto r) { auto [x,z] = select<0,2>(r); return x < z; }); drop_if(values, select<0,2>([](auto r) { auto [x,z] = r; return x < z; })); Apply functions to selected columns template <size_t ... Cs, typename F> auto select(F&& f) requires (! row_type<std::remove_cvref_t<F>>) { return [f = std::forward<F>(f)]<typename T, typename Is>(row<T, Is> r) { return f(select<Cs...>(r)); }; } template <size_t ... Cs, typename F> auto select(F&& f) requires (! row_type<std::remove_cvref_t<F>>) { return [f = std::forward<F>(f)](row_type auto r) { return f(select<Cs...>(r)); }; }
  166. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 170/ Apply functions to selected columns drop_if(values, select<0,2>([](auto r) { auto [x,z] = r; return x < z; })); template <typename F> auto apply(F&& f) { return [f = std::forward<F>(f)]<row_type R>(R r) { return std::invoke([&]<size_t ... Cs>(std::index_sequence<Cs...>) { return f(get<Cs>(r)...); }, std::make_index_sequence<std::tuple_size_v<R>>{}); }; }
  167. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 171/ Apply functions to selected columns drop_if(values, select<0,2>([](auto r) { auto [x,z] = r; return x < z; })); template <typename F> auto apply(F&& f) { return [f = std::forward<F>(f)]<row_type R>(R r) { return std::invoke([&]<size_t ... Cs>(std::index_sequence<Cs...>) { return f(get<Cs>(r)...); }, std::make_index_sequence<std::tuple_size_v<R>>{}); }; } apply() takes a function and returns a lambda that takes a row.
  168. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 172/ Apply functions to selected columns drop_if(values, select<0,2>([](auto r) { auto [x,z] = r; return x < z; })); template <typename F> auto apply(F&& f) { return [f = std::forward<F>(f)]<row_type R>(R r) { return std::invoke([&]<size_t ... Cs>(std::index_sequence<Cs...>) { return f(get<Cs>(r)...); }, std::make_index_sequence<std::tuple_size_v<R>>{}); }; } When called, it calls the bound function f with every element in the row.
  169. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 173/ Apply functions to selected columns drop_if(values, select<0,2>([](auto r) { auto [x,z] = r; return x < z; })); drop_if(values, select<0,2>(apply([](auto x, auto z) { return x < z; }))); template <typename F> auto apply(F&& f) { return [f = std::forward<F>(f)]<row_type R>(R r) { return std::invoke([&]<size_t ... Cs>(std::index_sequence<Cs...>) { return f(get<Cs>(r)...); }, std::make_index_sequence<std::tuple_size_v<R>>{}); }; }
  170. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 174/ Apply functions to selected columns drop_if(values, select<0,2>([](auto r) { auto [x,z] = r; return x < z; })); drop_if(values, select<0,2>(apply([](auto x, auto z) { return x < z; }))); template <typename F> auto apply(F&& f) { return [f = std::forward<F>(f)]<row_type R>(R r) { return std::invoke([&]<size_t ... Cs>(std::index_sequence<Cs...>) { return f(get<Cs>(r)...); }, std::make_index_sequence<std::tuple_size_v<R>>{}); }; }
  171. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 175/ Apply functions to selected columns drop_if(values, select<0,2>([](auto r) { auto [x,z] = r; return x < z; })); drop_if(values, select<0,2>(apply([](auto x, auto z) { return x < z; }))); template <typename F> auto apply(F&& f) { return [f = std::forward<F>(f)]<row_type R>(R r) { return std::invoke([&]<size_t ... Cs>(std::index_sequence<Cs...>) { return f(get<Cs>(r)...); }, std::make_index_sequence<std::tuple_size_v<R>>{}); }; } Isn’t there a name for this?
  172. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 176/ Apply functions to selected columns drop_if(values, select<0,2>([](auto r) { auto [x,z] = r; return x < z; })); drop_if(values, select<0,2>(apply([](auto x, auto z) { return x < z; }))); drop_if(values, select<0,2>(apply(std::less{}))); template <typename F> auto apply(F&& f) { return [f = std::forward<F>(f)]<row_type R>(R r) { return std::invoke([&]<size_t ... Cs>(std::index_sequence<Cs...>) { return f(get<Cs>(r)...); }, std::make_index_sequence<std::tuple_size_v<R>>{}); }; }
  173. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 177/ Apply functions to selected columns drop_if(values, select<0,2>([](auto r) { auto [x,z] = r; return x < z; })); drop_if(values, select<0,2>(apply([](auto x, auto z) { return x < z; }))); drop_if(values, select<0,2>(apply(std::less{}))); template <typename F> auto apply(F&& f) { return [f = std::forward<F>(f)]<row_type R>(R r) { return std::invoke([&]<size_t ... Cs>(std::index_sequence<Cs...>) { return f(get<Cs>(r)...); }, std::make_index_sequence<std::tuple_size_v<R>>{}); }; }
  174. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 178/ Live Demo filter_function_select
  175. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 179/ Status But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns … a convenient way to call functions with selected elements ✔️ ✔️ ✔️ ✔️
  176. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 180/ Status But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns … a convenient way to call functions with selected elements ✔️ ✔️ ✔️ ✔️ ✔️
  177. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 181/ Status But, we also need… … a stable row-ID to lookup objects … an iterator interface to work with ranges And want... … a convenient way to look at only some columns … a convenient way to call functions with selected elements ✔️ ✔️ ✔️ ✔️ ✔️
  178. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 182/ Cache Friendly + Functional + Ranges = ❤️ Give it a go, and please help out if you can https://github.com/rollbear/columnist
  179. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 183/ Cache Friendly + Functional + Ranges = ❤️ Give it a go, and please help out if you can https://github.com/rollbear/columnist https://github.com/rollbear/cache_functional_ranges/tree/NDC Source examples from this presentation
  180. Cache Friendly + Functional + Ranges = NDC{TechTown} 2024 ©

    Björn Fahller ❤️ @bjorn_fahller@mastodon.social 184/ Björn Fahller bjorn@fahller.se @bjorn_fahller@mastodon.social @rollbear Cache Friendly + Functional + Ranges = ❤️