From 28fd0836033184aae686eaf2bde79fac1842e65a Mon Sep 17 00:00:00 2001 From: Chris Blume Date: Tue, 3 Feb 2026 01:13:37 -0500 Subject: [PATCH 1/5] Add SlotMap container --- Code/max/Containers/SlotMap.hpp | 103 ++++++++++ Code/max/Containers/SlotMap.inl | 169 ++++++++++++++++ Code/max/Containers/SlotMapTest.cpp | 189 ++++++++++++++++++ Code/max/Containers/SlotMapTest.hpp | 18 ++ Code/max/Testing/AutomatedTestsEntryPoint.cpp | 2 + Projects/Make/Makefile | 1 + Projects/VisualStudio/max/max.vcxproj | 4 + Projects/VisualStudio/max/max.vcxproj.filters | 12 ++ .../maxAutomatedTests.vcxproj | 2 + .../maxAutomatedTests.vcxproj.filters | 6 + 10 files changed, 506 insertions(+) create mode 100644 Code/max/Containers/SlotMap.hpp create mode 100644 Code/max/Containers/SlotMap.inl create mode 100644 Code/max/Containers/SlotMapTest.cpp create mode 100644 Code/max/Containers/SlotMapTest.hpp diff --git a/Code/max/Containers/SlotMap.hpp b/Code/max/Containers/SlotMap.hpp new file mode 100644 index 0000000..908ed3e --- /dev/null +++ b/Code/max/Containers/SlotMap.hpp @@ -0,0 +1,103 @@ +// Copyright 2021, The max Contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MAX_CONTAINERS_SLOTMAP_HPP +#define MAX_CONTAINERS_SLOTMAP_HPP + +#include +#include +#include +#include + +#include +#include + + +namespace max +{ +MAX_CURRENT_VERSION_NAMESPACE_BEGIN(v0) +{ +namespace Containers +{ + + // SlotMap is a container that provides a stable handle to an element that was inserted. + // + // Insertion: O(1)* + // Access: O(1) + // Removal: O(1) + // *Unless the BackingType requires reallocation. Then it is O(1) amortized. + template typename BackingType = std::vector> + class SlotMap { + public: + + + + // Required to satisfy the C++ "Container" requirements + typedef T value_type; + typedef T& reference; + typedef const T& const_reference; + typedef BackingType::iterator iterator; + typedef BackingType::const_iterator const_iterator; + typedef BackingType::difference_type difference_type; + typedef BackingType::size_type size_type; + + + + HandleType push_back(T element) noexcept; + + template + HandleType emplace_back(Args&&... args) noexcept; + + typename reference operator[](HandleType handle) noexcept; + typename const_reference operator[](HandleType handle) const noexcept; + + void remove(HandleType handle) noexcept; + void pop_back() noexcept; + + + + // Required to satisfy the C++ "Container" requirements + typename iterator begin() noexcept; + typename const_iterator begin() const noexcept; + typename const_iterator cbegin() const noexcept; + typename iterator end() noexcept; + typename const_iterator end() const noexcept; + typename const_iterator cend() noexcept; + + typename size_type size() const noexcept; + constexpr typename size_type max_size() const noexcept; + + + + private: + + // The indices are stable and act as handles. + typename BackingType indices_; + // If we wanted to prevent a user accidently reusing a handle from an object they removed, + // |indices_| could be a tuple of HandleType and generation counter. + // Increment the generation every time an element is removed and compare against it when accessing. + // However, this is designed for performance and will assume the programmer did not make a mistake. + + // |data_| and |reverse_indices_| are parallel. The nth element in one corresponds to the nth element in the other. + // This means once we have an element's index into |data_|, we can use that same index into |reverse_indices_| to find + // the element in |indices_| that points here. + typename BackingType data_; + typename BackingType reverse_indices_; + + void remove_by_index(size_t index) noexcept; + + friend bool operator ==(const SlotMap& lhs, const SlotMap& rhs) noexcept; + friend bool operator !=(const SlotMap& lhs, const SlotMap& rhs) noexcept; + friend void swap(SlotMap& lhs, SlotMap& rhs) noexcept; + + }; + +} // namespace Containers +} // MAX_CURRENT_VERSION_NAMESPACE_BEGIN( v0 ) +MAX_CURRENT_VERSION_NAMESPACE_END(v0) +} // namespace max + +#include "SlotMap.inl" + +#endif // #ifndef MAX_CONTAINERS_SLOTMAP_HPP \ No newline at end of file diff --git a/Code/max/Containers/SlotMap.inl b/Code/max/Containers/SlotMap.inl new file mode 100644 index 0000000..d884b9d --- /dev/null +++ b/Code/max/Containers/SlotMap.inl @@ -0,0 +1,169 @@ +// Copyright 2021, The max Contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include + +namespace max +{ +namespace v0 +{ +namespace Containers +{ + + template typename BackingType> + HandleType SlotMap::push_back(T element) noexcept { + const auto data_size = data_.size(); + data_.push_back(std::move(element)); + + const auto reverse_indices_size = reverse_indices_.size(); + if (data_size == reverse_indices_size) { + // No elements had previously been removed, leaving gaps. + // This means we cannot reuse the handles and indices we left in place. + // We must add new ones. + auto new_handle = reverse_indices_size; + indices_.push_back(new_handle); + reverse_indices_.push_back(new_handle); + return new_handle; + } + else { + // We previously left the elements in |indices_| and |reverse_indices_|. + // That means we can just reuse those handles. + return reverse_indices_[data_size]; + } + } + + template typename BackingType> + template + HandleType SlotMap::emplace_back(Args&&... args) noexcept { + // TODO: constructing the element then moving it to push_back() really defeats the purpose of an emplace_back. + // Do this correctly. + return push_back(T{ std::forward(args)... }); + } + + template typename BackingType> + typename SlotMap::reference SlotMap::operator[](HandleType handle) noexcept { + // Assumes |handle| is within range. + + const auto index = indices_[handle]; + return data_[index]; + } + + template typename BackingType> + typename SlotMap::const_reference SlotMap::operator[](HandleType handle) const noexcept { + // Assumes |handle| is within range. + + const auto index = indices_[handle]; + return data_[index]; + } + + template typename BackingType> + void SlotMap::remove(HandleType handle) noexcept { + remove_by_index(indices_[handle]); + } + + template typename BackingType> + void SlotMap::pop_back() noexcept { + remove_by_index(data_.size() - 1); + } + + + + // Required to satisfy the C++ "Container" requirements + template typename BackingType> + typename SlotMap::iterator SlotMap::begin() noexcept { + return data_.begin(); + } + + template typename BackingType> + typename SlotMap::const_iterator SlotMap::begin() const noexcept { + return data_.begin(); + } + + template typename BackingType> + typename SlotMap::const_iterator SlotMap::cbegin() const noexcept { + return data_.begin(); + } + + template typename BackingType> + typename SlotMap::iterator SlotMap::end() noexcept { + return data_.end(); + } + + template typename BackingType> + typename SlotMap::const_iterator SlotMap::end() const noexcept { + return data_.end(); + } + + template typename BackingType> + typename SlotMap::const_iterator SlotMap::cend() noexcept { + return data_.end(); + } + + template typename BackingType> + typename SlotMap::size_type SlotMap::size() const noexcept { + return data_.size(); + } + + template typename BackingType> + constexpr typename SlotMap::size_type SlotMap::max_size() const noexcept { + return data_.max_size(); + } + + template typename BackingType> + void SlotMap::remove_by_index(size_t index) noexcept { + // Removing an element in the middle of a vector will cause all elements after it to shift over. + // Removing the final element does not have this effect. + + // In order to remove any given element, we first swap it with the element in the final spot. + // Then we can remove it and update indices. + + using std::swap; + + auto data_size = data_.size() - 1; // .size() tells us the index past the end. + + swap(data_[index], data_[data_size]); + swap(reverse_indices_[index], reverse_indices_[data_size]); // We only resize |data_|, so use it's size--not |reverse_indices_|'s. + data_.resize(data_size); + + // The catch is we only know one index, the parameter. + // We don't know which element in |indices_| corresponds to the final element in |data_|. + // To solve that problem, the |reverse_indices_| vector is parallel to the |data_| vector. + // An index into one is also an index into the other. + // We have the index of the final element in |data_|, so we can use it in |reverse_indices_| to get the corresponding entry in |indices_|. + auto reverse_index = reverse_indices_[data_size]; + indices_[reverse_index] = data_size; + + // We also update the reverse index of the that was removed. + // This is because it will be reused later when more elements are appended. + reverse_index = reverse_indices_[index]; + indices_[reverse_index] = index; + } + + template typename BackingType> + bool operator ==(const SlotMap& lhs, const SlotMap& rhs) noexcept { + return lhs.indices_ == rhs.indices_ && + lhs.data_ == rhs.data_ && + lhs.reverse_indices_ == rhs.reverse_indices_; + } + + template typename BackingType> + bool operator !=(const SlotMap& lhs, const SlotMap& rhs) noexcept { + return !(lhs == rhs); + } + + template typename BackingType> + void swap(SlotMap& lhs, SlotMap& rhs) noexcept { + using std::swap; + + swap(lhs.indices_, rhs.indices_); + swap(lhs.data_, rhs.data_); + swap(lhs.reverse_indices_, rhs.reverse_indices_); + } + +} // namespace Containers +} // namespace v0 +} // namespace max diff --git a/Code/max/Containers/SlotMapTest.cpp b/Code/max/Containers/SlotMapTest.cpp new file mode 100644 index 0000000..4909598 --- /dev/null +++ b/Code/max/Containers/SlotMapTest.cpp @@ -0,0 +1,189 @@ +// Copyright 2026, The max Contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "SlotMapTest.hpp" +#include +#include +#include +#include + +namespace maxAutomatedTests +{ +namespace Containers +{ + + void RunSlotMapTestSuite() + { + max::Testing::CoutResultPolicy ResultPolicy; + auto SlotMapTestSuite = max::Testing::TestSuite< max::Testing::CoutResultPolicy >{ "max::Containers::SlotMap test suite", std::move(ResultPolicy) }; + + SlotMapTestSuite.AddTest(max::Testing::Test< max::Testing::CoutResultPolicy >{ "push_back appends element", [](max::Testing::Test< max::Testing::CoutResultPolicy >& CurrentTest, max::Testing::CoutResultPolicy const& ResultPolicy) { + auto test = max::Containers::SlotMap< int >{}; + CurrentTest.MAX_TESTING_ASSERT(test.size() == 0); + + auto handle = test.push_back(10); + + CurrentTest.MAX_TESTING_ASSERT(test.size() == 1); + CurrentTest.MAX_TESTING_ASSERT(test[handle] == 10); + } + }); + + SlotMapTestSuite.AddTest(max::Testing::Test< max::Testing::CoutResultPolicy >{ "emplace_back constructs in-place, appended", [](max::Testing::Test< max::Testing::CoutResultPolicy >& CurrentTest, max::Testing::CoutResultPolicy const& ResultPolicy) { + struct Element { + int first_; + int second_; + }; + auto test = max::Containers::SlotMap< Element >{}; + CurrentTest.MAX_TESTING_ASSERT(test.size() == 0); + + auto handle = test.emplace_back(10, 20); + + CurrentTest.MAX_TESTING_ASSERT(test.size() == 1); + CurrentTest.MAX_TESTING_ASSERT(test[handle].first_ == 10); + CurrentTest.MAX_TESTING_ASSERT(test[handle].second_ == 20); + } + }); + + SlotMapTestSuite.AddTest(max::Testing::Test< max::Testing::CoutResultPolicy >{ "operator[] fetches the element", [](max::Testing::Test< max::Testing::CoutResultPolicy >& CurrentTest, max::Testing::CoutResultPolicy const& ResultPolicy) { + auto test = max::Containers::SlotMap< int >{}; + auto handle = test.push_back(10); + + CurrentTest.MAX_TESTING_ASSERT(test[handle] == 10); + } + }); + + SlotMapTestSuite.AddTest(max::Testing::Test< max::Testing::CoutResultPolicy >{ "const operator[] fetches the element", [](max::Testing::Test< max::Testing::CoutResultPolicy >& CurrentTest, max::Testing::CoutResultPolicy const& ResultPolicy) { + auto test = max::Containers::SlotMap< int >{}; + auto handle = test.push_back(10); + + const auto& const_test = test; + + CurrentTest.MAX_TESTING_ASSERT(const_test[handle] == 10); + } + }); + + SlotMapTestSuite.AddTest(max::Testing::Test< max::Testing::CoutResultPolicy >{ "remove removes the element", [](max::Testing::Test< max::Testing::CoutResultPolicy >& CurrentTest, max::Testing::CoutResultPolicy const& ResultPolicy) { + auto test = max::Containers::SlotMap< int >{}; + auto one_handle = test.push_back(1); + auto two_handle = test.push_back(2); + CurrentTest.MAX_TESTING_ASSERT(test.size() == 2); + + test.remove(one_handle); + + CurrentTest.MAX_TESTING_ASSERT(test.size() == 1); + CurrentTest.MAX_TESTING_ASSERT(test[two_handle] == 2); + } + }); + + SlotMapTestSuite.AddTest(max::Testing::Test< max::Testing::CoutResultPolicy >{ "pop_back removes the last element", [](max::Testing::Test< max::Testing::CoutResultPolicy >& CurrentTest, max::Testing::CoutResultPolicy const& ResultPolicy) { + auto test = max::Containers::SlotMap< int >{}; + auto one_handle = test.push_back(1); + test.push_back(2); + CurrentTest.MAX_TESTING_ASSERT(test.size() == 2); + + test.pop_back(); + + CurrentTest.MAX_TESTING_ASSERT(test.size() == 1); + CurrentTest.MAX_TESTING_ASSERT(test[one_handle] == 1); + } + }); + + SlotMapTestSuite.AddTest(max::Testing::Test< max::Testing::CoutResultPolicy >{ "pop_back removes the last element", [](max::Testing::Test< max::Testing::CoutResultPolicy >& CurrentTest, max::Testing::CoutResultPolicy const& ResultPolicy) { + auto test = max::Containers::SlotMap< int >{}; + auto one_handle = test.push_back(1); + test.push_back(2); + CurrentTest.MAX_TESTING_ASSERT(test.size() == 2); + + test.pop_back(); + + CurrentTest.MAX_TESTING_ASSERT(test.size() == 1); + CurrentTest.MAX_TESTING_ASSERT(test[one_handle] == 1); + } + }); + + SlotMapTestSuite.AddTest(max::Testing::Test< max::Testing::CoutResultPolicy >{ "begin() and end() allow iteration", [](max::Testing::Test< max::Testing::CoutResultPolicy >& CurrentTest, max::Testing::CoutResultPolicy const& ResultPolicy) { + auto test = max::Containers::SlotMap< size_t >{}; + auto one_handle = test.push_back(1); + auto two_handle = test.push_back(2); + + auto element_count = size_t{ 0 }; + + auto end = test.end(); + for (auto it = test.begin(); it != end; ++it) { + element_count++; + *it = 10 * element_count; + } + + CurrentTest.MAX_TESTING_ASSERT(test[one_handle] == 10); + CurrentTest.MAX_TESTING_ASSERT(test[two_handle] == 20); + CurrentTest.MAX_TESTING_ASSERT(element_count == 2); + } + }); + + SlotMapTestSuite.AddTest(max::Testing::Test< max::Testing::CoutResultPolicy >{ "const begin() and const end() allow iteration", [](max::Testing::Test< max::Testing::CoutResultPolicy >& CurrentTest, max::Testing::CoutResultPolicy const& ResultPolicy) { + auto test = max::Containers::SlotMap< int >{}; + test.push_back(1); + test.push_back(2); + const auto& const_test = test; + + auto element_count = size_t{ 0 }; + + auto end = const_test.end(); + for (auto it = const_test.begin(); it != end; ++it) { + element_count++; + } + + CurrentTest.MAX_TESTING_ASSERT(element_count == 2); + } + }); + + SlotMapTestSuite.AddTest(max::Testing::Test< max::Testing::CoutResultPolicy >{ "cbegin() and cend() allow iteration", [](max::Testing::Test< max::Testing::CoutResultPolicy >& CurrentTest, max::Testing::CoutResultPolicy const& ResultPolicy) { + auto test = max::Containers::SlotMap< int >{}; + test.push_back(1); + test.push_back(2); + + auto element_count = size_t{ 0 }; + + auto end = test.cend(); + for (auto it = test.cbegin(); it != end; ++it) { + element_count++; + } + + CurrentTest.MAX_TESTING_ASSERT(element_count == 2); + } + }); + + SlotMapTestSuite.AddTest(max::Testing::Test< max::Testing::CoutResultPolicy >{ "size() returns the element count", [](max::Testing::Test< max::Testing::CoutResultPolicy >& CurrentTest, max::Testing::CoutResultPolicy const& ResultPolicy) { + auto test = max::Containers::SlotMap< int >{}; + + CurrentTest.MAX_TESTING_ASSERT(test.size() == 0); + + test.push_back(1); + + CurrentTest.MAX_TESTING_ASSERT(test.size() == 1); + + test.push_back(2); + + CurrentTest.MAX_TESTING_ASSERT(test.size() == 2); + } + }); + + SlotMapTestSuite.AddTest(max::Testing::Test< max::Testing::CoutResultPolicy >{ "max_size() returns the max element count", [](max::Testing::Test< max::Testing::CoutResultPolicy >& CurrentTest, max::Testing::CoutResultPolicy const& ResultPolicy) { + auto test = max::Containers::SlotMap< int >{}; + + auto max_size = test.max_size(); + + CurrentTest.MAX_TESTING_ASSERT(max_size > test.size()); + + test.push_back(1); + + CurrentTest.MAX_TESTING_ASSERT(max_size == test.max_size()); + } + }); + + SlotMapTestSuite.RunTests(); + } + +} // namespace Containers +} // namespace maxAutomatedTests diff --git a/Code/max/Containers/SlotMapTest.hpp b/Code/max/Containers/SlotMapTest.hpp new file mode 100644 index 0000000..c9aca64 --- /dev/null +++ b/Code/max/Containers/SlotMapTest.hpp @@ -0,0 +1,18 @@ +// Copyright 2026, The max Contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MAXAUTOMATEDTESTS_CONTAINERS_SLOTMAPTEST_HPP +#define MAXAUTOMATEDTESTS_CONTAINERS_SLOTMAPTEST_HPP + +namespace maxAutomatedTests +{ +namespace Containers +{ + + void RunSlotMapTestSuite(); + +} // namespace Containers +} // namespace maxAutomatedTests + +#endif // #ifndef MAXAUTOMATEDTESTS_CONTAINERS_SLOTMAPTEST_HPP diff --git a/Code/max/Testing/AutomatedTestsEntryPoint.cpp b/Code/max/Testing/AutomatedTestsEntryPoint.cpp index df4f22e..f8f8091 100644 --- a/Code/max/Testing/AutomatedTestsEntryPoint.cpp +++ b/Code/max/Testing/AutomatedTestsEntryPoint.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -19,6 +20,7 @@ int main() maxAutomatedTests::Containers::RunPointTestSuite(); maxAutomatedTests::Containers::RunRangeTestSuite(); maxAutomatedTests::Containers::RunRectangleTestSuite(); + maxAutomatedTests::Containers::RunSlotMapTestSuite(); maxAutomatedTests::Containers::RunVectorTestSuite(); return 0; diff --git a/Projects/Make/Makefile b/Projects/Make/Makefile index 661a79a..011d96f 100644 --- a/Projects/Make/Makefile +++ b/Projects/Make/Makefile @@ -74,6 +74,7 @@ AUTOMATED_TEST_CXX_SRCS = \ ../../Code/max/Containers/PointTest.cpp \ ../../Code/max/Containers/RangeTest.cpp \ ../../Code/max/Containers/RectangleTest.cpp \ + ../../Code/max/Containers/SlotMapTest.cpp \ ../../Code/max/Containers/VectorTest.cpp \ ../../Code/max/Containers/StateMachine/AnythingMatcherTest.cpp \ ../../Code/max/Containers/StateMachine/NodeTest.cpp \ diff --git a/Projects/VisualStudio/max/max.vcxproj b/Projects/VisualStudio/max/max.vcxproj index 6658af7..a46eec0 100644 --- a/Projects/VisualStudio/max/max.vcxproj +++ b/Projects/VisualStudio/max/max.vcxproj @@ -51,6 +51,7 @@ + @@ -147,6 +148,8 @@ true true + + true @@ -318,6 +321,7 @@ true true + true true diff --git a/Projects/VisualStudio/max/max.vcxproj.filters b/Projects/VisualStudio/max/max.vcxproj.filters index b3aed8e..4d421cf 100644 --- a/Projects/VisualStudio/max/max.vcxproj.filters +++ b/Projects/VisualStudio/max/max.vcxproj.filters @@ -204,6 +204,9 @@ Code\max\Containers\StateMachine + + Code\max\Containers + @@ -474,6 +477,12 @@ Code\max\Containers\StateMachine + + Code\max\Containers + + + Code\max\Containers + @@ -599,6 +608,9 @@ Code\max\Containers\StateMachine + + Code\max\Containers + diff --git a/Projects/VisualStudio/maxAutomatedTests/maxAutomatedTests.vcxproj b/Projects/VisualStudio/maxAutomatedTests/maxAutomatedTests.vcxproj index 47b3df8..1d2f622 100644 --- a/Projects/VisualStudio/maxAutomatedTests/maxAutomatedTests.vcxproj +++ b/Projects/VisualStudio/maxAutomatedTests/maxAutomatedTests.vcxproj @@ -25,6 +25,7 @@ + @@ -43,6 +44,7 @@ + diff --git a/Projects/VisualStudio/maxAutomatedTests/maxAutomatedTests.vcxproj.filters b/Projects/VisualStudio/maxAutomatedTests/maxAutomatedTests.vcxproj.filters index 9367e49..61ea15d 100644 --- a/Projects/VisualStudio/maxAutomatedTests/maxAutomatedTests.vcxproj.filters +++ b/Projects/VisualStudio/maxAutomatedTests/maxAutomatedTests.vcxproj.filters @@ -63,6 +63,9 @@ Containers\StateMachine + + Containers + @@ -110,5 +113,8 @@ Containers\StateMachine + + Containers + \ No newline at end of file From 43bbf7d16d183cc4f48f3412fb8b8911f9832e14 Mon Sep 17 00:00:00 2001 From: Chris Blume Date: Tue, 3 Feb 2026 01:17:49 -0500 Subject: [PATCH 2/5] Remove excess 'typename' --- Code/max/Containers/SlotMap.hpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Code/max/Containers/SlotMap.hpp b/Code/max/Containers/SlotMap.hpp index 908ed3e..fffff38 100644 --- a/Code/max/Containers/SlotMap.hpp +++ b/Code/max/Containers/SlotMap.hpp @@ -49,8 +49,8 @@ namespace Containers template HandleType emplace_back(Args&&... args) noexcept; - typename reference operator[](HandleType handle) noexcept; - typename const_reference operator[](HandleType handle) const noexcept; + reference operator[](HandleType handle) noexcept; + const_reference operator[](HandleType handle) const noexcept; void remove(HandleType handle) noexcept; void pop_back() noexcept; @@ -58,22 +58,22 @@ namespace Containers // Required to satisfy the C++ "Container" requirements - typename iterator begin() noexcept; - typename const_iterator begin() const noexcept; - typename const_iterator cbegin() const noexcept; - typename iterator end() noexcept; - typename const_iterator end() const noexcept; - typename const_iterator cend() noexcept; + iterator begin() noexcept; + const_iterator begin() const noexcept; + const_iterator cbegin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + const_iterator cend() noexcept; - typename size_type size() const noexcept; - constexpr typename size_type max_size() const noexcept; + size_type size() const noexcept; + constexpr size_type max_size() const noexcept; private: // The indices are stable and act as handles. - typename BackingType indices_; + BackingType indices_; // If we wanted to prevent a user accidently reusing a handle from an object they removed, // |indices_| could be a tuple of HandleType and generation counter. // Increment the generation every time an element is removed and compare against it when accessing. @@ -82,8 +82,8 @@ namespace Containers // |data_| and |reverse_indices_| are parallel. The nth element in one corresponds to the nth element in the other. // This means once we have an element's index into |data_|, we can use that same index into |reverse_indices_| to find // the element in |indices_| that points here. - typename BackingType data_; - typename BackingType reverse_indices_; + BackingType data_; + BackingType reverse_indices_; void remove_by_index(size_t index) noexcept; From dadf7316a994ea2da10406d6f356ab74bdf11ce6 Mon Sep 17 00:00:00 2001 From: Chris Blume Date: Tue, 3 Feb 2026 01:30:41 -0500 Subject: [PATCH 3/5] Add template to friend functions --- Code/max/Containers/SlotMap.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Code/max/Containers/SlotMap.hpp b/Code/max/Containers/SlotMap.hpp index fffff38..422fbbb 100644 --- a/Code/max/Containers/SlotMap.hpp +++ b/Code/max/Containers/SlotMap.hpp @@ -87,8 +87,11 @@ namespace Containers void remove_by_index(size_t index) noexcept; + template<> friend bool operator ==(const SlotMap& lhs, const SlotMap& rhs) noexcept; + template<> friend bool operator !=(const SlotMap& lhs, const SlotMap& rhs) noexcept; + template<> friend void swap(SlotMap& lhs, SlotMap& rhs) noexcept; }; From bc210b7bec01628a859efbf8462cfeb453f42c27 Mon Sep 17 00:00:00 2001 From: Chris Blume Date: Tue, 3 Feb 2026 01:36:14 -0500 Subject: [PATCH 4/5] Specifying friend template parameters without shadowing --- Code/max/Containers/SlotMap.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Code/max/Containers/SlotMap.hpp b/Code/max/Containers/SlotMap.hpp index 422fbbb..4232f4f 100644 --- a/Code/max/Containers/SlotMap.hpp +++ b/Code/max/Containers/SlotMap.hpp @@ -87,12 +87,12 @@ namespace Containers void remove_by_index(size_t index) noexcept; - template<> - friend bool operator ==(const SlotMap& lhs, const SlotMap& rhs) noexcept; - template<> - friend bool operator !=(const SlotMap& lhs, const SlotMap& rhs) noexcept; - template<> - friend void swap(SlotMap& lhs, SlotMap& rhs) noexcept; + template typename BackingType2> + friend bool operator ==(const SlotMap& lhs, const SlotMap& rhs) noexcept; + template typename BackingType2> + friend bool operator !=(const SlotMap& lhs, const SlotMap& rhs) noexcept; + template typename BackingType2> + friend void swap(SlotMap& lhs, SlotMap& rhs) noexcept; }; From dde7da68e9f9d3a23ae03220916208defec619d1 Mon Sep 17 00:00:00 2001 From: Chris Blume Date: Tue, 3 Feb 2026 01:44:34 -0500 Subject: [PATCH 5/5] Add allocator template param --- Code/max/Containers/SlotMap.hpp | 9 ++++--- Code/max/Containers/SlotMap.inl | 36 +++++++++++++-------------- Projects/VisualStudio/max/max.vcxproj | 14 +++++++++-- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/Code/max/Containers/SlotMap.hpp b/Code/max/Containers/SlotMap.hpp index 4232f4f..f9d929c 100644 --- a/Code/max/Containers/SlotMap.hpp +++ b/Code/max/Containers/SlotMap.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -27,7 +28,7 @@ namespace Containers // Access: O(1) // Removal: O(1) // *Unless the BackingType requires reallocation. Then it is O(1) amortized. - template typename BackingType = std::vector> + template> typename BackingType = std::vector> class SlotMap { public: @@ -87,11 +88,11 @@ namespace Containers void remove_by_index(size_t index) noexcept; - template typename BackingType2> + template typename BackingType2> friend bool operator ==(const SlotMap& lhs, const SlotMap& rhs) noexcept; - template typename BackingType2> + template typename BackingType2> friend bool operator !=(const SlotMap& lhs, const SlotMap& rhs) noexcept; - template typename BackingType2> + template typename BackingType2> friend void swap(SlotMap& lhs, SlotMap& rhs) noexcept; }; diff --git a/Code/max/Containers/SlotMap.inl b/Code/max/Containers/SlotMap.inl index d884b9d..d7ac083 100644 --- a/Code/max/Containers/SlotMap.inl +++ b/Code/max/Containers/SlotMap.inl @@ -14,7 +14,7 @@ namespace v0 namespace Containers { - template typename BackingType> + template typename BackingType> HandleType SlotMap::push_back(T element) noexcept { const auto data_size = data_.size(); data_.push_back(std::move(element)); @@ -36,7 +36,7 @@ namespace Containers } } - template typename BackingType> + template typename BackingType> template HandleType SlotMap::emplace_back(Args&&... args) noexcept { // TODO: constructing the element then moving it to push_back() really defeats the purpose of an emplace_back. @@ -44,7 +44,7 @@ namespace Containers return push_back(T{ std::forward(args)... }); } - template typename BackingType> + template typename BackingType> typename SlotMap::reference SlotMap::operator[](HandleType handle) noexcept { // Assumes |handle| is within range. @@ -52,7 +52,7 @@ namespace Containers return data_[index]; } - template typename BackingType> + template typename BackingType> typename SlotMap::const_reference SlotMap::operator[](HandleType handle) const noexcept { // Assumes |handle| is within range. @@ -60,12 +60,12 @@ namespace Containers return data_[index]; } - template typename BackingType> + template typename BackingType> void SlotMap::remove(HandleType handle) noexcept { remove_by_index(indices_[handle]); } - template typename BackingType> + template typename BackingType> void SlotMap::pop_back() noexcept { remove_by_index(data_.size() - 1); } @@ -73,47 +73,47 @@ namespace Containers // Required to satisfy the C++ "Container" requirements - template typename BackingType> + template typename BackingType> typename SlotMap::iterator SlotMap::begin() noexcept { return data_.begin(); } - template typename BackingType> + template typename BackingType> typename SlotMap::const_iterator SlotMap::begin() const noexcept { return data_.begin(); } - template typename BackingType> + template typename BackingType> typename SlotMap::const_iterator SlotMap::cbegin() const noexcept { return data_.begin(); } - template typename BackingType> + template typename BackingType> typename SlotMap::iterator SlotMap::end() noexcept { return data_.end(); } - template typename BackingType> + template typename BackingType> typename SlotMap::const_iterator SlotMap::end() const noexcept { return data_.end(); } - template typename BackingType> + template typename BackingType> typename SlotMap::const_iterator SlotMap::cend() noexcept { return data_.end(); } - template typename BackingType> + template typename BackingType> typename SlotMap::size_type SlotMap::size() const noexcept { return data_.size(); } - template typename BackingType> + template typename BackingType> constexpr typename SlotMap::size_type SlotMap::max_size() const noexcept { return data_.max_size(); } - template typename BackingType> + template typename BackingType> void SlotMap::remove_by_index(size_t index) noexcept { // Removing an element in the middle of a vector will cause all elements after it to shift over. // Removing the final element does not have this effect. @@ -143,19 +143,19 @@ namespace Containers indices_[reverse_index] = index; } - template typename BackingType> + template typename BackingType> bool operator ==(const SlotMap& lhs, const SlotMap& rhs) noexcept { return lhs.indices_ == rhs.indices_ && lhs.data_ == rhs.data_ && lhs.reverse_indices_ == rhs.reverse_indices_; } - template typename BackingType> + template typename BackingType> bool operator !=(const SlotMap& lhs, const SlotMap& rhs) noexcept { return !(lhs == rhs); } - template typename BackingType> + template typename BackingType> void swap(SlotMap& lhs, SlotMap& rhs) noexcept { using std::swap; diff --git a/Projects/VisualStudio/max/max.vcxproj b/Projects/VisualStudio/max/max.vcxproj index a46eec0..7a9decf 100644 --- a/Projects/VisualStudio/max/max.vcxproj +++ b/Projects/VisualStudio/max/max.vcxproj @@ -149,7 +149,12 @@ true - + + true + true + true + true + true @@ -321,7 +326,12 @@ true true - + + true + true + true + true + true true