├── .gitignore ├── Articles ├── EnumMacros.md ├── EraserProfile.md ├── EraserSafeAppend.md ├── iter_explain.png ├── iter_int100.png ├── iter_int1000.png ├── iter_listcmp100.png ├── iter_string100.png └── iter_string1000.png ├── EnumMacros.h ├── Iterator.h ├── IteratorExt.h ├── LICENSE.txt ├── Profiler.h ├── README.md └── Tests ├── CppTemplates.cpp ├── CppTemplates.sln ├── CppTemplates.vcxproj ├── CppTemplates.vcxproj.filters ├── CppTemplates2022.sln ├── CppTemplates2022.vcxproj ├── CppTemplates2022.vcxproj.filters ├── EnumMacro_Base.cpp ├── EnumMacro_Base.h ├── EnumMacro_UnitTests.cpp ├── Iterator_Profile.cpp ├── Iterator_UnitTests.cpp └── Iterator_old.h /.gitignore: -------------------------------------------------------------------------------- 1 | Tests/x64/Debug/ 2 | Tests/x64/ 3 | Tests/.vs/ 4 | Tests/iter_profile.csv 5 | Tests/CppTemplates2022/ 6 | *.json 7 | *.user 8 | -------------------------------------------------------------------------------- /Articles/EnumMacros.md: -------------------------------------------------------------------------------- 1 | # Enum Macros 2 | This article describes why and how to use the provided enum macros. 3 | 4 | 5 | ## Introduction 6 | 7 | Typically when using enums you also want a string representation which involes maintaining a seperate list: 8 | 9 | ```c++ 10 | enum class MyEnum 11 | { 12 | Value1, 13 | Value2, 14 | Value3 15 | }; 16 | 17 | const char * s_myEnumStr[] 18 | { 19 | "Value1", 20 | "Value2", 21 | "Value3" 22 | }; 23 | ``` 24 | 25 | 26 | This is obviously error prone if the enum ever changes. One way of helping keep in sync is to add a static_assert(): 27 | 28 | ```c++ 29 | enum class MyEnum 30 | { 31 | Value1, 32 | Value2, 33 | Value3, 34 | COUNT 35 | }; 36 | 37 | const char * s_myEnumStr[] 38 | { 39 | "Value1", 40 | "Value2", 41 | "Value3" 42 | }; 43 | static_assert(MyEnum::COUNT == (sizeof(s_myEnumStr)/ sizeof(s_myEnumStr[0])), "Update lookup table"); 44 | ``` 45 | 46 | But this is still error prone if enums change naming or ordering. This will also not work well if the enums are assigned non-sequential values. 47 | 48 | 49 | ## VALUE_ENUM() and SEQUENTIAL_ENUM() Macros 50 | 51 | 52 | Usage - First define the enum values with a define: 53 | ```c++ 54 | #define MyEnum_EnumValues(EV) \ 55 | EV(Value1) \ 56 | EV(Value2) \ 57 | EV(value3) 58 | ``` 59 | 60 | Values can also be assigned to each enum: 61 | ```c++ 62 | #define MyEnum_EnumValues(EV) \ 63 | EV(Value1) \ 64 | EV(Value2, 1 << 8) \ 65 | EV(value3, Value2 | Value1) 66 | ``` 67 | 68 | Then in a header file use the define like: 69 | 70 | ```c++ 71 | VALUE_ENUM(MyEnum, uint32_t) // second parameter is the base type of the enum 72 | ``` 73 | 74 | This expands to: 75 | 76 | ```c++ 77 | enum class MyEnum : uint32_t { ...Enum values... }; 78 | ``` 79 | 80 | If the enum is purely sequential, some optimizations can be made by using; 81 | 82 | ```c++ 83 | SEQUENTIAL_ENUM(MyEnum, uint32_t); 84 | ``` 85 | 86 | In a .cpp file call: 87 | 88 | ```c++ 89 | VALUE_ENUM_BODY(MyEnum) // For value enum 90 | SEQUENTIAL_ENUM_BODY(MyEnum) // For sequential enum 91 | 92 | // Use the below if the enum is in a class/namespace 93 | VALUE_ENUM_BODYSCOPE(MyEnum, ClassName) 94 | SEQUENTIAL_ENUM_BODYSCOPE(MyEnum, ClassName) 95 | ``` 96 | 97 | Once defined, a **MyEnum_Values** type is available. 98 | This can be used in code to: 99 | 100 | - Convert values to strings: **MyEnum_Values::to_string(Value1)** 101 | - Get the count of enum values **MyEnum_Values::COUNT** 102 | - Iterate over the values/strings: 103 | ```c++ 104 | for (auto val : MyEnum_Values()) { 105 | val.c_str() // Get string of enum 106 | val.value() // Get enum value 107 | ``` 108 | 109 | Using this code you can easily write code to convert from a string to an enum. 110 | (Not provided as every code base has different error handling and string requirements) 111 | 112 | 113 | Some other solutons to this enum issue can be found at: 114 | - Better Enums (https://github.com/aantron/better-enums) 115 | - C++ proposial N4428 (possibly for C++20?) 116 | 117 | 118 | ## ENUM_FLAG_OPS() Macro 119 | 120 | A macro is also provided to generate the bitwise operations generally needed if the enum is a flag type. 121 | This can be a plain enum - does not need to be defined by VALUE_ENUM(). 122 | 123 | ```c++ 124 | // Define this below an enum 125 | ENUM_FLAG_OPS(MyEnum) 126 | 127 | // Allows the below bit operations: 128 | MyEnum bitOp1 = MyEnum::Value1 | MyEnum::Value2; 129 | MyEnum bitOp2 = MyEnum::Value1 & MyEnum::Value2; 130 | MyEnum bitOp3 = MyEnum::Value1 ^ MyEnum::Value2; 131 | MyEnum bitOp4 = ~MyEnum::Value1; 132 | ``` 133 | 134 | If the enum is inside a class, use the macro outside the class definition 135 | ```c++ 136 | class ClassName 137 | { 138 | public: 139 | enum MyEnum { ...} 140 | }; 141 | 142 | ENUM_FLAG_OPS(ClassName::MyEnum) 143 | ``` 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /Articles/EraserProfile.md: -------------------------------------------------------------------------------- 1 | # Eraser Profile 2 | This article describes the performance differences of the eraser() and unordered_eraser() helper iterators. 3 | 4 | 5 | ## Introduction 6 | When iterating in modern C++, you can use the range for syntax: 7 | ```c++ 8 | for(auto& item : array) 9 | ``` 10 | 11 | But when you want to delete elements in the array while iterating, you either have to revert to the old fashioned index method: 12 | 13 | ```c++ 14 | for(int i = 0; i < array.size(); ) 15 | { 16 | if(array[i] == delete) 17 | { 18 | array.erase(array.begin() + i); 19 | } 20 | else 21 | { 22 | i++;// Increment i++ if not deleting 23 | } 24 | } 25 | ``` 26 | or use iterators 27 | ```c++ 28 | for(auto it = array.begin(); it != array.end();) 29 | { 30 | if(*it == delete) 31 | { 32 | it = array.erase(it); 33 | } 34 | else 35 | { 36 | it++; 37 | } 38 | } 39 | ``` 40 | 41 | However, these have performance issues if deleting more than one element as the container elements may be shuffled multiple times. 42 | A slightly more modern approach is to use ```std::remove_if()``` with a lambda 43 | ```c++ 44 | array.erase(std::remove_if(array.begin(), array.end(), 45 | [] (auto& item) 46 | { 47 | bool deleting = condition; 48 | return deleting; 49 | } 50 | ), array.end()); 51 | ``` 52 | 53 | But this is a bit ugly to type, and may be error prone in ensuring the correct container is always used. 54 | 55 | Another way is to copy out the array data you want to keep into a temporary working buffer. 56 | ```c++ 57 | size_t size = 0; 58 | copyData.resize(array.size()); // Existing temp buffer 59 | for (auto& v : array) 60 | { 61 | if (v != delete) 62 | { 63 | copyData[size] = std::move(v); 64 | size++; 65 | } 66 | } 67 | copyData.resize(size); 68 | array.swap(copyData); 69 | copyData.resize(0); 70 | ``` 71 | 72 | But this can be quite error prone. 73 | 74 | ## eraser() and unordered_eraser() 75 | 76 | Presented is a safe and performant way of removing elements while iterating on them. 77 | Inspired by [Jonathan Blow's language feature on iterator removal](https://youtu.be/-UPFH0eWHEI?list=PLmV5I2fxaiCKfxMBrNsU1kgKJXD3PkyxO&t=2017) : 78 | ```c++ 79 | for(auto& item : iter::eraser(array)) 80 | { 81 | if(*item == value) // Access item via deref 82 | { 83 | item.mark_for_erase(); // Item is marked for deletion, but is still valid until end of loop iteration 84 | item.index(); // Get the origional index of the item in the array 85 | } 86 | } 87 | ``` 88 | If preserving order is not important: 89 | ```c++ 90 | for(auto& item : iter::unordered_eraser(array)) 91 | { 92 | if(*item == value) // Access item via deref 93 | { 94 | item.mark_for_erase(); // Item is marked for deletion, but is still valid until end of loop iteration 95 | } 96 | } 97 | 98 | ``` 99 | 100 | 101 | ## Performance 102 | 103 | Below is presented performance graphs of the different ways of removing elements from an array as presented in the introduction. 104 | Source code for the tests can be found in Iterator_Profile.cpp. 105 | 106 | All timings were done with VisualStudio 2017 RC (x64) on a Intel i7-6700 3.4GHz, 16GB ram @ 2133MHz, Windows 10. 107 | 108 | The tests were done using std::vector data structure as linear data structures are most often used in performance code. 109 | 110 | Note that the graphs have been cropped, as the performance of the index and iterator method was so bad for multiple removals. 111 | 112 | ![alt text](iter_explain.png "Uncropped data") 113 | 114 | Note that while eraser() and unordered_eraser() are generic, template specializations for could be written to optimize for other container types. 115 | 116 | ### std::vector < int > 117 | 118 | ![alt text](iter_int100.png "std::vector (100)") 119 | ![alt text](iter_int1000.png "std::vector (1000)") 120 | 121 | 122 | ### std::vector < std ::string > 123 | 124 | ![alt text](iter_string100.png "std::vector (100)") 125 | ![alt text](iter_string1000.png "std::vector (1000)") 126 | 127 | 128 | ## Compare with fast remove containers 129 | It may be said that if fast removal is required, to use a container type that supports this. 130 | The removal via the iterator method should perform well with such container types. 131 | 132 | ![alt text](iter_listcmp100.png "Compare std::vector to std::list") 133 | 134 | As the above graph shows, using std ::vector with eraser/unordered_eraser is typically much faster that using a std ::list with iterator removal. 135 | 136 | ## Conclusion 137 | 138 | As the above graphs show, eraser() and unordered_eraser() is faster than manual element removal - either by index or iterator. 139 | Where elements are removed, eraser() and unordered_eraser() are faster or comparable to std::remove_if(). 140 | 141 | Deciding between eraser() and unordered_eraser() : 142 | 143 | - If data needs to retain order - **eraser()** 144 | - If data is a basic type (eg int,pointer) - **eraser()** 145 | - If data is a complex type (expensive move/copy) and remove >50% of elements - **eraser()** 146 | - If data is a complex type (expensive move/copy) and remove <50% of elements- **unordered_eraser()** 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /Articles/EraserSafeAppend.md: -------------------------------------------------------------------------------- 1 | # Eraser Safe Append 2 | This article describes the reasons behind eraser_safe_append() and how it should be used. 3 | 4 | 5 | ## Introduction 6 | 7 | Say that we are writing a ParticleManager class in a game that controls a list of particle effects. 8 | 9 | ```c++ 10 | class ParticleManager 11 | { 12 | public: 13 | void Add(PFX newPFX){ m_particles._push_back(newPFX); } 14 | void Update(); 15 | private: 16 | std::vector m_particles; 17 | }; 18 | ``` 19 | 20 | 21 | In the update of the manager you typically want to destroy any pfx that are completed - here we can use the iterator [eraser()](./EraserProfile.md). 22 | 23 | 24 | ```c++ 25 | void Particlemanager::Update() 26 | { 27 | // Iterate all particles and remove ones that are done. 28 | for(auto& item : iter::eraser(m_particles)) 29 | { 30 | if(!item->Update()) 31 | { 32 | item.mark_for_erase(); 33 | } 34 | } 35 | } 36 | ``` 37 | 38 | However, as happens in many manager classes, the array being iterated on may be added to while iterating. For our example, a new particle type is added that is a firework that spawns other particles on death. 39 | 40 | ```c++ 41 | bool FireworkPFX::Update() 42 | { 43 | // If complete - spawn some new pfx 44 | if(m_bComplete) 45 | { 46 | m_manager->Add(sparklesPFX); 47 | } 48 | return !m_bComplete; 49 | } 50 | ``` 51 | 52 | This will cause issues as the array is being iterated on in the update method. A typical solution to this issue is to have a local copy of the array under iteration. 53 | 54 | ```c++ 55 | void Particlemanager::Update() 56 | { 57 | // Save a copy of the array under iteration - to allow additions during iteration 58 | auto localArray = m_particles; 59 | m_particles.clear(); 60 | for(auto& item : iter::eraser(localArray)) 61 | { 62 | if(!item->Update()) 63 | { 64 | item.mark_for_erase(); 65 | } 66 | } 67 | 68 | // Append the two resulting arrays together 69 | m_particles.insert(m_particles.end(), localArray.begin(), localArray.end()); 70 | } 71 | ``` 72 | 73 | 74 | ## eraser_safe_append() 75 | 76 | Presented is a safe way of appending to an array under iteration. 77 | 78 | ```c++ 79 | void Particlemanager::Update() 80 | { 81 | // Update pfx allowing appends to the array under iteration 82 | for(auto& item : iter::eraser_safe_append(m_particles)) 83 | { 84 | if(!item->Update()) 85 | { 86 | item.mark_for_erase(); 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | Note that it is safe to take references to the current iteration item and it will still be valid even if the array resizes 93 | 94 | 95 | ```c++ 96 | void Particlemanager::Update() 97 | { 98 | // Update pfx allowing appends to the array under iteration 99 | for(auto& item : iter::eraser_safe_append(m_particles)) 100 | { 101 | auto& data = *item; // Can safely store reference to item under iteration 102 | m_particles.push_back(newPFX); // Can append new data to vector (even if vector resizes) 103 | 104 | if(!data.Update()) 105 | { 106 | item.mark_for_erase(); 107 | } 108 | } 109 | } 110 | ``` 111 | 112 | 113 | ## Caution 114 | 115 | The reason this iterator type is in the "IteratorExt.h" extension file is due to some strict rules when using this iterator and updating the data. 116 | 117 | * *Do not* attempt to access any elements of the array being iterated on manually while iteration is occurring. 118 | eg. If appending, do not attempt to insert uniquely by finding a duplicate from the current array - an element may be marked for deletion or be temporally removed from the array. 119 | 120 | 121 | * *Do not* attempt to insert into the middle of the array during iteration - only appends are allowed. For this reason the array should be guarded by accessor functions. 122 | -------------------------------------------------------------------------------- /Articles/iter_explain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dtrebilco/Taren/98ee18b001309692fd75ee99017adaffa5875893/Articles/iter_explain.png -------------------------------------------------------------------------------- /Articles/iter_int100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dtrebilco/Taren/98ee18b001309692fd75ee99017adaffa5875893/Articles/iter_int100.png -------------------------------------------------------------------------------- /Articles/iter_int1000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dtrebilco/Taren/98ee18b001309692fd75ee99017adaffa5875893/Articles/iter_int1000.png -------------------------------------------------------------------------------- /Articles/iter_listcmp100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dtrebilco/Taren/98ee18b001309692fd75ee99017adaffa5875893/Articles/iter_listcmp100.png -------------------------------------------------------------------------------- /Articles/iter_string100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dtrebilco/Taren/98ee18b001309692fd75ee99017adaffa5875893/Articles/iter_string100.png -------------------------------------------------------------------------------- /Articles/iter_string1000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dtrebilco/Taren/98ee18b001309692fd75ee99017adaffa5875893/Articles/iter_string1000.png -------------------------------------------------------------------------------- /EnumMacros.h: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Copyright (C) 2016 Damian Trebilco 3 | // Licensed under the MIT license - See LICENSE.txt for details. 4 | //============================================================================= 5 | #ifndef __TAREN_ENUM_MACROS_H__ 6 | #define __TAREN_ENUM_MACROS_H__ 7 | 8 | #include 9 | #include 10 | 11 | /// \brief This header contains helper enum macros to all iterating over enum values and the string representation of them. 12 | /// These enums provide similar functionality to Better Enums (https://github.com/aantron/better-enums) or C++ proposial N4428. 13 | /// Usage - First define the enum values with a define: 14 | /// #define MyEnum_EnumValues(EV) \ 15 | /// EV(Value1) \ 16 | /// EV(Value2) \ 17 | /// EV(value3) 18 | /// 19 | /// Values can also be assigned to each enum: 20 | /// #define MyEnumVal_EnumValues(EV) \ 21 | /// EV(Value1) \ 22 | /// EV(Value2, 1 << 8) \ 23 | /// EV(value3, Value2 | Value1) 24 | /// 25 | /// Then in a header file use the define like: 26 | /// VALUE_ENUM(MyEnum, uint32_t) // second parameter is the base type of the enum 27 | /// This expands to: 28 | /// enum class MyEnum : uint32_t { ...Enum values... }; 29 | /// 30 | /// If the enum is purely sequential, some optimizations can be made by using; 31 | /// SEQUENTIAL_ENUM(MyEnum, uint32_t); 32 | /// 33 | /// In a .cpp file a call to VALUE_ENUM_BODY(MyEnum) or SEQUENTIAL_ENUM_BODY(MyEnum) should be made. 34 | /// If the enum is defined in a class or a namespace, use VALUE_ENUM_BODYSCOPE(MyEnum, ClassName) or SEQUENTIAL_ENUM_BODYSCOPE(MyEnum, ClassName) 35 | /// 36 | /// Once defined, a MyEnumVal_Values type is available. 37 | /// This can be used in code to: 38 | /// - Convert values to strings: MyEnumVal_Values::to_string(Value1) 39 | /// - Get the count of enum values MyEnumVal_Values::COUNT 40 | /// - Iterate over the values/strings: 41 | /// for (auto val : MyEnumVal_Values()) { 42 | /// val.c_str() // Get string of enum 43 | /// val.value() // Get enum value 44 | /// Using this code you can easily write code to convert from a string to an enum. 45 | /// (Not provided as every code base has different error handling and string requirements) 46 | /// 47 | /// Finally, a macro is provided ENUM_FLAG_OPS(MyEnum) to generate the bitwise operations generally needed if the enum is a flag type. 48 | /// (Can be a plain enum, does not need to be defined by VALUE_ENUM) 49 | /// Eg. MyEnum bitOp1 = MyEnum::Bar | MyEnum::Baz; 50 | /// MyEnum bitOp2 = MyEnum::Bar & MyEnum::Baz; 51 | /// MyEnum bitOp3 = MyEnum::Bar ^ MyEnum::Baz; 52 | /// MyEnum bitOp4 = ~MyEnum::Bar; 53 | /// 54 | 55 | /// \brief Helper template for enums that are sequential 56 | template 57 | class SequentialEnum 58 | { 59 | public: 60 | 61 | using ValueType = typename std::underlying_type::type; 62 | 63 | static const size_t COUNT; //!< The count of how many enum values there are 64 | static const char * const s_strValues[]; //!< The string values of each enum 65 | 66 | struct Value 67 | { 68 | public: 69 | 70 | inline T value() { return static_cast(m_item); } 71 | inline const char* c_str() { return s_strValues[m_item]; } 72 | 73 | protected: 74 | ValueType m_item = 0; //!< The current iterator item type 75 | }; 76 | 77 | struct Iterator : public Value 78 | { 79 | inline Iterator& operator++() { this->m_item++; return *this; } 80 | template 81 | inline bool operator != (const V&) const { return this->m_item != COUNT; } 82 | inline Value& operator *() { return *this; } 83 | }; 84 | 85 | static const char* to_string(T a_type) 86 | { 87 | ValueType value = static_cast(a_type); 88 | if (value < COUNT) 89 | { 90 | return s_strValues[value]; 91 | } 92 | return ""; 93 | } 94 | 95 | inline Iterator begin() { return Iterator(); } 96 | inline Iterator end() { return Iterator(); } 97 | }; 98 | 99 | /// \brief Helper template for enums that have assigned values 100 | template 101 | class ValueEnum 102 | { 103 | public: 104 | 105 | using EnumType = T; 106 | using ValueType = typename std::underlying_type::type; 107 | 108 | static const size_t COUNT; //!< The count of how many enum values there are 109 | static const T s_values[]; //!< The array of enum values 110 | static const char * const s_strValues[]; //!< The string values of each enum 111 | 112 | struct Value 113 | { 114 | public: 115 | 116 | inline T value() { return s_values[m_index]; } 117 | inline const char* c_str() { return s_strValues[m_index]; } 118 | 119 | protected: 120 | size_t m_index = 0; //!< The current iterator index 121 | }; 122 | 123 | struct Iterator : public Value 124 | { 125 | inline Iterator& operator++() { this->m_index++; return *this; } 126 | template 127 | inline bool operator != (const V&) const { return this->m_index != COUNT; } 128 | inline Value& operator *() { return *this; } 129 | }; 130 | 131 | static const char* to_string(T a_type) 132 | { 133 | for (size_t i = 0; i < COUNT; i++) 134 | { 135 | if (a_type == s_values[i]) 136 | { 137 | return s_strValues[i]; 138 | } 139 | } 140 | return ""; 141 | } 142 | 143 | inline Iterator begin() { return Iterator(); } 144 | inline Iterator end() { return Iterator(); } 145 | }; 146 | 147 | // Check that the enum values do not have any equals signs in them 148 | constexpr bool TAREN_ENUM_BAD_STR_VALUE(const char *a_str) 149 | { 150 | return (*a_str == 0) ? false : 151 | (*a_str == '=') ? true : 152 | TAREN_ENUM_BAD_STR_VALUE(a_str + 1); 153 | } 154 | 155 | // Sequential enum definition 156 | #define TAREN_MAKE_ENUM(VAR) VAR, 157 | #define TAREN_MAKE_STRINGS(VAR) #VAR, 158 | #define TAREN_MAKE_ENUM_CHECK(VAR, ...) #VAR 159 | 160 | #define SEQUENTIAL_ENUM(NAME, TYPE) \ 161 | enum class NAME : TYPE{ \ 162 | NAME##_EnumValues(TAREN_MAKE_ENUM) \ 163 | }; \ 164 | static_assert(!TAREN_ENUM_BAD_STR_VALUE(NAME##_EnumValues(TAREN_MAKE_ENUM_CHECK)), "Bad sequential enum value"); \ 165 | using NAME##_Values = SequentialEnum; \ 166 | 167 | #define TAREN_SEQUENTIAL_ENUM_INTERNAL(NAME, VALUES) \ 168 | template<> const char* const SequentialEnum::s_strValues[] = { \ 169 | VALUES(TAREN_MAKE_STRINGS) \ 170 | }; \ 171 | template<> const size_t SequentialEnum::COUNT = sizeof(SequentialEnum::s_strValues) / sizeof(SequentialEnum::s_strValues[0]);\ 172 | 173 | #define SEQUENTIAL_ENUM_BODY(NAME) TAREN_SEQUENTIAL_ENUM_INTERNAL(NAME, NAME##_EnumValues) 174 | #define SEQUENTIAL_ENUM_BODYSCOPE(NAME, SCOPE) TAREN_SEQUENTIAL_ENUM_INTERNAL(SCOPE::NAME, NAME##_EnumValues) 175 | 176 | // Value assigned enum definition 177 | #define TAREN_MAKE_ENUM_MACRO_VAL(X) X 178 | #define TAREN_MAKE_ENUM_FUNC_APPLY(FUNC, ...) TAREN_MAKE_ENUM_MACRO_VAL(FUNC(__VA_ARGS__)) 179 | 180 | #define TAREN_MAKE_ENUM_ARG_COUNT_IMPL(_1, _2, COUNT, ...) COUNT 181 | #define TAREN_MAKE_ENUM_ARG_COUNTER(...) TAREN_MAKE_ENUM_MACRO_VAL(TAREN_MAKE_ENUM_ARG_COUNT_IMPL(__VA_ARGS__, 2, 1)) 182 | 183 | #define TAREN_MAKE_ENUM_VALUE1(VAR) VAR, 184 | #define TAREN_MAKE_ENUM_VALUE2(VAR,VAL) VAR = (VAL), 185 | #define TAREN_MAKE_ENUM_VALUE_COUNT(COUNT) TAREN_MAKE_ENUM_VALUE##COUNT 186 | 187 | #define TAREN_MAKE_ENUM_VALUE(...) TAREN_MAKE_ENUM_MACRO_VAL(TAREN_MAKE_ENUM_FUNC_APPLY(TAREN_MAKE_ENUM_VALUE_COUNT, TAREN_MAKE_ENUM_ARG_COUNTER(__VA_ARGS__)) ( __VA_ARGS__)) 188 | #define TAREN_MAKE_ENUM_SET_VALUE(VAR,...) EnumType::VAR, 189 | #define TAREN_MAKE_STRINGS_VALUE(VAR,...) #VAR, 190 | 191 | #define VALUE_ENUM(NAME, TYPE) \ 192 | enum class NAME : TYPE{ \ 193 | NAME##_EnumValues(TAREN_MAKE_ENUM_VALUE) \ 194 | }; \ 195 | using NAME##_Values = ValueEnum; \ 196 | static_assert(!TAREN_ENUM_BAD_STR_VALUE(NAME##_EnumValues(TAREN_MAKE_ENUM_CHECK)), "Bad enum value"); 197 | 198 | #define TAREN_VALUE_ENUM_BODY_INTERNAL(NAME, VALUES) \ 199 | template<> const NAME ValueEnum::s_values[] = { \ 200 | VALUES(TAREN_MAKE_ENUM_SET_VALUE) \ 201 | }; \ 202 | template<> const char* const ValueEnum::s_strValues[] = { \ 203 | VALUES(TAREN_MAKE_STRINGS_VALUE) \ 204 | }; \ 205 | template<> const size_t ValueEnum::COUNT = sizeof(ValueEnum::s_strValues) / sizeof(ValueEnum::s_strValues[0]); \ 206 | 207 | #define VALUE_ENUM_BODY(NAME) TAREN_VALUE_ENUM_BODY_INTERNAL(NAME, NAME##_EnumValues) 208 | #define VALUE_ENUM_BODYSCOPE(NAME, SCOPE) TAREN_VALUE_ENUM_BODY_INTERNAL(SCOPE::NAME, NAME##_EnumValues) 209 | 210 | // Bit wise operations for enums that are flag values 211 | #define ENUM_FLAG_OPS(NAME) \ 212 | static_assert(!std::numeric_limits::type>::is_signed, "Unsigned enums only"); \ 213 | constexpr inline NAME operator|(NAME a_lhs, NAME a_rhs) \ 214 | { \ 215 | return NAME(std::underlying_type::type(a_lhs) | \ 216 | std::underlying_type::type(a_rhs)); \ 217 | } \ 218 | \ 219 | constexpr inline NAME operator&(NAME a_lhs, NAME a_rhs) \ 220 | { \ 221 | return NAME(std::underlying_type::type(a_lhs) & \ 222 | std::underlying_type::type(a_rhs)); \ 223 | } \ 224 | \ 225 | constexpr inline NAME operator^(NAME a_lhs, NAME a_rhs) \ 226 | { \ 227 | return NAME(std::underlying_type::type(a_lhs) ^ \ 228 | std::underlying_type::type(a_rhs)); \ 229 | } \ 230 | \ 231 | constexpr inline NAME operator~(NAME a_lhs) \ 232 | { \ 233 | return NAME(~std::underlying_type::type(a_lhs)); \ 234 | } 235 | 236 | 237 | #endif // !__TAREN_ENUM_MACROS_H__ 238 | 239 | -------------------------------------------------------------------------------- /Iterator.h: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Copyright (C) 2016 Damian Trebilco 3 | // Licensed under the MIT license - See LICENSE.txt for details. 4 | //============================================================================= 5 | #ifndef __TAREN_ITERATOR_H__ 6 | #define __TAREN_ITERATOR_H__ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | // Defines to determine if to use extended C++17 range based for loops 13 | #if defined(_MSC_VER) && (_MSC_VER >= 1910) 14 | #define ITER_USE_CPP17 15 | #endif 16 | 17 | #if defined(__cpp_range_based_for) && (__cpp_range_based_for >= 201603) 18 | #define ITER_USE_CPP17 19 | #endif 20 | 21 | /// \brief Helper methods/structures when using for-range iteration 22 | namespace iter 23 | { 24 | 25 | template 26 | struct reverse_wrapper 27 | { 28 | const T& m_v; 29 | 30 | explicit reverse_wrapper(const T& a_v) : m_v(a_v) {} 31 | inline auto begin() -> decltype(m_v.rbegin()) { return m_v.rbegin(); } 32 | inline auto end() -> decltype(m_v.rend()) { return m_v.rend(); } 33 | }; 34 | 35 | /// \brief A iterator modifier that will iterate the passed type in reverse 36 | /// Usage: for(auto& value : iter::reverse(array)) 37 | template 38 | reverse_wrapper reverse(const T& v) 39 | { 40 | return reverse_wrapper(v); 41 | } 42 | 43 | /// \brief A iterator that will output the numbers 0 .. (total - 1) 44 | /// Usage: for(auto value : iter::counter(6)) produces -> 0,1,2,3,4,5 45 | /// 46 | /// Also useful for container access when range for cannot be used 47 | /// Usage: for(auto value : iter::counter(array.size())) 48 | struct counter 49 | { 50 | struct Iterator 51 | { 52 | size_t m_pos; 53 | 54 | inline Iterator& operator++() { m_pos++; return *this; } 55 | inline bool operator!=(const Iterator& a_rhs) const { return m_pos != a_rhs.m_pos; } 56 | inline size_t operator *() const { return m_pos; } 57 | }; 58 | 59 | inline explicit counter(size_t a_size) : m_size(a_size) {} 60 | 61 | inline Iterator begin() { return Iterator{ 0 }; } 62 | inline Iterator end() { return Iterator{ m_size }; } 63 | 64 | size_t m_size = 0; 65 | }; 66 | 67 | /// \brief A iterator that will output the numbers (total - 1) .. 0 68 | /// Usage: for(auto value : iter::counter_reverse(6)) produces -> 5,4,3,2,1,0 69 | struct counter_reverse 70 | { 71 | struct Iterator 72 | { 73 | size_t m_pos; 74 | 75 | inline Iterator& operator++() { m_pos--; return *this; } 76 | inline bool operator!=(const Iterator& a_rhs) const { return m_pos != a_rhs.m_pos; } 77 | inline size_t operator *() const { return m_pos; } 78 | }; 79 | 80 | inline explicit counter_reverse(size_t a_size) : m_size(a_size) {} 81 | 82 | inline Iterator begin() { return Iterator{ m_size - 1 }; } 83 | inline Iterator end() { return Iterator{ size_t(0) - 1 }; } 84 | 85 | size_t m_size = 0; 86 | }; 87 | 88 | template 89 | struct eraser_wrapper 90 | { 91 | private: 92 | using IterType = decltype(std::declval().begin()); 93 | using ValueRef = decltype(*std::declval()); 94 | using ValueType = typename std::remove_reference::type; 95 | 96 | struct Value 97 | { 98 | protected: 99 | 100 | IterType m_current; //!< The current iterator position 101 | IterType m_eraseStart; //!< The erase start position 102 | IterType m_end; //!< The end marker 103 | 104 | T* m_data = nullptr; //!< The data structure being processed 105 | 106 | bool m_markRemove = false; //!< If the item pointed to by m_current is to be removed 107 | 108 | inline Value() {} 109 | inline explicit Value(T* a_data) : m_data(a_data) 110 | { 111 | m_current = m_data->begin(); 112 | m_end = m_data->end(); 113 | m_eraseStart = m_current; 114 | } 115 | 116 | public: 117 | 118 | /// \brief Get the value from the container element 119 | inline ValueType& operator *() { return *m_current; } 120 | inline const ValueType& operator *() const { return *m_current; } 121 | 122 | /// \brief Get the value from the container element 123 | inline ValueType* operator ->() { return &*m_current; } 124 | inline const ValueType* operator ->() const { return &*m_current; } 125 | 126 | /// \brief Mark the current item to be erased from the parent container at a later stage. 127 | /// Can be called multiple times and value will still be valid until the next iteration. 128 | inline void mark_for_erase() { m_markRemove = true; } 129 | 130 | /// \brief Get the index of the this value in the parent container. Item may be shifted later if previous elements are erased. 131 | inline size_t index() const { return std::distance(m_data->begin(), m_current); } 132 | }; 133 | 134 | struct Iterator : public Value 135 | { 136 | inline Iterator() {} 137 | inline explicit Iterator(T* a_data) : Value(a_data) {} 138 | 139 | inline Iterator& operator++() 140 | { 141 | if (!this->m_markRemove) 142 | { 143 | // Move the existing value to the new position 144 | if (std::is_trivially_copyable::value || 145 | this->m_eraseStart != this->m_current) 146 | { 147 | *this->m_eraseStart = std::move(*this->m_current); 148 | } 149 | ++this->m_eraseStart; 150 | } 151 | 152 | this->m_markRemove = false; 153 | ++this->m_current; 154 | return *this; 155 | } 156 | 157 | template 158 | inline bool operator != (const V&) const { return this->m_current != this->m_end; } 159 | inline Value& operator *() { return *this; } 160 | 161 | inline ~Iterator() 162 | { 163 | #if !defined(ITER_USE_CPP17) 164 | if (this->m_data == nullptr) 165 | { 166 | return; 167 | } 168 | #endif 169 | 170 | // If aborted mid iteration (via break), still remove if flagged 171 | if (this->m_markRemove) 172 | { 173 | ++this->m_current; 174 | } 175 | 176 | this->m_data->erase(this->m_eraseStart, this->m_current); 177 | } 178 | }; 179 | 180 | T& m_data; //!< The data structure being processed 181 | 182 | public: 183 | inline explicit eraser_wrapper(T& a_data) : m_data(a_data) {} 184 | 185 | inline Iterator begin() { return Iterator(&m_data); } 186 | 187 | #if defined(ITER_USE_CPP17) 188 | inline int end() { return 0; } 189 | #else 190 | inline Iterator end() { return Iterator(); } 191 | #endif 192 | }; 193 | 194 | /// \brief A iterator modifier that allows elements to be erased from the underlying type during iteration. 195 | /// Order of elements in the container type is preserved during deletion. Deletion of elements will occur in order 196 | /// and by the exit of the iteration, but may be delayed. 197 | /// Container type must support an erase() method similar to std::vector::erase() 198 | /// 199 | /// Usage: for(auto& value : iter::eraser(vector)) 200 | /// { 201 | /// if(someCondition == *value) 202 | /// { 203 | /// value.mark_for_erase(); // Item will still be valid for remainer of iteration and will be erased in a future iteration 204 | /// 205 | /// Note that the value returned is a smart pointer that needs to be de-referenced to access the value 206 | /// (either with *value or value-> ) 207 | /// The original index of the item in the array can also be retrieved by calling value.index(). This may not be optimal on some container types. 208 | /// 209 | /// IMPORTANT: Do not store a pointer to any data inside the array being iterated on outside the for-loop scope - data may be moved between each iteration. 210 | /// See iter::unordered_eraser() for a relaxation of this rule 211 | /// Eg. Do not do this: 212 | /// Type* foundItem = nullptr; 213 | /// for(auto& value : iter::eraser(vector)) 214 | /// { 215 | /// ///.. erase value code ... 216 | /// foundItem = &*value; 217 | /// } 218 | /// foundItem->blah; // Data may not be valid 219 | /// 220 | template 221 | eraser_wrapper eraser(T& v) 222 | { 223 | return eraser_wrapper(v); 224 | } 225 | 226 | template 227 | struct unordered_eraser_wrapper 228 | { 229 | private: 230 | using IterType = decltype(std::declval().begin()); 231 | using ValueRef = decltype(*std::declval()); 232 | using ValueType = typename std::remove_reference::type; 233 | 234 | struct Value 235 | { 236 | protected: 237 | 238 | IterType m_current; //!< The current iterator position 239 | IterType m_eraseStart; //!< The erase start position 240 | IterType m_end; //!< The end marker 241 | 242 | T* m_data = nullptr; //!< The data structure being processed 243 | 244 | bool m_markRemove = false; //!< If the item pointed to by m_current is to be removed 245 | 246 | inline Value() {} 247 | inline explicit Value(T* a_data) : m_data(a_data) 248 | { 249 | m_current = m_data->begin(); 250 | m_end = m_data->end(); 251 | m_eraseStart = m_end; 252 | } 253 | 254 | public: 255 | 256 | /// \brief Get the value from the container element 257 | inline ValueType& operator *() { return *m_current; } 258 | inline const ValueType& operator *() const { return *m_current; } 259 | 260 | /// \brief Get the value from the container element 261 | inline ValueType* operator ->() { return &*m_current; } 262 | inline const ValueType* operator ->() const { return &*m_current; } 263 | 264 | /// \brief Mark the current item to be erased from the parent container at a later stage. 265 | /// Can be called multiple times and value will still be valid until the next iteration. 266 | inline void mark_for_erase() { m_markRemove = true; } 267 | 268 | /// \brief Get the index of the loop counter - useful for debugging 269 | inline size_t loop_index() const { 270 | return std::distance(m_data->begin(), m_current) + 271 | std::distance(m_eraseStart, m_end); 272 | } 273 | }; 274 | 275 | struct Iterator : public Value 276 | { 277 | inline Iterator() {} 278 | inline explicit Iterator(T* a_data) : Value(a_data) {} 279 | 280 | inline Iterator& operator++() 281 | { 282 | // If removing - swap with last 283 | if (this->m_markRemove) 284 | { 285 | this->m_markRemove = false; 286 | --this->m_eraseStart; 287 | if (std::is_trivially_copyable::value || 288 | this->m_current != this->m_eraseStart) 289 | { 290 | *this->m_current = std::move(*this->m_eraseStart); 291 | } 292 | } 293 | else 294 | { 295 | ++this->m_current; 296 | } 297 | return *this; 298 | } 299 | 300 | template 301 | inline bool operator != (const V&) const { return this->m_current != this->m_eraseStart; } 302 | inline Value& operator *() { return *this; } 303 | 304 | inline ~Iterator() 305 | { 306 | #if !defined(ITER_USE_CPP17) 307 | if (this->m_data == nullptr) 308 | { 309 | return; 310 | } 311 | #endif 312 | 313 | // If aborted mid iteration (via break), still remove if flagged 314 | if (this->m_markRemove) 315 | { 316 | --this->m_eraseStart; 317 | if (std::is_trivially_copyable::value || 318 | this->m_current != this->m_eraseStart) 319 | { 320 | *this->m_current = std::move(*this->m_eraseStart); 321 | } 322 | } 323 | 324 | this->m_data->erase(this->m_eraseStart, this->m_end); 325 | } 326 | }; 327 | 328 | T& m_data; //!< The data structure being processed 329 | 330 | public: 331 | 332 | inline explicit unordered_eraser_wrapper(T& a_data) : m_data(a_data) {} 333 | 334 | inline Iterator begin() { return Iterator(&m_data); } 335 | 336 | #if defined(ITER_USE_CPP17) 337 | inline int end() { return 0; } 338 | #else 339 | inline Iterator end() { return Iterator(); } 340 | #endif 341 | 342 | }; 343 | 344 | /// \brief A iterator modifier that allows elements to be erased from the underlying type during iteration. 345 | /// Order of elements in the container type is NOT preserved during deletion. Deletion of elements will occur in order 346 | /// and by the exit of the iteration, but may be delayed. 347 | /// Container type must support an erase() method similar to std::vector::erase() 348 | /// 349 | /// Usage: for(auto& value : iter::unordered_eraser(vector)) 350 | /// { 351 | /// if(someCondition == *value) 352 | /// { 353 | /// value.mark_for_erase(); // Item will still be valid for remainer of iteration and will be erased in a future iteration 354 | /// 355 | /// Note that the value returned is a smart pointer that needs to be de-referenced to access the value 356 | /// (either with *value or value-> ) 357 | /// 358 | /// IMPORTANT: Do not store a pointer to deleted data inside the array being iterated on outside the for-loop scope - data may be moved between each iteration. 359 | /// Unlike iter::eraser(), it is valid (but not recommended) to store a pointer to data that is not going to be erased if the underlying container 360 | /// does not re-allocate on erase(). 361 | /// Eg. Do not do this: 362 | /// Type* foundItem = nullptr; 363 | /// for(auto& value : iter::unordered_eraser(vector)) 364 | /// { 365 | /// value.mark_for_delete(); 366 | /// foundItem = &*value; 367 | /// } 368 | /// foundItem->blah; // Data is not valid 369 | /// 370 | template 371 | unordered_eraser_wrapper unordered_eraser(T& v) 372 | { 373 | return unordered_eraser_wrapper(v); 374 | } 375 | 376 | template 377 | struct indexer_wrapper 378 | { 379 | private: 380 | using IterType = decltype(std::declval().begin()); 381 | using ValueRef = decltype(*std::declval()); 382 | using ValueType = typename std::remove_reference::type; 383 | 384 | struct Value 385 | { 386 | protected: 387 | 388 | T& m_data; //!< The data structure being processed 389 | IterType m_iter; //!< The iterator position 390 | 391 | inline Value(T& a_data, IterType a_iter) : m_data(a_data), m_iter(a_iter) {} 392 | 393 | public: 394 | 395 | /// \brief Get the value from the container element 396 | inline ValueType& operator *() { return *m_iter; } 397 | inline const ValueType& operator *() const { return *m_iter; } 398 | 399 | /// \brief Get the value from the container element 400 | inline ValueType* operator ->() { return &*m_iter; } 401 | inline const ValueType* operator ->() const { return &*m_iter; } 402 | 403 | /// \brief Get the item index 404 | inline size_t index() const { return std::distance(m_data.begin(), m_iter); } 405 | }; 406 | 407 | struct Iterator : public Value 408 | { 409 | inline Iterator(T& a_data, IterType a_iter) : Value(a_data, a_iter) {} 410 | 411 | inline Iterator& operator++() 412 | { 413 | ++this->m_iter; 414 | return *this; 415 | } 416 | 417 | inline bool operator != (const Iterator& a_other) const { return this->m_iter != a_other.m_iter; } 418 | inline Value& operator *() { return *this; } 419 | }; 420 | 421 | T& m_data; //!< The data structure being processed 422 | 423 | public: 424 | 425 | inline explicit indexer_wrapper(T& a_data) : m_data(a_data) {} 426 | 427 | inline Iterator begin() { return Iterator(m_data, m_data.begin()); } 428 | inline Iterator end() { return Iterator(m_data, m_data.end()); } 429 | 430 | }; 431 | 432 | /// \brief A helper to get the index of the currently processed item when using range for loops. 433 | /// 434 | /// Usage: for(auto& value : iter::indexer(vector)) 435 | /// { 436 | /// if(someCondition == *value) 437 | /// { 438 | /// value.index(); // Get the index of the item 439 | /// 440 | /// Note that the value returned is a smart pointer that needs to be de-referenced to access the value 441 | /// (either with *value or value-> ) 442 | /// 443 | template 444 | indexer_wrapper indexer(T& v) 445 | { 446 | return indexer_wrapper(v); 447 | } 448 | 449 | } // namespace iter 450 | 451 | #endif // !__TAREN_ITERATOR_H__ 452 | 453 | 454 | -------------------------------------------------------------------------------- /IteratorExt.h: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Copyright (C) 2016 Damian Trebilco 3 | // Licensed under the MIT license - See LICENSE.txt for details. 4 | //============================================================================= 5 | #ifndef __TAREN_ITERATOR_EXT_H__ 6 | #define __TAREN_ITERATOR_EXT_H__ 7 | 8 | #include "Iterator.h" 9 | #include 10 | 11 | /// \brief Special use case iterators that need care in use 12 | namespace iter 13 | { 14 | 15 | template 16 | struct eraser_append_wrapper 17 | { 18 | private: 19 | using IterType = decltype(std::declval().begin()); 20 | using ValueRef = decltype(*std::declval()); 21 | using ValueType = typename std::remove_reference::type; 22 | 23 | struct Value 24 | { 25 | protected: 26 | 27 | size_t m_current = 0; //!< The current iterator position 28 | size_t m_eraseStart = 0; //!< The erase start position 29 | size_t m_end = 0; //!< The end marker 30 | 31 | ValueType m_accessData; //!< Instance of the data to access 32 | 33 | T& m_data; //!< The data structure being processed 34 | 35 | bool m_markRemove = false; //!< If the item pointed to by m_current is to be removed 36 | 37 | inline Value(T& a_data) : m_data(a_data) 38 | { 39 | m_end = m_data.size(); 40 | 41 | // Get the first value 42 | if (m_end > 0) 43 | { 44 | m_accessData = std::move(*m_data.begin()); 45 | } 46 | } 47 | 48 | public: 49 | 50 | /// \brief Get the value from the container element 51 | inline ValueType& operator *() { return m_accessData; } 52 | inline const ValueType& operator *() const { return m_accessData; } 53 | 54 | /// \brief Get the value from the container element 55 | inline ValueType* operator ->() { return &m_accessData; } 56 | inline const ValueType* operator ->() const { return &m_accessData; } 57 | 58 | /// \brief Mark the current item to be erased from the parent container at a later stage. 59 | /// Can be called multiple times and value will still be valid until the next iteration. 60 | inline void mark_for_erase() { m_markRemove = true; } 61 | 62 | /// \brief Get the index of the this value in the parent container. Item may be shifted later if previous elements are erased. 63 | inline size_t index() const { return m_current; } 64 | }; 65 | 66 | struct Iterator : public Value 67 | { 68 | inline Iterator(T& a_data) : Value(a_data) {} 69 | 70 | inline Iterator& operator++() 71 | { 72 | auto restoreIter = this->m_data.begin(); 73 | 74 | // Determine where the value is restored 75 | if (this->m_markRemove) 76 | { 77 | restoreIter += this->m_current; 78 | } 79 | else 80 | { 81 | restoreIter += this->m_eraseStart; 82 | ++this->m_eraseStart; 83 | } 84 | 85 | // Put the data back into the container 86 | *restoreIter = std::move(this->m_accessData); 87 | 88 | this->m_markRemove = false; 89 | ++this->m_current; 90 | 91 | // Move the new item to local storage 92 | if (this->m_current != this->m_end) 93 | { 94 | this->m_accessData = std::move(*(this->m_data.begin() + this->m_current)); 95 | } 96 | return *this; 97 | } 98 | 99 | template 100 | inline bool operator != (const V&) const { return this->m_current != this->m_end; } 101 | inline Value& operator *() { return *this; } 102 | 103 | inline ~Iterator() 104 | { 105 | // If aborted mid iteration (via break), still remove if flagged 106 | if (this->m_current != this->m_end) 107 | { 108 | // Move the item back 109 | auto restoreIter = this->m_data.begin(); 110 | restoreIter += this->m_current; 111 | *restoreIter = std::move(this->m_accessData); 112 | 113 | if (this->m_markRemove) 114 | { 115 | ++this->m_current; 116 | } 117 | } 118 | this->m_data.erase(this->m_data.begin() + this->m_eraseStart, this->m_data.begin() + this->m_current); 119 | } 120 | }; 121 | 122 | T& m_data; //!< The data structure being processed 123 | 124 | public: 125 | inline eraser_append_wrapper(T& a_data) : m_data(a_data) {} 126 | 127 | inline Iterator begin() { return Iterator(m_data); } 128 | 129 | #ifdef ITER_USE_CPP17 130 | inline int end() { return 0; } 131 | #else 132 | T m_dummy; 133 | inline Iterator end() { return Iterator(m_dummy); } 134 | #endif 135 | }; 136 | 137 | /// \brief A iterator modifier that allows elements to be erased and appended from the underlying type during iteration. 138 | /// Order of elements in the container type is preserved during deletion. Deletion of elements will occur in order 139 | /// and by the exit of the iteration, but may be delayed. Any new elements appended during iteration are not processed. 140 | /// Container type must support an erase() method similar to std::vector::erase() 141 | /// 142 | /// Usage: for(auto& value : iter::eraser_safe_append(vector)) 143 | /// { 144 | /// auto& data = *value; // Can safely store reference to item under iteration 145 | /// vector.push_back(newValue); // Can append new data to vector (even if vector resizes) 146 | /// if(someCondition == data) 147 | /// { 148 | /// value.mark_for_erase(); // Item will still be valid for remainer of iteration and will be erased in a future iteration 149 | /// 150 | /// Note that the value returned is a smart pointer that needs to be de-referenced to access the value 151 | /// (either with *value or value-> ) 152 | /// The original index of the item in the array can also be retrieved by calling value.index(). This may not be optimal on some container types. 153 | /// 154 | /// IMPORTANT: Do not attempt to access any elements of the array being iterated on manually while iteration is occuring. 155 | /// eg. If appending, do not attempt to insert uniquely by finding a duplicate from the current array - an element may be marked for deletion or be temporally removed from the array. 156 | /// 157 | /// IMPORTANT: Do not store a pointer to any data inside the array being iterated on outside the for-loop scope - data is moved between each iteration. 158 | /// Eg. Do not do this: 159 | /// Type* foundItem = nullptr; 160 | /// for(auto& value : iter::eraser_safe_append(vector)) 161 | /// { 162 | /// ///.. erase value code ... 163 | /// foundItem = &*value; 164 | /// } 165 | /// foundItem->blah; // Data is not valid 166 | /// 167 | template 168 | eraser_append_wrapper> eraser_safe_append(std::vector& v) 169 | { 170 | // Not using a generic type here - as this is specialized on what containers this can work with 171 | return eraser_append_wrapper>(v); 172 | } 173 | 174 | 175 | } 176 | 177 | #endif // !__TAREN_ITERATOR_EXT_H__ 178 | 179 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Damian Trebilco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Profiler.h: -------------------------------------------------------------------------------- 1 | /// \brief A simple profiler that generates json that can be loaded into chrome://tracing 2 | /// It is implemented lock free and allocation free (during profiling) with no platform specific code. 3 | /// 4 | /// See: http://www.gamasutra.com/view/news/176420/Indepth_Using_Chrometracing_to_view_your_inline_profiling_data.php 5 | /// https://aras-p.info/blog/2017/01/23/Chrome-Tracing-as-Profiler-Frontend/ 6 | /// https://github.com/mainroach/sandbox/tree/master/chrome-event-trace 7 | /// https://github.com/catapult-project/catapult 8 | /// https://src.chromium.org/viewvc/chrome/trunk/src/base/debug/trace_event.h?view=markup 9 | /// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit 10 | /// 11 | /// To enable, define TAREN_PROFILE_ENABLE in the project builds that need profiling, then in one .cpp file 12 | /// define TAREN_PROFILER_IMPLEMENTATION before including this file. 13 | /// // i.e. it should look like this: 14 | /// #define TAREN_PROFILER_IMPLEMENTATION 15 | /// #include "Profiler.h" 16 | /// 17 | /// Usage: 18 | /// PROFILE_BEGIN(); // Enables profiling 19 | /// PROFILE_TAG_BEGIN("TagName"); // Starts a tag 20 | /// PROFILE_TAG_END(); // Ends a tag 21 | /// 22 | /// PROFILE_SCOPE("TagName); // Begin / End tag scope 23 | /// 24 | /// PROFILE_TAG_VALUE("TagName", 123); // Add an instant tag with a value 25 | /// PROFILE_END(string) or PROFILE_ENDFILEJSON("filename") // Writes tags to a string or a file 26 | /// 27 | /// Default tags must be a string literal or it will fail to compile. If you need a dynamic string, 28 | /// there is a limited scratch buffer that is used with the COPY / FORMAT / PRINTF variants of the tag types. 29 | /// eg. PROFILE_TAG_PRINTF_BEGIN("Value %d", 1234); 30 | /// PROFILE_TAG_FORMAT_BEGIN("Value {}", 1234); 31 | /// PROFILE_TAG_COPY_BEGIN(dynamicString.c_str()); 32 | /// 33 | /// Thread safety: 34 | /// The tag calls are thread safe, but the PROFILE_BEGIN() / PROFILE_END() are not. If you need to call these concurrently, protect with a mutex. 35 | /// 36 | /// Resource limits: 37 | /// The profiler has some hard coded limits that can be overridden by specifying some project #defines: 38 | /// TAREN_PROFILER_TAG_MAX_COUNT - How many tags to support in a capture 39 | /// TAREN_PROFILER_TAG_NAME_BUFFER_SIZE - Size of the buffer that caches dynamic tag names 40 | /// TAREN_PROFILER_FORMAT_COUNT - Max size of a dynamic tag 41 | #pragma once 42 | 43 | // TODO: Test thread safety in End() code 44 | 45 | #ifdef TAREN_PROFILE_ENABLE 46 | 47 | #ifndef TAREN_PROFILER_FORMAT_COUNT 48 | #define TAREN_PROFILER_FORMAT_COUNT 30 49 | #endif //!TAREN_PROFILER_TAG_COUNT 50 | 51 | #define PROFILE_FORMAT_INTERNAL(...) char buf[TAREN_PROFILER_FORMAT_COUNT]; const auto out = std::format_to_n(buf, TAREN_PROFILER_FORMAT_COUNT - 1, __VA_ARGS__); *out.out = '\0' 52 | #define PROFILE_PRINTF_INTERNAL(...) char buf[TAREN_PROFILER_FORMAT_COUNT]; std::snprintf(buf, TAREN_PROFILER_FORMAT_COUNT, __VA_ARGS__) 53 | 54 | #define PROFILE_BEGIN(...) taren_profiler::Begin(__VA_ARGS__) 55 | #define PROFILE_END(...) taren_profiler::End(__VA_ARGS__) 56 | #define PROFILE_ENDFILEJSON(...) taren_profiler::EndFileJson(__VA_ARGS__) 57 | 58 | #define PROFILE_TAG_BEGIN(str) static_assert(str[0] != 0, "Only literal strings - Use PROFILE_TAGCOPY_BEGIN"); taren_profiler::ProfileTag(taren_profiler::TagType::Begin, str) 59 | #define PROFILE_TAG_COPY_BEGIN(str) taren_profiler::ProfileTag(taren_profiler::TagType::Begin, str, true) 60 | #define PROFILE_TAG_FORMAT_BEGIN(...) if(taren_profiler::IsProfiling()) { PROFILE_FORMAT_INTERNAL(__VA_ARGS__); PROFILE_TAG_COPY_BEGIN(buf); } 61 | #define PROFILE_TAG_PRINTF_BEGIN(...) if(taren_profiler::IsProfiling()) { PROFILE_PRINTF_INTERNAL(__VA_ARGS__); PROFILE_TAG_COPY_BEGIN(buf); } 62 | #define PROFILE_TAG_END() taren_profiler::ProfileTag(taren_profiler::TagType::End, nullptr) 63 | 64 | #define PROFILE_SCOPE_INTERNAL2(X,Y) X ## Y 65 | #define PROFILE_SCOPE_INTERNAL(a,b) PROFILE_SCOPE_INTERNAL2(a,b) 66 | #define PROFILE_SCOPE(str) PROFILE_TAG_BEGIN(str); taren_profiler::ProfileScope PROFILE_SCOPE_INTERNAL(taren_profile_scope,__LINE__) 67 | #define PROFILE_SCOPE_COPY(str) PROFILE_TAG_COPY_BEGIN(str); taren_profiler::ProfileScope PROFILE_SCOPE_INTERNAL(taren_profile_scope,__LINE__) 68 | #define PROFILE_SCOPE_FORMAT(...) PROFILE_TAG_FORMAT_BEGIN(__VA_ARGS__); taren_profiler::ProfileScope PROFILE_SCOPE_INTERNAL(taren_profile_scope,__LINE__) 69 | #define PROFILE_SCOPE_PRINTF(...) PROFILE_TAG_PRINTF_BEGIN(__VA_ARGS__); taren_profiler::ProfileScope PROFILE_SCOPE_INTERNAL(taren_profile_scope,__LINE__) 70 | 71 | #define PROFILE_TAG_VALUE(str, value) static_assert(str[0] != 0, "Only literal strings - Use PROFILE_TAG_VALUE_COPY"); taren_profiler::ProfileTag(taren_profiler::TagType::Value, str, false, value) 72 | #define PROFILE_TAG_VALUE_COPY(str, value) taren_profiler::ProfileTag(taren_profiler::TagType::Value, str, true, value) 73 | #define PROFILE_TAG_VALUE_FORMAT(value, ...) if(taren_profiler::IsProfiling()) { PROFILE_FORMAT_INTERNAL(__VA_ARGS__); PROFILE_TAG_VALUE_COPY(buf, value); } 74 | #define PROFILE_TAG_VALUE_PRINTF(value, ...) if(taren_profiler::IsProfiling()) { PROFILE_PRINTF_INTERNAL(__VA_ARGS__); PROFILE_TAG_VALUE_COPY(buf, value); } 75 | 76 | #else // !TAREN_PROFILE_ENABLE 77 | 78 | #define PROFILE_BEGIN(...) 79 | #define PROFILE_END(...) 80 | #define PROFILE_ENDFILEJSON(...) 81 | 82 | #define PROFILE_TAG_BEGIN(...) 83 | #define PROFILE_TAG_COPY_BEGIN(...) 84 | #define PROFILE_TAG_FORMAT_BEGIN(...) 85 | #define PROFILE_TAG_PRINTF_BEGIN(...) 86 | #define PROFILE_TAG_END() 87 | 88 | #define PROFILE_SCOPE(...) 89 | #define PROFILE_SCOPE_COPY(...) 90 | #define PROFILE_SCOPE_FORMAT(...) 91 | #define PROFILE_SCOPE_PRINTF(...) 92 | 93 | #define PROFILE_TAG_VALUE(...) 94 | #define PROFILE_TAG_VALUE_COPY(...) 95 | #define PROFILE_TAG_VALUE_FORMAT(...) 96 | #define PROFILE_TAG_VALUE_PRINTF(...) 97 | 98 | #endif // !TAREN_PROFILE_ENABLE 99 | 100 | #ifdef TAREN_PROFILE_ENABLE 101 | 102 | #include 103 | #include 104 | 105 | #if (__cplusplus >= 202002L) 106 | #include 107 | #endif 108 | 109 | namespace taren_profiler 110 | { 111 | enum class TagType 112 | { 113 | Begin, 114 | End, 115 | Value, 116 | }; 117 | 118 | /// \brief Get if the profiler is currently running 119 | /// \return Returns true if profiling is current running 120 | bool IsProfiling(); 121 | 122 | /// \brief Start profiling recording 123 | /// \return Returns true if profiling was started 124 | bool Begin(); 125 | 126 | /// \brief Ends the profiling 127 | /// \param o_outStream The stream to write the json to 128 | /// \param o_outString The string to write the json to 129 | /// \return Returns true on success 130 | bool End(std::ostream& o_outStream); 131 | bool End(std::string& o_outString); 132 | 133 | /// \brief Ends the profiling and writes the json results to a file 134 | /// \param i_fileName The file name to write to. 135 | /// \param i_appendDateExtension If true, the current date/time and the extension .json is appended to the filename before opening. 136 | /// \return Returns true on success 137 | bool EndFileJson(const char* i_fileName, bool i_appendDateExtension = true); 138 | 139 | /// \brief Set a profiling tag 140 | /// \param i_type The type of tag 141 | /// \param i_str The tag name, must be a literal string or i_copyTag set to true 142 | /// \param i_copyStr If the i_str value should be copied internally 143 | /// \param i_value The value to supply with the tag 144 | void ProfileTag(TagType i_type, const char* i_str, bool i_copyStr = false, int32_t i_value = 0); 145 | 146 | struct ProfileScope 147 | { 148 | ~ProfileScope() { ProfileTag(TagType::End, nullptr); } 149 | }; 150 | } 151 | 152 | #ifdef TAREN_PROFILER_IMPLEMENTATION 153 | 154 | #ifndef TAREN_PROFILER_TAG_MAX_COUNT 155 | #define TAREN_PROFILER_TAG_MAX_COUNT 10000000 156 | #endif //!TAREN_PROFILER_TAG_COUNT 157 | 158 | #ifndef TAREN_PROFILER_TAG_NAME_BUFFER_SIZE 159 | #define TAREN_PROFILER_TAG_NAME_BUFFER_SIZE 1000000 160 | #endif //!TAREN_PROFILER_TAG_NAME_BUFFER_SIZE 161 | 162 | #include 163 | #include 164 | #include 165 | 166 | #include 167 | #include 168 | #include 169 | #include 170 | #include 171 | 172 | namespace 173 | { 174 | using clock = std::chrono::high_resolution_clock; 175 | 176 | struct ProfileRecord 177 | { 178 | clock::time_point m_time; // The time of the profile data 179 | const char* m_tag = nullptr; // The tag used in profiling - if empty is an end event 180 | std::thread::id m_threadID; // The id of the thread 181 | 182 | taren_profiler::TagType m_type; // The tag type 183 | int32_t m_value = 0; // Misc value used with the tag 184 | }; 185 | 186 | std::atomic_bool g_enabled = false; // If profiling is enabled 187 | clock::time_point g_startTime; // The start time of the profile 188 | 189 | std::atomic_uint32_t g_slotCount = 0; // The current slot counter 190 | std::atomic_uint32_t g_recordCount = 0; // The current record count 191 | ProfileRecord g_records[TAREN_PROFILER_TAG_MAX_COUNT]; // The profiling records 192 | 193 | std::atomic_uint32_t g_copyBufferSize = 0; // The current copy buffer usage count 194 | char g_copyBuffer[TAREN_PROFILER_TAG_NAME_BUFFER_SIZE]; // The buffer to store copied tag names 195 | 196 | const char* CopyStr(const char* i_str) 197 | { 198 | if (i_str == nullptr) 199 | { 200 | return nullptr; 201 | } 202 | 203 | // Allocate space to copy into 204 | uint32_t len = (uint32_t)strlen(i_str) + 1; 205 | uint32_t startOffset = g_copyBufferSize.fetch_add(len); 206 | if ((startOffset + len) <= TAREN_PROFILER_TAG_NAME_BUFFER_SIZE) 207 | { 208 | char* outBuffer = &g_copyBuffer[startOffset]; 209 | memcpy(outBuffer, i_str, len); 210 | return outBuffer; 211 | } 212 | else 213 | { 214 | g_copyBufferSize -= len; // Undo the add to make room for a smaller tag 215 | } 216 | 217 | return "OutOfTagBufferSpace"; 218 | } 219 | } 220 | 221 | namespace taren_profiler 222 | { 223 | void ProfileTag(TagType i_type, const char* i_str, bool i_copyStr, int32_t i_value) 224 | { 225 | if (!g_enabled) 226 | { 227 | return; 228 | } 229 | 230 | // Get the slot to put the record 231 | uint32_t recordIndex = g_slotCount.fetch_add(1); 232 | if (recordIndex < TAREN_PROFILER_TAG_MAX_COUNT) 233 | { 234 | ProfileRecord& newData = g_records[recordIndex]; 235 | newData.m_type = i_type; 236 | newData.m_threadID = std::this_thread::get_id(); 237 | newData.m_tag = i_copyStr ? CopyStr(i_str) : i_str; 238 | newData.m_value = i_value; 239 | newData.m_time = clock::now(); // Assign the time as the last possible thing 240 | 241 | g_recordCount++; // Flag that the record is complete 242 | } 243 | else 244 | { 245 | g_slotCount--; // Only hit if exceeded the record count or end of profiling, reverse the add 246 | } 247 | } 248 | 249 | bool IsProfiling() 250 | { 251 | return g_enabled; 252 | } 253 | 254 | bool Begin() 255 | { 256 | if (g_enabled) 257 | { 258 | return false; 259 | } 260 | 261 | // Clear all data (may have been some extra in buffers from previous enable) 262 | g_recordCount = 0; 263 | g_slotCount = 0; 264 | g_copyBufferSize = 0; 265 | g_startTime = clock::now(); 266 | g_enabled = true; 267 | return true; 268 | } 269 | 270 | static void CleanJsonStr(std::string& io_str) 271 | { 272 | size_t startPos = 0; 273 | while ((startPos = io_str.find_first_of("\\\"", startPos)) != std::string::npos) 274 | { 275 | io_str.insert(startPos, 1, '\\'); 276 | startPos += 2; 277 | } 278 | } 279 | 280 | bool End(std::ostream& o_outStream) 281 | { 282 | if (!g_enabled) 283 | { 284 | return false; 285 | } 286 | g_enabled = false; 287 | 288 | struct Tags 289 | { 290 | int32_t m_index = -1; // The index of the thread 291 | std::vector m_tags; // The tag stack 292 | }; 293 | 294 | // Init this thread as the primary thread 295 | std::unordered_map threadStack; 296 | threadStack[std::this_thread::get_id()].m_index = 0; 297 | 298 | int32_t threadCounter = 1; 299 | 300 | std::string cleanTag; 301 | o_outStream << "{\"traceEvents\":[\n"; 302 | 303 | // Flag that records should no longer be written by setting the slot count to TAREN_PROFILER_TAG_COUNT 304 | uint32_t slotCount = g_slotCount; 305 | do 306 | { 307 | // Check if already at the limit (can exceed the limit for a short duration) 308 | if (slotCount >= TAREN_PROFILER_TAG_MAX_COUNT) 309 | { 310 | slotCount = TAREN_PROFILER_TAG_MAX_COUNT; 311 | break; 312 | } 313 | 314 | } while (!g_slotCount.compare_exchange_weak(slotCount, (uint32_t)TAREN_PROFILER_TAG_MAX_COUNT)); 315 | 316 | // Wait for all threads to finish writing tags 317 | uint32_t recordCount = g_recordCount; 318 | while (recordCount != slotCount) 319 | { 320 | std::this_thread::yield(); 321 | recordCount = g_recordCount; 322 | } 323 | 324 | for (size_t i = 0; i < recordCount; i++) 325 | { 326 | const ProfileRecord& entry = g_records[i]; 327 | 328 | // Assign a unique index to each thread 329 | Tags& stack = threadStack[entry.m_threadID]; 330 | if (stack.m_index < 0) 331 | { 332 | stack.m_index = threadCounter; 333 | threadCounter++; 334 | } 335 | 336 | // Get the name tags 337 | const char* tag = entry.m_tag; 338 | if (tag == nullptr) 339 | { 340 | tag = "Unknown"; 341 | } 342 | const char* typeTag = "B"; 343 | if (entry.m_type == TagType::Begin) 344 | { 345 | stack.m_tags.push_back(tag); 346 | } 347 | else if (entry.m_type == TagType::End) 348 | { 349 | typeTag = "E"; 350 | if (stack.m_tags.size() > 0) 351 | { 352 | tag = stack.m_tags.back(); 353 | stack.m_tags.pop_back(); 354 | } 355 | } 356 | else if (entry.m_type == TagType::Value) 357 | { 358 | typeTag = "O"; 359 | } 360 | 361 | // Markup invalid json characters 362 | if (strchr(tag, '"') != nullptr || 363 | strchr(tag, '\\') != nullptr) 364 | { 365 | cleanTag = tag; 366 | CleanJsonStr(cleanTag); 367 | tag = cleanTag.c_str(); 368 | } 369 | 370 | // Get the microsecond count 371 | long long msCount = std::chrono::duration_cast(entry.m_time - g_startTime).count(); 372 | 373 | if (i != 0) 374 | { 375 | o_outStream << ",\n"; 376 | } 377 | 378 | // Format the string (Note using process ID for threads as that gives a better formatting in the output tool for value tags) 379 | o_outStream << 380 | "{\"name\":\"" << tag << "\",\"ph\":\"" << typeTag << "\",\"ts\":" << msCount << ",\"pid\":" << stack.m_index << ",\"cat\":\"\",\"tid\":0,"; 381 | 382 | if (entry.m_type == TagType::Value) 383 | { 384 | o_outStream << "\"id\":\"" << tag << "\", \"args\":{\"snapshot\":{\"Value\": " << entry.m_value << "}}}"; 385 | } 386 | else 387 | { 388 | o_outStream << "\"args\":{}}"; 389 | } 390 | } 391 | 392 | // Write thread "names" 393 | if (recordCount > 0) 394 | { 395 | for (auto& t : threadStack) 396 | { 397 | // Sort thread listing by the time that they appear in the profile (tool sorts by name) 398 | char indexSpaceString[64]; 399 | std::snprintf(indexSpaceString, sizeof(indexSpaceString), "%02d", t.second.m_index); 400 | 401 | // Ensure a clean json string (undefined what thread::id is) 402 | std::ostringstream ss; 403 | ss << t.first; 404 | std::string threadName = ss.str(); 405 | CleanJsonStr(threadName); 406 | 407 | o_outStream << // (Note using process ID for threads as that gives a better formatting in the output tool for value tags) 408 | ",\n{\"name\":\"thread_name\",\"ph\":\"M\",\"tid\":0,\"pid\":" << t.second.m_index << 409 | ",\"args\":{\"name\":\"Thread" << indexSpaceString << "_" << threadName << "\"}}"; 410 | } 411 | } 412 | 413 | o_outStream << "\n]\n}\n"; 414 | return true; 415 | } 416 | 417 | bool End(std::string& o_outString) 418 | { 419 | std::ostringstream ss; 420 | bool retval = End(ss); 421 | o_outString = ss.str(); 422 | return retval; 423 | } 424 | 425 | bool EndFileJson(const char* i_fileName, bool i_appendDateExtension) 426 | { 427 | std::ofstream file; 428 | if (i_appendDateExtension) 429 | { 430 | // Create a filename with the current date in it with extension 431 | std::time_t t = std::time(nullptr); 432 | tm timeBuf; 433 | localtime_s(&timeBuf, &t); 434 | char extStr[120]; 435 | if (std::strftime(extStr, sizeof(extStr), "_%Y%m%d-%H%M%S.json", &timeBuf) == 0) 436 | { 437 | return false; 438 | } 439 | 440 | file.open(std::string(i_fileName) + extStr); 441 | } 442 | else 443 | { 444 | file.open(i_fileName); 445 | } 446 | 447 | if (!file.is_open()) 448 | { 449 | return false; 450 | } 451 | return End(file); 452 | } 453 | 454 | } 455 | 456 | #endif // TAREN_PROFILER_IMPLEMENTATION 457 | 458 | #endif // TAREN_PROFILE_ENABLE 459 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Taren 2 | Taren is a collection of C++ code that provide simple but useful functionality. 3 | Taren code is standalone and only makes use of some STL. 4 | 5 | # Taren articles 6 | Along with the documentation is a series of articles explaining why certain functions exist. 7 | - [Eraser iterators.](./Articles/EraserProfile.md) 8 | - [Eraser append.](./Articles/EraserSafeAppend.md) 9 | - [Enum Macros.](./Articles/EnumMacros.md) 10 | 11 | # Taren functions 12 | ## Iterator: eraser() and unordered_eraser() 13 | Helpers to remove elements from a container while iterating it. 14 | 15 | Read about the performance of these iterators in [this article.](./Articles/EraserProfile.md) 16 | 17 | ```c++ 18 | for(auto& item : iter::eraser(array)) 19 | { 20 | if(*item == value) // Access item via deref 21 | { 22 | item.mark_for_erase(); // Item is marked for deletion, but is still valid until end of loop iteration 23 | item.index(); // Get the original index of the item in the array 24 | } 25 | } 26 | ``` 27 | If preserving order is not important: 28 | ```c++ 29 | for(auto& item : iter::unordered_eraser(array)) 30 | { 31 | if(*item == value) // Access item via deref 32 | { 33 | item.mark_for_erase(); // Item is marked for deletion, but is still valid until end of loop iteration 34 | } 35 | } 36 | ``` 37 | 38 | ## IteratorExt: eraser_safe_append() 39 | Helper to remove elements and append to a container while iterating it. 40 | 41 | Read about the usage of this iterator in [this article.](./Articles/EraserSafeAppend.md) 42 | 43 | ```c++ 44 | for(auto& item : iter::eraser_safe_append(vector)) 45 | { 46 | auto& data = *item; // Can safely store reference to item under iteration 47 | vector.push_back(newValue); // Can append new data to vector (even if vector resizes) 48 | 49 | if(data == value) 50 | { 51 | item.mark_for_erase(); // Item is marked for deletion, but is still valid until end of loop iteration 52 | item.index(); // Get the original index of the item in the array 53 | } 54 | } 55 | ``` 56 | 57 | ## Iterator: reverse() 58 | This helper simply reverses the iteration of the container 59 | ```c++ 60 | for(auto& item : iter::reverse(array)) 61 | ``` 62 | 63 | 64 | ## Iterator: counter() and counter_reverse() 65 | This helper provides a counter iterator in the cases where you still need a index. 66 | ```c++ 67 | // Produces -> 0,1,2,3,4,5... 68 | for(auto& index : iter::counter(array.size())) 69 | ``` 70 | 71 | ```c++ 72 | // Produces -> ...5,4,3,2,1,0 73 | for(auto& index : iter::counter_reverse(array.size())) 74 | ``` 75 | 76 | If used throughout a code base, it can be easily modified to account for different platforms preferences in loop counter types. 77 | 78 | 79 | ## Iterator: indexer() 80 | This helper simply provides a way to get the index of the iteration item (eraser() also provides this functionality) 81 | ```c++ 82 | for(const auto& item : iter::indexer(array)) 83 | { 84 | if(*item == blah) 85 | { 86 | return item.index(); 87 | } 88 | } 89 | ``` 90 | 91 | ## EnumMacros VALUE_ENUM() / SEQUENTIAL_ENUM() 92 | These macros provide a way to iterate enum values and associated strings. 93 | See the [article](./Articles/EnumMacros.md) for more details. 94 | ```c++ 95 | #define MyEnum_EnumValues(EV) \ 96 | EV(Value1) \ 97 | EV(Value2, 1 << 8) \ 98 | EV(value3, Value2 | Value1) 99 | VALUE_ENUM(MyEnum, uint32_t) 100 | VALUE_ENUM_BODY(MyEnum) 101 | 102 | // Can now do the below code 103 | const char* enumStr = MyEnum_Values::to_string(Value1); 104 | 105 | for (auto val : MyEnum_Values()) { 106 | val.c_str() // Get string of enum 107 | val.value() // Get enum value 108 | ``` 109 | 110 | ## EnumMacros ENUM_FLAG_OPS() 111 | This macro is used to generate the bitwise operations generally needed if the enum is a flag type. 112 | See the [article](./Articles/EnumMacros.md) for more details. 113 | ```c++ 114 | enum class BitFlags 115 | { 116 | Flag1 = 1 << 0, 117 | Flag2 = 1 << 1, 118 | Flag3 = 1 << 2, 119 | }; 120 | ENUM_FLAG_OPS(BitFlags) 121 | 122 | // Allows the below bit operations: 123 | BitFlags bitOp1 = BitFlags::Flag1 | BitFlags::Flag2; 124 | BitFlags bitOp2 = BitFlags::Flag1 & BitFlags::Flag2; 125 | BitFlags bitOp3 = BitFlags::Flag1 ^ BitFlags::Flag2; 126 | BitFlags bitOp4 = ~BitFlags::Flag1; 127 | 128 | ``` 129 | 130 | ## Profiler 131 | 132 | This header generates profile json that can be loaded into: 133 | 134 | [chrome://tracing](chrome://tracing) 135 | 136 | [Blog on Chrome tracing](https://aras-p.info/blog/2017/01/23/Chrome-Tracing-as-Profiler-Frontend/) 137 | 138 | It is implemented lock free and allocation free (during profiling) with no platform specific code. 139 | 140 | To enable, define **TAREN_PROFILE_ENABLE** in the project builds that need profiling, then in one .cpp file define **TAREN_PROFILER_IMPLEMENTATION** before including this file. 141 | i.e. it should look like this: 142 | ```c++ 143 | #define TAREN_PROFILER_IMPLEMENTATION 144 | #include "Profiler.h" 145 | ``` 146 | 147 | ```c++ 148 | PROFILE_BEGIN(); // Enables profiling 149 | 150 | PROFILE_TAG_BEGIN("TagName"); // Starts a tag 151 | PROFILE_TAG_END(); // Ends a tag 152 | 153 | PROFILE_SCOPE("TagName"); // Begin / End tag scope 154 | 155 | PROFILE_TAG_VALUE("TagName", 123); // Add an instant tag with a value 156 | 157 | PROFILE_END(string) // Writes tags to a string 158 | PROFILE_ENDFILEJSON("filename") // Writes tags to a file 159 | ``` 160 | 161 | Default tags must be a string literal or it will fail to compile. If you need a dynamic string, there is a limited scratch buffer that is used with the COPY / FORMAT / PRINTF variants of the tag types. 162 | 163 | ```c++ 164 | PROFILE_TAG_PRINTF_BEGIN("Value %d", 1234); 165 | PROFILE_TAG_FORMAT_BEGIN("Value {}", 1234); 166 | PROFILE_TAG_COPY_BEGIN(dynamicString.c_str()); 167 | ``` 168 | -------------------------------------------------------------------------------- /Tests/CppTemplates.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "../Iterator.h" 3 | 4 | #define TAREN_PROFILER_IMPLEMENTATION 5 | #include "../Profiler.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | bool Iterator_UnitTests(); 12 | void Iterator_RunProfile(); 13 | bool EnumMacro_UnitTests(); 14 | 15 | 16 | std::atomic_bool g_threadshutdown = false; 17 | 18 | void print_thread_id(int id) { 19 | 20 | while (!g_threadshutdown) 21 | { 22 | PROFILE_SCOPE_FORMAT("Thread processing {}", id); 23 | std::random_device rd; 24 | std::mt19937 mt(rd()); 25 | std::uniform_int_distribution dist(10, 50); 26 | int sleep_time = dist(mt); 27 | 28 | for (uint32_t i = 0; i < sleep_time; i++) 29 | { 30 | PROFILE_SCOPE("Funny"); 31 | 32 | using clock = std::chrono::high_resolution_clock; 33 | clock::time_point startTime = clock::now(); 34 | while ((clock::now() - startTime) < std::chrono::milliseconds(1)) 35 | { 36 | } 37 | 38 | //std::this_thread::sleep_for(std::chrono::milliseconds(1)); 39 | //std::this_thread::sleep_for(std::chrono::milliseconds(150)); 40 | } 41 | 42 | //std::cout << "Thread " << id << " slept for " << sleep_time * 10 << " milliseconds\n"; 43 | } 44 | } 45 | 46 | int main() 47 | { 48 | EnumMacro_UnitTests(); 49 | Iterator_UnitTests(); 50 | Iterator_RunProfile(); 51 | 52 | /* 53 | std::vector threads; 54 | 55 | for (int i = 0; i < 10; ++i) 56 | { 57 | threads.push_back(std::thread(print_thread_id, i)); 58 | } 59 | 60 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 61 | 62 | PROFILE_BEGIN(); 63 | 64 | { 65 | PROFILE_SCOPE("Funny"); 66 | PROFILE_SCOPE("Funny"); 67 | 68 | { 69 | 70 | std::string buf("Data"); 71 | PROFILE_SCOPE_COPY(buf.c_str()); 72 | 73 | PROFILE_SCOPE_FORMAT("Data Format {}", 675); 74 | 75 | PROFILE_SCOPE_PRINTF("Data %d", 123); 76 | 77 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 78 | 79 | PROFILE_SCOPE_PRINTF("Data %d", 456); 80 | 81 | PROFILE_TAG_VALUE("Test", 123); 82 | 83 | std::this_thread::sleep_for(std::chrono::milliseconds(200)); 84 | 85 | PROFILE_TAG_VALUE("FooBar", 678); 86 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 87 | PROFILE_TAG_VALUE_COPY("Copy str", 675); 88 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 89 | PROFILE_TAG_VALUE_FORMAT(13, "Format str {0}", 132); 90 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 91 | PROFILE_TAG_VALUE_PRINTF(14, "Printf %d", 142); 92 | 93 | PROFILE_TAG_BEGIN("Not ending"); 94 | 95 | PROFILE_TAG_COPY_BEGIN(std::string("CopyStr").c_str()); 96 | PROFILE_TAG_FORMAT_BEGIN("Format {0}", 376); 97 | PROFILE_TAG_PRINTF_BEGIN("Printf %d", 567); 98 | std::this_thread::sleep_for(std::chrono::milliseconds(200)); 99 | PROFILE_TAG_END(); 100 | PROFILE_TAG_END(); 101 | PROFILE_TAG_END(); 102 | 103 | std::this_thread::sleep_for(std::chrono::milliseconds(200)); 104 | } 105 | } 106 | PROFILE_ENDFILEJSON("Profile"); 107 | 108 | std::string outString; 109 | PROFILE_END(outString); 110 | 111 | g_threadshutdown = true; 112 | for (auto& thread : threads) 113 | { 114 | thread.join(); 115 | } 116 | */ 117 | //getchar(); 118 | return 0; 119 | } 120 | 121 | -------------------------------------------------------------------------------- /Tests/CppTemplates.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CppTemplates", "CppTemplates.vcxproj", "{68D79694-7498-4782-802B-18592EAD9160}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release (Clang)|x64 = Release (Clang)|x64 13 | Release (Clang)|x86 = Release (Clang)|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {68D79694-7498-4782-802B-18592EAD9160}.Debug|x64.ActiveCfg = Debug|x64 19 | {68D79694-7498-4782-802B-18592EAD9160}.Debug|x64.Build.0 = Debug|x64 20 | {68D79694-7498-4782-802B-18592EAD9160}.Debug|x86.ActiveCfg = Debug|Win32 21 | {68D79694-7498-4782-802B-18592EAD9160}.Debug|x86.Build.0 = Debug|Win32 22 | {68D79694-7498-4782-802B-18592EAD9160}.Release (Clang)|x64.ActiveCfg = Release (Clang)|x64 23 | {68D79694-7498-4782-802B-18592EAD9160}.Release (Clang)|x64.Build.0 = Release (Clang)|x64 24 | {68D79694-7498-4782-802B-18592EAD9160}.Release (Clang)|x86.ActiveCfg = Release (Clang)|Win32 25 | {68D79694-7498-4782-802B-18592EAD9160}.Release (Clang)|x86.Build.0 = Release (Clang)|Win32 26 | {68D79694-7498-4782-802B-18592EAD9160}.Release|x64.ActiveCfg = Release|x64 27 | {68D79694-7498-4782-802B-18592EAD9160}.Release|x64.Build.0 = Release|x64 28 | {68D79694-7498-4782-802B-18592EAD9160}.Release|x86.ActiveCfg = Release|Win32 29 | {68D79694-7498-4782-802B-18592EAD9160}.Release|x86.Build.0 = Release|Win32 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /Tests/CppTemplates.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release (Clang) 10 | Win32 11 | 12 | 13 | Release (Clang) 14 | x64 15 | 16 | 17 | Release 18 | Win32 19 | 20 | 21 | Debug 22 | x64 23 | 24 | 25 | Release 26 | x64 27 | 28 | 29 | 30 | {68D79694-7498-4782-802B-18592EAD9160} 31 | Win32Proj 32 | CppTemplates 33 | 10.0 34 | 35 | 36 | 37 | Application 38 | true 39 | v142 40 | Unicode 41 | 42 | 43 | Application 44 | false 45 | v142 46 | true 47 | Unicode 48 | 49 | 50 | Application 51 | false 52 | v140_clang_c2 53 | true 54 | Unicode 55 | 56 | 57 | Application 58 | true 59 | v142 60 | Unicode 61 | 62 | 63 | Application 64 | false 65 | v142 66 | true 67 | Unicode 68 | 69 | 70 | Application 71 | false 72 | v141_clang_c2 73 | true 74 | Unicode 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | true 102 | 103 | 104 | true 105 | 106 | 107 | false 108 | 109 | 110 | false 111 | $(ProjectName)_cl 112 | 113 | 114 | false 115 | 116 | 117 | false 118 | $(ProjectName)_cl 119 | 120 | 121 | 122 | 123 | 124 | Level3 125 | Disabled 126 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 127 | 128 | 129 | Console 130 | true 131 | 132 | 133 | 134 | 135 | 136 | 137 | Level3 138 | Disabled 139 | _DEBUG;_CONSOLE;TAREN_PROFILE_ENABLE;TAREN_PROFILER_IMPLEMENTATION;%(PreprocessorDefinitions) 140 | stdcpp17 141 | 142 | 143 | Console 144 | true 145 | 146 | 147 | 148 | 149 | Level3 150 | 151 | 152 | Full 153 | true 154 | true 155 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 156 | false 157 | false 158 | Speed 159 | StreamingSIMDExtensions2 160 | Fast 161 | true 162 | 163 | 164 | Console 165 | true 166 | true 167 | true 168 | 169 | 170 | 171 | 172 | Level3 173 | 174 | 175 | Full 176 | true 177 | true 178 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 179 | Disabled 180 | false 181 | Speed 182 | StreamingSIMDExtensions2 183 | Fast 184 | true 185 | 186 | 187 | Console 188 | true 189 | true 190 | false 191 | UseLinkTimeCodeGeneration 192 | 193 | 194 | 195 | 196 | Level3 197 | 198 | 199 | Full 200 | false 201 | true 202 | NDEBUG;_CONSOLE;TAREN_PROFILE_ENABLE;%(PreprocessorDefinitions) 203 | false 204 | false 205 | Fast 206 | true 207 | Speed 208 | StreamingSIMDExtensions2 209 | false 210 | 211 | 212 | Console 213 | true 214 | true 215 | true 216 | 217 | 218 | 219 | 220 | Level3 221 | 222 | 223 | Full 224 | false 225 | true 226 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 227 | Disabled 228 | false 229 | Fast 230 | true 231 | Speed 232 | StreamingSIMDExtensions2 233 | false 234 | 235 | 236 | Console 237 | true 238 | true 239 | false 240 | UseLinkTimeCodeGeneration 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /Tests/CppTemplates.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {eda8dea0-969e-43b2-9c20-d194cab5bc4f} 10 | 11 | 12 | {79a87e11-80e1-4879-aa13-e257271d67c8} 13 | 14 | 15 | 16 | 17 | Tests 18 | 19 | 20 | Tests 21 | 22 | 23 | Tests 24 | 25 | 26 | Tests 27 | 28 | 29 | Tests 30 | 31 | 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Tests 47 | 48 | 49 | Source Files 50 | 51 | 52 | 53 | 54 | Tests 55 | 56 | 57 | Articles 58 | 59 | 60 | Articles 61 | 62 | 63 | Articles 64 | 65 | 66 | -------------------------------------------------------------------------------- /Tests/CppTemplates2022.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CppTemplates", "CppTemplates2022.vcxproj", "{68D79694-7498-4782-802B-18592EAD9160}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release (Clang)|x64 = Release (Clang)|x64 13 | Release (Clang)|x86 = Release (Clang)|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {68D79694-7498-4782-802B-18592EAD9160}.Debug|x64.ActiveCfg = Debug|x64 19 | {68D79694-7498-4782-802B-18592EAD9160}.Debug|x64.Build.0 = Debug|x64 20 | {68D79694-7498-4782-802B-18592EAD9160}.Debug|x86.ActiveCfg = Debug|Win32 21 | {68D79694-7498-4782-802B-18592EAD9160}.Debug|x86.Build.0 = Debug|Win32 22 | {68D79694-7498-4782-802B-18592EAD9160}.Release (Clang)|x64.ActiveCfg = Release (Clang)|x64 23 | {68D79694-7498-4782-802B-18592EAD9160}.Release (Clang)|x64.Build.0 = Release (Clang)|x64 24 | {68D79694-7498-4782-802B-18592EAD9160}.Release (Clang)|x86.ActiveCfg = Release (Clang)|Win32 25 | {68D79694-7498-4782-802B-18592EAD9160}.Release (Clang)|x86.Build.0 = Release (Clang)|Win32 26 | {68D79694-7498-4782-802B-18592EAD9160}.Release|x64.ActiveCfg = Release|x64 27 | {68D79694-7498-4782-802B-18592EAD9160}.Release|x64.Build.0 = Release|x64 28 | {68D79694-7498-4782-802B-18592EAD9160}.Release|x86.ActiveCfg = Release|Win32 29 | {68D79694-7498-4782-802B-18592EAD9160}.Release|x86.Build.0 = Release|Win32 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /Tests/CppTemplates2022.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release (Clang) 10 | Win32 11 | 12 | 13 | Release (Clang) 14 | x64 15 | 16 | 17 | Release 18 | Win32 19 | 20 | 21 | Debug 22 | x64 23 | 24 | 25 | Release 26 | x64 27 | 28 | 29 | 30 | {68D79694-7498-4782-802B-18592EAD9160} 31 | Win32Proj 32 | CppTemplates 33 | 10.0 34 | 35 | 36 | 37 | Application 38 | true 39 | v143 40 | Unicode 41 | 42 | 43 | Application 44 | false 45 | v143 46 | true 47 | Unicode 48 | 49 | 50 | Application 51 | false 52 | v140_clang_c2 53 | true 54 | Unicode 55 | 56 | 57 | Application 58 | true 59 | v143 60 | Unicode 61 | 62 | 63 | Application 64 | false 65 | v143 66 | true 67 | Unicode 68 | 69 | 70 | Application 71 | false 72 | v141_clang_c2 73 | true 74 | Unicode 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | true 102 | 103 | 104 | true 105 | 106 | 107 | false 108 | 109 | 110 | false 111 | $(ProjectName)_cl 112 | 113 | 114 | false 115 | 116 | 117 | false 118 | $(ProjectName)_cl 119 | 120 | 121 | 122 | 123 | 124 | Level3 125 | Disabled 126 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 127 | 128 | 129 | Console 130 | true 131 | 132 | 133 | 134 | 135 | 136 | 137 | Level3 138 | Disabled 139 | _DEBUG;_CONSOLE;TAREN_PROFILE_ENABLE;%(PreprocessorDefinitions) 140 | stdcpp20 141 | 142 | 143 | Console 144 | true 145 | 146 | 147 | 148 | 149 | Level3 150 | 151 | 152 | Full 153 | true 154 | true 155 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 156 | false 157 | false 158 | Speed 159 | StreamingSIMDExtensions2 160 | Fast 161 | true 162 | 163 | 164 | Console 165 | true 166 | true 167 | true 168 | 169 | 170 | 171 | 172 | Level3 173 | 174 | 175 | Full 176 | true 177 | true 178 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 179 | Disabled 180 | false 181 | Speed 182 | StreamingSIMDExtensions2 183 | Fast 184 | true 185 | 186 | 187 | Console 188 | true 189 | true 190 | false 191 | UseLinkTimeCodeGeneration 192 | 193 | 194 | 195 | 196 | Level3 197 | 198 | 199 | Full 200 | false 201 | true 202 | NDEBUG;_CONSOLE;xTAREN_PROFILE_ENABLE;%(PreprocessorDefinitions) 203 | false 204 | false 205 | Fast 206 | true 207 | Speed 208 | StreamingSIMDExtensions2 209 | false 210 | stdcpp20 211 | 212 | 213 | Console 214 | true 215 | true 216 | true 217 | 218 | 219 | 220 | 221 | Level3 222 | 223 | 224 | Full 225 | false 226 | true 227 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 228 | Disabled 229 | false 230 | Fast 231 | true 232 | Speed 233 | StreamingSIMDExtensions2 234 | false 235 | 236 | 237 | Console 238 | true 239 | true 240 | false 241 | UseLinkTimeCodeGeneration 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /Tests/CppTemplates2022.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {eda8dea0-969e-43b2-9c20-d194cab5bc4f} 10 | 11 | 12 | {79a87e11-80e1-4879-aa13-e257271d67c8} 13 | 14 | 15 | 16 | 17 | Tests 18 | 19 | 20 | Tests 21 | 22 | 23 | Tests 24 | 25 | 26 | Tests 27 | 28 | 29 | Tests 30 | 31 | 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Tests 47 | 48 | 49 | Source Files 50 | 51 | 52 | 53 | 54 | Tests 55 | 56 | 57 | Articles 58 | 59 | 60 | Articles 61 | 62 | 63 | Articles 64 | 65 | 66 | -------------------------------------------------------------------------------- /Tests/EnumMacro_Base.cpp: -------------------------------------------------------------------------------- 1 | #include "EnumMacro_Base.h" 2 | 3 | 4 | 5 | SEQUENTIAL_ENUM_BODY(Test) 6 | VALUE_ENUM_BODY(TestVal) 7 | VALUE_ENUM_BODY(TestValDup) 8 | VALUE_ENUM_BODY(TestFlags) 9 | 10 | SEQUENTIAL_ENUM_BODYSCOPE(Test, ClassTest) 11 | VALUE_ENUM_BODYSCOPE(TestVal, ClassTest) 12 | VALUE_ENUM_BODYSCOPE(TestFlags, ClassTest) 13 | 14 | SEQUENTIAL_ENUM_BODYSCOPE(Test, NameTest) 15 | VALUE_ENUM_BODYSCOPE(TestVal, NameTest) 16 | VALUE_ENUM_BODYSCOPE(TestFlags, NameTest) 17 | 18 | 19 | -------------------------------------------------------------------------------- /Tests/EnumMacro_Base.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __TAREN_ENUM_MACROS_BASE_H__ 3 | #define __TAREN_ENUM_MACROS_BASE_H__ 4 | 5 | #include "../EnumMacros.h" 6 | #include 7 | 8 | 9 | #define Test_EnumValues(EV) \ 10 | EV(Foo) \ 11 | EV(Bar) \ 12 | EV(Baz) 13 | 14 | #define TestVal_EnumValues(EV) \ 15 | EV(Foo,2) \ 16 | EV(Bar) \ 17 | EV(Baz,7) \ 18 | EV(Single) //Single incrementing 19 | 20 | #define TestValDup_EnumValues(EV) \ 21 | EV(Foo,2) \ 22 | EV(Bar,6) \ 23 | EV(Baz,7) \ 24 | EV(Baz2,7) \ 25 | 26 | #define TestFlags_EnumValues(EV) \ 27 | EV(Foo, 1 << 0) \ 28 | EV(Bar, 1 << 1) \ 29 | EV(Baz, 1 << 2) \ 30 | EV(FooBaz, 1 << 3) \ 31 | EV(All, Foo|Bar|Baz|FooBaz) 32 | 33 | SEQUENTIAL_ENUM(Test, uint32_t) 34 | 35 | VALUE_ENUM(TestVal, uint32_t) 36 | VALUE_ENUM(TestValDup, uint32_t) 37 | 38 | VALUE_ENUM(TestFlags, uint32_t) 39 | ENUM_FLAG_OPS(TestFlags) 40 | 41 | class ClassTest 42 | { 43 | public: 44 | SEQUENTIAL_ENUM(Test, uint32_t) 45 | VALUE_ENUM(TestVal, uint32_t) 46 | VALUE_ENUM(TestFlags, uint32_t) 47 | }; 48 | 49 | ENUM_FLAG_OPS(ClassTest::TestFlags) 50 | 51 | namespace NameTest 52 | { 53 | SEQUENTIAL_ENUM(Test, uint32_t) 54 | VALUE_ENUM(TestVal, uint32_t) 55 | VALUE_ENUM(TestFlags, uint32_t) 56 | ENUM_FLAG_OPS(TestFlags) 57 | }; 58 | 59 | #endif // __TAREN_ENUM_MACROS_BASE_H__ 60 | -------------------------------------------------------------------------------- /Tests/EnumMacro_UnitTests.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "EnumMacro_Base.h" 4 | #include 5 | #include 6 | #include 7 | 8 | static_assert(uint32_t(Test::Foo) == 0, "Bad enum"); 9 | static_assert(uint32_t(Test::Bar) == 1, "Bad enum"); 10 | static_assert(uint32_t(Test::Baz) == 2, "Bad enum"); 11 | 12 | static_assert(uint32_t(TestVal::Foo) == 2, "Bad enum"); 13 | static_assert(uint32_t(TestVal::Bar) == 3, "Bad enum"); 14 | static_assert(uint32_t(TestVal::Baz) == 7, "Bad enum"); 15 | static_assert(uint32_t(TestVal::Single) == 8, "Bad enum"); 16 | 17 | static_assert(uint32_t(TestValDup::Foo) == 2, "Bad enum"); 18 | static_assert(uint32_t(TestValDup::Bar) == 6, "Bad enum"); 19 | static_assert(uint32_t(TestValDup::Baz) == 7, "Bad enum"); 20 | static_assert(uint32_t(TestValDup::Baz2) == 7, "Bad enum"); 21 | 22 | static_assert(uint32_t(TestFlags::Foo) == (1 << 0), "Bad enum"); 23 | static_assert(uint32_t(TestFlags::Bar) == (1 << 1), "Bad enum"); 24 | static_assert(uint32_t(TestFlags::Baz) == (1 << 2), "Bad enum"); 25 | static_assert(uint32_t(TestFlags::FooBaz) == (1 << 3), "Bad enum"); 26 | static_assert(uint32_t(TestFlags::All) == uint32_t(TestFlags::Foo | TestFlags::Bar | TestFlags::Baz | TestFlags::FooBaz), "Bad enum"); 27 | 28 | 29 | bool EnumMacro_UnitTests() 30 | { 31 | { 32 | std::vector strValues; 33 | for (auto val : Test_Values()) 34 | { 35 | std::string strValue = val.c_str(); 36 | if (strValue != Test_Values::to_string(val.value())) 37 | { 38 | std::cout << "Bad string"; 39 | return false; 40 | } 41 | 42 | strValues.push_back(strValue); 43 | } 44 | 45 | if (Test_Values::COUNT != 3 || 46 | strValues !=std::vector({ "Foo", "Bar", "Baz"})) 47 | { 48 | std::cout << "Bad enum array"; 49 | return false; 50 | } 51 | } 52 | 53 | { 54 | std::vector strValues; 55 | for (auto val : ClassTest::Test_Values()) 56 | { 57 | std::string strValue = val.c_str(); 58 | if (strValue != ClassTest::Test_Values::to_string(val.value())) 59 | { 60 | std::cout << "Bad string"; 61 | return false; 62 | } 63 | 64 | strValues.push_back(strValue); 65 | } 66 | 67 | if (ClassTest::Test_Values::COUNT != 3 || 68 | strValues != std::vector({ "Foo", "Bar", "Baz" })) 69 | { 70 | std::cout << "Bad enum array"; 71 | return false; 72 | } 73 | } 74 | 75 | { 76 | std::vector strValues; 77 | for (auto val : NameTest::Test_Values()) 78 | { 79 | std::string strValue = val.c_str(); 80 | if (strValue != NameTest::Test_Values::to_string(val.value())) 81 | { 82 | std::cout << "Bad string"; 83 | return false; 84 | } 85 | 86 | strValues.push_back(strValue); 87 | } 88 | 89 | if (NameTest::Test_Values::COUNT != 3 || 90 | strValues != std::vector({ "Foo", "Bar", "Baz" })) 91 | { 92 | std::cout << "Bad enum array"; 93 | return false; 94 | } 95 | } 96 | 97 | { 98 | std::vector strValues; 99 | for (auto val : TestVal_Values()) 100 | { 101 | std::string strValue = val.c_str(); 102 | if (strValue != TestVal_Values::to_string(val.value())) 103 | { 104 | std::cout << "Bad string"; 105 | return false; 106 | } 107 | 108 | strValues.push_back(strValue); 109 | } 110 | 111 | if (TestVal_Values::COUNT != 4 || 112 | strValues != std::vector({ "Foo", "Bar", "Baz", "Single" })) 113 | { 114 | std::cout << "Bad enum array"; 115 | return false; 116 | } 117 | } 118 | 119 | { 120 | std::vector strValues; 121 | for (auto val : ClassTest::TestVal_Values()) 122 | { 123 | std::string strValue = val.c_str(); 124 | if (strValue != ClassTest::TestVal_Values::to_string(val.value())) 125 | { 126 | std::cout << "Bad string"; 127 | return false; 128 | } 129 | 130 | strValues.push_back(strValue); 131 | } 132 | 133 | if (ClassTest::TestVal_Values::COUNT != 4 || 134 | strValues != std::vector({ "Foo", "Bar", "Baz", "Single" })) 135 | { 136 | std::cout << "Bad enum array"; 137 | return false; 138 | } 139 | } 140 | 141 | { 142 | std::vector strValues; 143 | for (auto val : NameTest::TestVal_Values()) 144 | { 145 | std::string strValue = val.c_str(); 146 | if (strValue != NameTest::TestVal_Values::to_string(val.value())) 147 | { 148 | std::cout << "Bad string"; 149 | return false; 150 | } 151 | 152 | strValues.push_back(strValue); 153 | } 154 | 155 | if (NameTest::TestVal_Values::COUNT != 4 || 156 | strValues != std::vector({ "Foo", "Bar", "Baz", "Single" })) 157 | { 158 | std::cout << "Bad enum array"; 159 | return false; 160 | } 161 | } 162 | 163 | { 164 | std::vector strValues; 165 | for (auto val : TestValDup_Values()) 166 | { 167 | std::string strValue = val.c_str(); 168 | if (strValue != TestValDup_Values::to_string(val.value())) 169 | { 170 | // This test has a duplicate value 171 | std::string serachStr = TestValDup_Values::to_string(val.value()); 172 | bool foundVal = false; 173 | for (auto serach : TestValDup_Values()) 174 | { 175 | if (serachStr == serach.c_str()) 176 | { 177 | if (val.value() != serach.value()) 178 | { 179 | std::cout << "Bad string"; 180 | return false; 181 | } 182 | foundVal = true; 183 | break; 184 | } 185 | } 186 | 187 | if (!foundVal) 188 | { 189 | std::cout << "Bad string"; 190 | return false; 191 | } 192 | } 193 | 194 | strValues.push_back(strValue); 195 | } 196 | 197 | if (TestValDup_Values::COUNT != 4 || 198 | strValues != std::vector({ "Foo", "Bar", "Baz", "Baz2" })) 199 | { 200 | std::cout << "Bad enum array"; 201 | return false; 202 | } 203 | } 204 | 205 | 206 | // Test flags 207 | { 208 | std::vector strValues; 209 | for (auto val : TestFlags_Values()) 210 | { 211 | std::string strValue = val.c_str(); 212 | if (strValue != TestFlags_Values::to_string(val.value())) 213 | { 214 | std::cout << "Bad string"; 215 | return false; 216 | } 217 | 218 | strValues.push_back(strValue); 219 | } 220 | 221 | if (TestFlags_Values::COUNT != 5 || 222 | strValues != std::vector({ "Foo", "Bar", "Baz", "FooBaz", "All" })) 223 | { 224 | std::cout << "Bad enum array"; 225 | return false; 226 | } 227 | } 228 | 229 | { 230 | std::vector strValues; 231 | for (auto val : ClassTest::TestFlags_Values()) 232 | { 233 | std::string strValue = val.c_str(); 234 | if (strValue != ClassTest::TestFlags_Values::to_string(val.value())) 235 | { 236 | std::cout << "Bad string"; 237 | return false; 238 | } 239 | 240 | strValues.push_back(strValue); 241 | } 242 | 243 | if (ClassTest::TestFlags_Values::COUNT != 5 || 244 | strValues != std::vector({ "Foo", "Bar", "Baz", "FooBaz", "All" })) 245 | { 246 | std::cout << "Bad enum array"; 247 | return false; 248 | } 249 | } 250 | 251 | { 252 | std::vector strValues; 253 | for (auto val : NameTest::TestFlags_Values()) 254 | { 255 | std::string strValue = val.c_str(); 256 | if (strValue != NameTest::TestFlags_Values::to_string(val.value())) 257 | { 258 | std::cout << "Bad string"; 259 | return false; 260 | } 261 | 262 | strValues.push_back(strValue); 263 | } 264 | 265 | if (NameTest::TestFlags_Values::COUNT != 5 || 266 | strValues != std::vector({ "Foo", "Bar", "Baz", "FooBaz", "All" })) 267 | { 268 | std::cout << "Bad enum array"; 269 | return false; 270 | } 271 | } 272 | 273 | { 274 | TestFlags bitOp1 = TestFlags::Bar | TestFlags::Baz; 275 | TestFlags bitOp2 = TestFlags::Bar & TestFlags::Baz; 276 | TestFlags bitOp3 = TestFlags::Bar ^ TestFlags::Baz; 277 | TestFlags bitOp4 = ~TestFlags::Bar; 278 | if (int(bitOp1) != 6 || 279 | int(bitOp2) != 0 || 280 | int(bitOp3) != 6 || 281 | int64_t(bitOp4) != 4294967293ULL) 282 | { 283 | std::cout << "Bad bit ops"; 284 | return false; 285 | } 286 | } 287 | 288 | { 289 | ClassTest::TestFlags bitOp1 = ClassTest::TestFlags::Bar | ClassTest::TestFlags::Baz; 290 | ClassTest::TestFlags bitOp2 = ClassTest::TestFlags::Bar & ClassTest::TestFlags::Baz; 291 | ClassTest::TestFlags bitOp3 = ClassTest::TestFlags::Bar ^ ClassTest::TestFlags::Baz; 292 | ClassTest::TestFlags bitOp4 = ~ClassTest::TestFlags::Bar; 293 | if (int(bitOp1) != 6 || 294 | int(bitOp2) != 0 || 295 | int(bitOp3) != 6 || 296 | int64_t(bitOp4) != 4294967293ULL) 297 | { 298 | std::cout << "Bad bit ops"; 299 | return false; 300 | } 301 | } 302 | 303 | { 304 | NameTest::TestFlags bitOp1 = NameTest::TestFlags::Bar | NameTest::TestFlags::Baz; 305 | NameTest::TestFlags bitOp2 = NameTest::TestFlags::Bar & NameTest::TestFlags::Baz; 306 | NameTest::TestFlags bitOp3 = NameTest::TestFlags::Bar ^ NameTest::TestFlags::Baz; 307 | NameTest::TestFlags bitOp4 = ~NameTest::TestFlags::Bar; 308 | if (int(bitOp1) != 6 || 309 | int(bitOp2) != 0 || 310 | int(bitOp3) != 6 || 311 | int64_t(bitOp4) != 4294967293ULL) 312 | { 313 | std::cout << "Bad bit ops"; 314 | return false; 315 | } 316 | } 317 | 318 | return true; 319 | } 320 | 321 | -------------------------------------------------------------------------------- /Tests/Iterator_Profile.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "../Iterator.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | static int32_t LOOPCOUNT = 1000; 13 | static uint32_t TEST_LOOPCOUNT = 100; 14 | 15 | static std::ofstream s_csvFile; 16 | 17 | #define TIMER_START std::chrono::microseconds shortDuration; \ 18 | for (uint32_t t = 0; t < TEST_LOOPCOUNT; t++) \ 19 | { \ 20 | std::vector process(LOOPCOUNT); \ 21 | for (auto& i : process) \ 22 | { \ 23 | i = a_data; \ 24 | } \ 25 | auto start_time = chrono::high_resolution_clock::now(); 26 | 27 | #define TIMER_END auto duration_time = chrono::duration_cast(chrono::high_resolution_clock::now() - start_time); \ 28 | if (t == 0 || duration_time < shortDuration) \ 29 | { \ 30 | shortDuration = duration_time; \ 31 | } \ 32 | } \ 33 | cout << chrono::duration_cast(shortDuration).count() << "us\n"; \ 34 | s_csvFile << chrono::duration_cast(shortDuration).count() << ","; 35 | 36 | 37 | using namespace std; 38 | 39 | template 40 | void ProfileRemove(const T & a_data, const V a_value, bool a_unorderd = false) 41 | { 42 | TIMER_START 43 | 44 | if (a_unorderd) 45 | { 46 | for (auto& d : process) 47 | { 48 | for (auto& a : iter::unordered_eraser(d)) 49 | { 50 | if (*a == a_value) 51 | { 52 | a.mark_for_erase(); 53 | } 54 | } 55 | } 56 | } 57 | else 58 | { 59 | for (auto& d : process) 60 | { 61 | for (auto& a : iter::eraser(d)) 62 | { 63 | if (*a == a_value) 64 | { 65 | a.mark_for_erase(); 66 | } 67 | } 68 | } 69 | } 70 | TIMER_END 71 | } 72 | 73 | 74 | template 75 | void ProfileRemoveFirst(const T & a_data, const V a_value, bool a_unorderd = false) 76 | { 77 | TIMER_START 78 | if (a_unorderd) 79 | { 80 | for (auto& d : process) 81 | { 82 | for (auto& a : iter::unordered_eraser(d)) 83 | { 84 | if (*a == a_value) 85 | { 86 | a.mark_for_erase(); 87 | break; 88 | } 89 | } 90 | } 91 | } 92 | else 93 | { 94 | for (auto& d : process) 95 | { 96 | for (auto& a : iter::eraser(d)) 97 | { 98 | if (*a == a_value) 99 | { 100 | a.mark_for_erase(); 101 | break; 102 | } 103 | } 104 | } 105 | } 106 | TIMER_END 107 | } 108 | 109 | 110 | template 111 | void GenericRemove(const T & a_data, const V a_value) 112 | { 113 | TIMER_START 114 | for (auto& d : process) 115 | { 116 | d.erase(std::remove(d.begin(), d.end(), a_value), d.end()); 117 | } 118 | TIMER_END 119 | } 120 | 121 | template 122 | void GenericRemoveIf(const T & a_data, const V a_value) 123 | { 124 | using IterType = decltype(std::declval().begin()); 125 | using ValueRef = decltype(*std::declval()); 126 | using ValueType = typename std::remove_reference::type; 127 | 128 | TIMER_START 129 | for (auto& d : process) 130 | { 131 | d.erase(std::remove_if(d.begin(), d.end(), [a_value](ValueType& a) { return (a == a_value); }), d.end()); 132 | } 133 | TIMER_END 134 | } 135 | 136 | template 137 | void RemoveArrayCopy(const T & a_data, const V a_value) 138 | { 139 | T copyData; 140 | copyData.resize(a_data.size()); 141 | 142 | TIMER_START 143 | for (auto& d : process) 144 | { 145 | // This code is about 2x slower than what is below 146 | //copyData.resize(0); 147 | //for (auto& v : d) 148 | //{ 149 | // if (v != a_value) 150 | // { 151 | // copyData.push_back(std::move(v)); 152 | // } 153 | //} 154 | //d.swap(copyData); 155 | 156 | copyData.resize(d.size()); 157 | int32_t size = 0; 158 | for (auto& v : d) 159 | { 160 | if (v != a_value) 161 | { 162 | copyData[size] = std::move(v); 163 | size++; 164 | } 165 | } 166 | copyData.resize(size); 167 | d.swap(copyData); 168 | } 169 | TIMER_END 170 | } 171 | 172 | 173 | template 174 | void GenericRemoveFirst(const T & a_data, const V a_value) 175 | { 176 | 177 | TIMER_START 178 | for (auto& d : process) 179 | { 180 | auto found = std::find(d.begin(), d.end(), a_value); 181 | if (found != d.end()) 182 | { 183 | d.erase(found); 184 | } 185 | } 186 | TIMER_END 187 | } 188 | 189 | 190 | template 191 | void ProfileIndex(const T & a_data, const V a_value) 192 | { 193 | TIMER_START 194 | for (auto& d : process) 195 | { 196 | 197 | for (int i = 0; i < d.size(); ) 198 | { 199 | if (d[i] == a_value) 200 | { 201 | d.erase(d.begin() + i); 202 | } 203 | else 204 | { 205 | i++;// Increment i++ if not deleting 206 | } 207 | } 208 | } 209 | TIMER_END 210 | } 211 | 212 | template 213 | void ProfileIndexFirst(const T & a_data, const V a_value) 214 | { 215 | TIMER_START 216 | for (auto& d : process) 217 | { 218 | 219 | for (int i = 0; i < d.size(); ) 220 | { 221 | if (d[i] == a_value) 222 | { 223 | d.erase(d.begin() + i); 224 | break; 225 | } 226 | else 227 | { 228 | i++;// Increment i++ if not deleting 229 | } 230 | } 231 | } 232 | TIMER_END 233 | } 234 | 235 | 236 | template 237 | void ProfileIterator(const T & a_data, const V a_value) 238 | { 239 | TIMER_START 240 | for (auto& d : process) 241 | { 242 | for (auto it = d.begin(); it != d.end();) 243 | { 244 | if (*it == a_value) 245 | { 246 | it = d.erase(it); 247 | } 248 | else 249 | { 250 | it++; 251 | } 252 | } 253 | } 254 | TIMER_END 255 | } 256 | 257 | 258 | template 259 | void ProfileIteratorFirst(const T & a_data, const V a_value) 260 | { 261 | TIMER_START 262 | for (auto& d : process) 263 | { 264 | for (auto it = d.begin(); it != d.end();) 265 | { 266 | if (*it == a_value) 267 | { 268 | it = d.erase(it); 269 | break; 270 | } 271 | else 272 | { 273 | it++; 274 | } 275 | } 276 | } 277 | TIMER_END 278 | } 279 | 280 | template 281 | void ProfileDirectRemoveFirstBreak(const T & a_data) 282 | { 283 | 284 | TIMER_START 285 | for (auto& d : process) 286 | { 287 | d.erase(d.begin()); 288 | } 289 | TIMER_END 290 | } 291 | 292 | 293 | template 294 | void ProfileDirectMemRemoveFirstBreak(const T & a_data) 295 | { 296 | 297 | TIMER_START 298 | for (auto& d : process) 299 | { 300 | memmove(d.data(), d.data() + 1, (d.size() - 1) *sizeof(int)); 301 | d.resize(d.size() - 1); 302 | } 303 | TIMER_END 304 | } 305 | 306 | 307 | template 308 | void RunTest(const T & a_data, const V & a_value, bool a_firstOnlyTests = false) 309 | { 310 | cout << "======================\n"; 311 | cout << " eraser : "; ProfileRemove(a_data, a_value); 312 | cout << " unordered_eraser : "; ProfileRemove(a_data, a_value, true); 313 | cout << " std::remove : "; GenericRemove(a_data, a_value); 314 | cout << " std::remove_if : "; GenericRemoveIf(a_data, a_value); 315 | cout << " array_copy : "; RemoveArrayCopy(a_data, a_value); 316 | cout << " index : "; ProfileIndex(a_data, a_value); 317 | cout << " iterator : "; ProfileIterator(a_data, a_value); 318 | 319 | if (a_firstOnlyTests) 320 | { 321 | cout << "======================\n"; 322 | cout << " eraser First : "; ProfileRemoveFirst(a_data, a_value); 323 | cout << " unordered_eraser First : "; ProfileRemoveFirst(a_data, a_value, true); 324 | cout << " std::find First : "; GenericRemoveFirst(a_data, a_value); 325 | cout << " index First : "; ProfileIndexFirst(a_data, a_value); 326 | cout << " iterator First: "; ProfileIteratorFirst(a_data, a_value); 327 | } 328 | 329 | s_csvFile << "\n"; 330 | } 331 | 332 | void WriteHeaders() 333 | { 334 | s_csvFile << "eraser,"; 335 | s_csvFile << "unordered_eraser,"; 336 | s_csvFile << "std::remove,"; 337 | s_csvFile << "std::remove_if,"; 338 | s_csvFile << "array_copy,"; 339 | s_csvFile << "index,"; 340 | s_csvFile << "iterator,"; 341 | 342 | s_csvFile << "eraser First,"; 343 | s_csvFile << "unordered_eraser First,"; 344 | s_csvFile << "std::find First,"; 345 | s_csvFile << "index First,"; 346 | s_csvFile << "iterator First,"; 347 | 348 | s_csvFile << "\n"; 349 | } 350 | 351 | template 352 | void RunTests(const T & a_data, const V & a_value) 353 | { 354 | { 355 | T newData = a_data; 356 | s_csvFile << "Base Iterate,"; 357 | RunTest(a_data, a_value, true); 358 | } 359 | 360 | { 361 | T newData = a_data; 362 | newData[0] = a_value; 363 | s_csvFile << "First value,"; 364 | RunTest(newData, a_value, true); 365 | } 366 | 367 | { 368 | T newData = a_data; 369 | newData[newData.size() / 2] = a_value; 370 | s_csvFile << "Middle value,"; 371 | RunTest(newData, a_value, true); 372 | } 373 | 374 | { 375 | T newData = a_data; 376 | newData[newData.size() - 1] = a_value; 377 | s_csvFile << "Last value,"; 378 | RunTest(newData, a_value, true); 379 | } 380 | 381 | { 382 | T newData = a_data; 383 | for (auto i : iter::counter(newData.size() / 4)) { newData[i] = a_value; } 384 | s_csvFile << "Remove 1/4,"; 385 | RunTest(newData, a_value); 386 | } 387 | 388 | { 389 | T newData = a_data; 390 | for (auto i : iter::counter(newData.size() / 2) ){ newData[i] = a_value; } 391 | 392 | s_csvFile << "Remove 1/2,"; 393 | RunTest(newData, a_value); 394 | } 395 | 396 | { 397 | T newData = a_data; 398 | for (auto i : iter::counter(newData.size() * 3 / 4)) { newData[i] = a_value; } 399 | s_csvFile << "Remove 3/4,"; 400 | RunTest(newData, a_value); 401 | } 402 | 403 | { 404 | T newData = a_data; 405 | for (auto& i : newData) { i = a_value; } 406 | s_csvFile << "Remove all,"; 407 | RunTest(newData, a_value); 408 | } 409 | } 410 | 411 | void Iterator_RunProfile() 412 | { 413 | s_csvFile.open("iter_profile.csv"); 414 | 415 | for(int i = 10; i <= 1000; i*=10) 416 | { 417 | std::vector testData(i); 418 | cout << "\n\nstd:vector: (" << testData.size() << ") x (" << LOOPCOUNT << ")\n"; 419 | s_csvFile << "\n\nstd:vector: (" << testData.size() << ") x (" << LOOPCOUNT << "),"; 420 | WriteHeaders(); 421 | RunTests(testData, 1); 422 | } 423 | 424 | for (int i = 10; i <= 1000; i *= 10) 425 | { 426 | std::vector testData(i); 427 | for (auto& v : testData) { v = "0"; } 428 | 429 | cout << "===================================================================\n"; 430 | cout << "\n\nstd:vector: (" << testData.size() << ") x (" << LOOPCOUNT << ")\n"; 431 | s_csvFile << "\n\nstd:vector: (" << testData.size() << ") x (" << LOOPCOUNT << "),"; 432 | WriteHeaders(); 433 | RunTests(testData, "1"); 434 | } 435 | 436 | 437 | s_csvFile.close(); 438 | } -------------------------------------------------------------------------------- /Tests/Iterator_UnitTests.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "../Iterator.h" 4 | #include "../IteratorExt.h" 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | 11 | bool ReverseTests() 12 | { 13 | { 14 | std::vector reverseTest = { 1,2,3,4 }; 15 | std::vector reverseResultTest; 16 | for (auto& val : iter::reverse(reverseTest)) 17 | { 18 | reverseResultTest.push_back(val); 19 | } 20 | if (reverseResultTest != std::vector{4, 3, 2, 1}) 21 | { 22 | std::cout << "Reverse failed\n"; 23 | return false; 24 | } 25 | } 26 | 27 | { 28 | const std::vector reverseTest = { 1,2,3,4 }; 29 | std::vector reverseResultTest; 30 | for (auto& val : iter::reverse(reverseTest)) 31 | { 32 | reverseResultTest.push_back(val); 33 | } 34 | if (reverseResultTest != std::vector{4, 3, 2, 1}) 35 | { 36 | std::cout << "Reverse failed\n"; 37 | return false; 38 | } 39 | } 40 | 41 | { 42 | const std::vector reverseTest = { "1","2","3","4" }; 43 | std::vector reverseResultTest; 44 | for (auto& val : iter::reverse(reverseTest)) 45 | { 46 | reverseResultTest.push_back(val); 47 | } 48 | if (reverseResultTest != std::vector{"4", "3", "2", "1"}) 49 | { 50 | std::cout << "Reverse failed\n"; 51 | return false; 52 | } 53 | } 54 | 55 | return true; 56 | } 57 | 58 | static bool CounterTests() 59 | { 60 | // Counter tests 61 | { 62 | std::vector counterResultTest; 63 | for (auto val : iter::counter(10)) 64 | { 65 | counterResultTest.push_back(int(val)); 66 | } 67 | if (counterResultTest != std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) 68 | { 69 | std::cout << "Counter failed\n"; 70 | return false; 71 | } 72 | } 73 | 74 | { 75 | std::vector counterResultTest; 76 | for (auto val : iter::counter(0)) 77 | { 78 | counterResultTest.push_back(int(val)); 79 | } 80 | if (counterResultTest != std::vector{}) 81 | { 82 | std::cout << "Counter failed\n"; 83 | return false; 84 | } 85 | } 86 | 87 | { 88 | std::vector counterResultTest; 89 | for (auto val : iter::counter(1)) 90 | { 91 | counterResultTest.push_back(int(val)); 92 | } 93 | if (counterResultTest != std::vector{0}) 94 | { 95 | std::cout << "Counter failed\n"; 96 | return false; 97 | } 98 | } 99 | 100 | 101 | // Counter reverse tests 102 | { 103 | std::vector counterResultTest; 104 | for (auto val : iter::counter_reverse(10)) 105 | { 106 | counterResultTest.push_back(int(val)); 107 | } 108 | if (counterResultTest != std::vector{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}) 109 | { 110 | std::cout << "Counter Reverse failed\n"; 111 | return false; 112 | } 113 | } 114 | 115 | { 116 | std::vector counterResultTest; 117 | for (auto val : iter::counter_reverse(0)) 118 | { 119 | counterResultTest.push_back(int(val)); 120 | } 121 | if (counterResultTest != std::vector{}) 122 | { 123 | std::cout << "Counter Reverse failed\n"; 124 | return false; 125 | } 126 | } 127 | 128 | { 129 | std::vector counterResultTest; 130 | for (auto val : iter::counter_reverse(1)) 131 | { 132 | counterResultTest.push_back(int(val)); 133 | } 134 | if (counterResultTest != std::vector{0}) 135 | { 136 | std::cout << "Counter Reverse failed\n"; 137 | return false; 138 | } 139 | } 140 | return true; 141 | } 142 | 143 | template 144 | bool IsSame(const A& a_a, const B& a_b) 145 | { 146 | if (a_a.size() != a_b.size()) 147 | { 148 | return false; 149 | } 150 | 151 | auto aStart = a_a.begin(); 152 | auto bStart = a_b.begin(); 153 | 154 | for (; aStart != a_a.end() && 155 | bStart != a_b.end(); ) 156 | { 157 | if (*aStart != *bStart) 158 | { 159 | return false; 160 | } 161 | ++aStart; 162 | ++bStart; 163 | } 164 | return true; 165 | } 166 | 167 | template 168 | bool ContainsSame(const A& a_a, const B& a_b) 169 | { 170 | if (a_a.size() != a_b.size()) 171 | { 172 | return false; 173 | } 174 | 175 | for (const auto& item : a_a) 176 | { 177 | if (std::count(a_a.begin(), a_a.end(), item) != 178 | std::count(a_b.begin(), a_b.end(), item)) 179 | { 180 | return false; 181 | } 182 | } 183 | 184 | return true; 185 | } 186 | 187 | template 188 | bool TypeIntTests() 189 | { 190 | // Eraser tests 191 | { 192 | T eraserTest = { 1,2,3,4 }; 193 | for (auto& val : iter::eraser(eraserTest)) 194 | { 195 | *val; 196 | } 197 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 })) 198 | { 199 | std::cout << "Eraser test failed\n"; 200 | return false; 201 | } 202 | } 203 | 204 | { 205 | size_t foundIndex = 0; 206 | T eraserTest = { 1,2,3,4 }; 207 | for (auto& val : iter::eraser(eraserTest)) 208 | { 209 | if (*val == 1) 210 | { 211 | foundIndex = val.index(); 212 | } 213 | } 214 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 215 | foundIndex != 0) 216 | { 217 | std::cout << "Eraser test failed\n"; 218 | return false; 219 | } 220 | } 221 | 222 | { 223 | size_t foundIndex = 0; 224 | T eraserTest = { 1,2,3,4 }; 225 | for (auto& val : iter::eraser(eraserTest)) 226 | { 227 | if (*val == 2) 228 | { 229 | foundIndex = val.index(); 230 | } 231 | } 232 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 233 | foundIndex != 1) 234 | { 235 | std::cout << "Eraser test failed\n"; 236 | return false; 237 | } 238 | } 239 | 240 | { 241 | size_t foundIndex = 0; 242 | T eraserTest = { 1,2,3,4 }; 243 | for (auto& val : iter::eraser(eraserTest)) 244 | { 245 | if (*val == 3) 246 | { 247 | foundIndex = val.index(); 248 | } 249 | } 250 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 251 | foundIndex != 2) 252 | { 253 | std::cout << "Eraser test failed\n"; 254 | return false; 255 | } 256 | } 257 | 258 | { 259 | size_t foundIndex = 0; 260 | T eraserTest = { 1,2,3,4 }; 261 | for (auto& val : iter::eraser(eraserTest)) 262 | { 263 | if (*val == 4) 264 | { 265 | foundIndex = val.index(); 266 | } 267 | } 268 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 269 | foundIndex != 3) 270 | { 271 | std::cout << "Eraser test failed\n"; 272 | return false; 273 | } 274 | } 275 | 276 | 277 | //// 278 | 279 | { 280 | size_t foundIndex = 0; 281 | T eraserTest = { 1,2,3,4 }; 282 | for (auto& val : iter::eraser(eraserTest)) 283 | { 284 | if (*val == 1) 285 | { 286 | foundIndex = val.index(); 287 | break; 288 | } 289 | } 290 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 291 | foundIndex != 0) 292 | { 293 | std::cout << "Eraser test failed\n"; 294 | return false; 295 | } 296 | } 297 | 298 | { 299 | size_t foundIndex = 0; 300 | T eraserTest = { 1,2,3,4 }; 301 | for (auto& val : iter::eraser(eraserTest)) 302 | { 303 | if (*val == 2) 304 | { 305 | foundIndex = val.index(); 306 | break; 307 | } 308 | } 309 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 310 | foundIndex != 1) 311 | { 312 | std::cout << "Eraser test failed\n"; 313 | return false; 314 | } 315 | } 316 | 317 | { 318 | size_t foundIndex = 0; 319 | T eraserTest = { 1,2,3,4 }; 320 | for (auto& val : iter::eraser(eraserTest)) 321 | { 322 | if (*val == 3) 323 | { 324 | foundIndex = val.index(); 325 | break; 326 | } 327 | } 328 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 329 | foundIndex != 2) 330 | { 331 | std::cout << "Eraser test failed\n"; 332 | return false; 333 | } 334 | } 335 | 336 | { 337 | size_t foundIndex = 0; 338 | T eraserTest = { 1,2,3,4 }; 339 | for (auto& val : iter::eraser(eraserTest)) 340 | { 341 | if (*val == 4) 342 | { 343 | foundIndex = val.index(); 344 | break; 345 | } 346 | } 347 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 348 | foundIndex != 3) 349 | { 350 | std::cout << "Eraser test failed\n"; 351 | return false; 352 | } 353 | } 354 | 355 | 356 | //// 357 | 358 | { 359 | size_t foundIndex = 0; 360 | T eraserTest = { 1,2,3,4 }; 361 | for (auto& val : iter::eraser(eraserTest)) 362 | { 363 | if (*val == 1) 364 | { 365 | val.mark_for_erase(); 366 | foundIndex = val.index(); 367 | } 368 | } 369 | if (!IsSame(eraserTest, T{2, 3, 4}) || 370 | foundIndex != 0) 371 | { 372 | std::cout << "Eraser test failed\n"; 373 | return false; 374 | } 375 | } 376 | { 377 | size_t foundIndex = 0; 378 | T eraserTest = { 1,2,3,4 }; 379 | for (auto& val : iter::eraser(eraserTest)) 380 | { 381 | if (*val == 2) 382 | { 383 | val.mark_for_erase(); 384 | foundIndex = val.index(); 385 | } 386 | } 387 | if (!IsSame(eraserTest, T{1, 3, 4}) || 388 | foundIndex != 1) 389 | { 390 | std::cout << "Eraser test failed\n"; 391 | return false; 392 | } 393 | } 394 | 395 | { 396 | size_t foundIndex = 0; 397 | T eraserTest = { 1,2,3,4 }; 398 | for (auto& val : iter::eraser(eraserTest)) 399 | { 400 | if (*val == 3) 401 | { 402 | val.mark_for_erase(); 403 | foundIndex = val.index(); 404 | } 405 | } 406 | if (!IsSame(eraserTest, T{1, 2, 4}) || 407 | foundIndex != 2) 408 | { 409 | std::cout << "Eraser test failed\n"; 410 | return false; 411 | } 412 | } 413 | { 414 | size_t foundIndex = 0; 415 | T eraserTest = { 1,2,3,4 }; 416 | for (auto& val : iter::eraser(eraserTest)) 417 | { 418 | if (*val == 4) 419 | { 420 | val.mark_for_erase(); 421 | foundIndex = val.index(); 422 | } 423 | } 424 | if (!IsSame(eraserTest, T{1, 2, 3}) || 425 | foundIndex != 3) 426 | { 427 | std::cout << "Eraser test failed\n"; 428 | return false; 429 | } 430 | } 431 | 432 | // Eraser break early tests 433 | { 434 | size_t foundIndex = 0; 435 | T eraserTest = { 1,2,3,4 }; 436 | for (auto& val : iter::eraser(eraserTest)) 437 | { 438 | if (*val == 1) 439 | { 440 | val.mark_for_erase(); 441 | foundIndex = val.index(); 442 | break; 443 | } 444 | } 445 | if (!IsSame(eraserTest, T{2, 3, 4}) || 446 | foundIndex != 0) 447 | { 448 | std::cout << "Eraser test failed\n"; 449 | return false; 450 | } 451 | } 452 | { 453 | size_t foundIndex = 0; 454 | T eraserTest = { 1,2,3,4 }; 455 | for (auto& val : iter::eraser(eraserTest)) 456 | { 457 | if (*val == 2) 458 | { 459 | val.mark_for_erase(); 460 | foundIndex = val.index(); 461 | break; 462 | } 463 | } 464 | if (!IsSame(eraserTest, T{1, 3, 4}) || 465 | foundIndex != 1) 466 | { 467 | std::cout << "Eraser test failed\n"; 468 | return false; 469 | } 470 | } 471 | 472 | { 473 | size_t foundIndex = 0; 474 | T eraserTest = { 1,2,3,4 }; 475 | for (auto& val : iter::eraser(eraserTest)) 476 | { 477 | if (*val == 3) 478 | { 479 | val.mark_for_erase(); 480 | foundIndex = val.index(); 481 | break; 482 | } 483 | } 484 | if (!IsSame(eraserTest, T{1, 2, 4}) || 485 | foundIndex != 2) 486 | { 487 | std::cout << "Eraser test failed\n"; 488 | return false; 489 | } 490 | } 491 | { 492 | size_t foundIndex = 0; 493 | T eraserTest = { 1,2,3,4 }; 494 | for (auto& val : iter::eraser(eraserTest)) 495 | { 496 | if (*val == 4) 497 | { 498 | val.mark_for_erase(); 499 | foundIndex = val.index(); 500 | break; 501 | } 502 | } 503 | if (!IsSame(eraserTest, T{1, 2, 3}) || 504 | foundIndex != 3) 505 | { 506 | std::cout << "Eraser test failed\n"; 507 | return false; 508 | } 509 | } 510 | 511 | { 512 | T eraserTest = { 1,2,3,4 }; 513 | for (auto& val : iter::eraser(eraserTest)) 514 | { 515 | if (*val == 4) 516 | { 517 | break; 518 | } 519 | else 520 | { 521 | val.mark_for_erase(); 522 | } 523 | } 524 | if (!IsSame(eraserTest, T{ 4 })) 525 | { 526 | std::cout << "Eraser test failed\n"; 527 | return false; 528 | } 529 | } 530 | 531 | // Eraser unordered tests 532 | { 533 | T eraserTest = { 1,2,3,4 }; 534 | for (auto& val : iter::unordered_eraser(eraserTest)) 535 | { 536 | *val; 537 | } 538 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 })) 539 | { 540 | std::cout << "Eraser test failed\n"; 541 | return false; 542 | } 543 | } 544 | 545 | //// 546 | 547 | { 548 | T eraserTest = { 1,2,3,4 }; 549 | for (auto& val : iter::unordered_eraser(eraserTest)) 550 | { 551 | if (*val == 1) 552 | { 553 | break; 554 | } 555 | } 556 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 })) 557 | { 558 | std::cout << "Eraser test failed\n"; 559 | return false; 560 | } 561 | } 562 | 563 | { 564 | T eraserTest = { 1,2,3,4 }; 565 | for (auto& val : iter::unordered_eraser(eraserTest)) 566 | { 567 | if (*val == 2) 568 | { 569 | break; 570 | } 571 | } 572 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) ) 573 | { 574 | std::cout << "Eraser test failed\n"; 575 | return false; 576 | } 577 | } 578 | 579 | { 580 | T eraserTest = { 1,2,3,4 }; 581 | for (auto& val : iter::unordered_eraser(eraserTest)) 582 | { 583 | if (*val == 3) 584 | { 585 | break; 586 | } 587 | } 588 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 })) 589 | { 590 | std::cout << "Eraser test failed\n"; 591 | return false; 592 | } 593 | } 594 | 595 | { 596 | T eraserTest = { 1,2,3,4 }; 597 | for (auto& val : iter::unordered_eraser(eraserTest)) 598 | { 599 | if (*val == 4) 600 | { 601 | break; 602 | } 603 | } 604 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 })) 605 | { 606 | std::cout << "Eraser test failed\n"; 607 | return false; 608 | } 609 | } 610 | 611 | //// 612 | 613 | { 614 | T eraserTest = { 1,2,3,4 }; 615 | for (auto& val : iter::unordered_eraser(eraserTest)) 616 | { 617 | if (*val == 1) 618 | { 619 | val.mark_for_erase(); 620 | } 621 | } 622 | if (!ContainsSame(eraserTest, T{2, 3, 4})) 623 | { 624 | std::cout << "Eraser test failed\n"; 625 | return false; 626 | } 627 | } 628 | { 629 | T eraserTest = { 1,2,3,4 }; 630 | for (auto& val : iter::unordered_eraser(eraserTest)) 631 | { 632 | if (*val == 2) 633 | { 634 | val.mark_for_erase(); 635 | } 636 | } 637 | if (!ContainsSame(eraserTest, T{1, 3, 4})) 638 | { 639 | std::cout << "Eraser test failed\n"; 640 | return false; 641 | } 642 | } 643 | 644 | { 645 | T eraserTest = { 1,2,3,4 }; 646 | for (auto& val : iter::unordered_eraser(eraserTest)) 647 | { 648 | if (*val == 3) 649 | { 650 | val.mark_for_erase(); 651 | } 652 | } 653 | if (!ContainsSame(eraserTest, T{1, 2, 4})) 654 | { 655 | std::cout << "Eraser test failed\n"; 656 | return false; 657 | } 658 | } 659 | { 660 | T eraserTest = { 1,2,3,4 }; 661 | for (auto& val : iter::unordered_eraser(eraserTest)) 662 | { 663 | if (*val == 4) 664 | { 665 | val.mark_for_erase(); 666 | } 667 | } 668 | if (!ContainsSame(eraserTest, T{1, 2, 3})) 669 | { 670 | std::cout << "Eraser test failed\n"; 671 | return false; 672 | } 673 | } 674 | 675 | // Eraser unordered break early tests 676 | { 677 | T eraserTest = { 1,2,3,4 }; 678 | for (auto& val : iter::unordered_eraser(eraserTest)) 679 | { 680 | if (*val == 1) 681 | { 682 | val.mark_for_erase(); 683 | break; 684 | } 685 | } 686 | if (!ContainsSame(eraserTest, T{2, 3, 4})) 687 | { 688 | std::cout << "Eraser test failed\n"; 689 | return false; 690 | } 691 | } 692 | { 693 | T eraserTest = { 1,2,3,4 }; 694 | for (auto& val : iter::unordered_eraser(eraserTest)) 695 | { 696 | if (*val == 2) 697 | { 698 | val.mark_for_erase(); 699 | break; 700 | } 701 | } 702 | if (!ContainsSame(eraserTest, T{1, 3, 4})) 703 | { 704 | std::cout << "Eraser test failed\n"; 705 | return false; 706 | } 707 | } 708 | 709 | { 710 | T eraserTest = { 1,2,3,4 }; 711 | for (auto& val : iter::unordered_eraser(eraserTest)) 712 | { 713 | if (*val == 3) 714 | { 715 | val.mark_for_erase(); 716 | break; 717 | } 718 | } 719 | if (!ContainsSame(eraserTest, T{1, 2, 4})) 720 | { 721 | std::cout << "Eraser test failed\n"; 722 | return false; 723 | } 724 | } 725 | { 726 | T eraserTest = { 1,2,3,4 }; 727 | for (auto& val : iter::unordered_eraser(eraserTest)) 728 | { 729 | if (*val == 4) 730 | { 731 | val.mark_for_erase(); 732 | break; 733 | } 734 | } 735 | if (!ContainsSame(eraserTest, T{1, 2, 3})) 736 | { 737 | std::cout << "Eraser test failed\n"; 738 | return false; 739 | } 740 | } 741 | 742 | return true; 743 | } 744 | 745 | 746 | template 747 | bool TypeStringTests() 748 | { 749 | // Eraser tests 750 | { 751 | size_t foundIndex = 0; 752 | T eraserTest = { "1","2","3","4" }; 753 | for (auto& val : iter::eraser(eraserTest)) 754 | { 755 | if (*val == "1") 756 | { 757 | val.mark_for_erase(); 758 | foundIndex = val.index(); 759 | } 760 | } 761 | if (!IsSame(eraserTest, T{"2", "3", "4"}) || 762 | foundIndex != 0) 763 | { 764 | std::cout << "Eraser test failed\n"; 765 | return false; 766 | } 767 | } 768 | { 769 | size_t foundIndex = 0; 770 | T eraserTest = { "1","2","3","4" }; 771 | for (auto& val : iter::eraser(eraserTest)) 772 | { 773 | if (*val == "2") 774 | { 775 | val.mark_for_erase(); 776 | foundIndex = val.index(); 777 | } 778 | } 779 | if (!IsSame(eraserTest, T{"1", "3", "4"}) || 780 | foundIndex != 1) 781 | { 782 | std::cout << "Eraser test failed\n"; 783 | return false; 784 | } 785 | } 786 | 787 | { 788 | size_t foundIndex = 0; 789 | T eraserTest = { "1","2","3","4" }; 790 | for (auto& val : iter::eraser(eraserTest)) 791 | { 792 | if (*val == "3") 793 | { 794 | val.mark_for_erase(); 795 | foundIndex = val.index(); 796 | } 797 | } 798 | if (!IsSame(eraserTest, T{"1", "2", "4"}) || 799 | foundIndex != 2) 800 | { 801 | std::cout << "Eraser test failed\n"; 802 | return false; 803 | } 804 | } 805 | { 806 | size_t foundIndex = 0; 807 | T eraserTest = { "1","2","3","4" }; 808 | for (auto& val : iter::eraser(eraserTest)) 809 | { 810 | if (*val == "4") 811 | { 812 | val.mark_for_erase(); 813 | foundIndex = val.index(); 814 | } 815 | } 816 | if (!IsSame(eraserTest, T{"1", "2", "3"}) || 817 | foundIndex != 3) 818 | { 819 | std::cout << "Eraser test failed\n"; 820 | return false; 821 | } 822 | } 823 | 824 | // Eraser break early tests 825 | { 826 | size_t foundIndex = 0; 827 | T eraserTest = { "1","2","3","4" }; 828 | for (auto& val : iter::eraser(eraserTest)) 829 | { 830 | if (*val == "1") 831 | { 832 | val.mark_for_erase(); 833 | foundIndex = val.index(); 834 | break; 835 | } 836 | } 837 | if (!IsSame(eraserTest, T{"2", "3", "4"}) || 838 | foundIndex != 0) 839 | { 840 | std::cout << "Eraser test failed\n"; 841 | return false; 842 | } 843 | } 844 | { 845 | size_t foundIndex = 0; 846 | T eraserTest = { "1","2","3","4" }; 847 | for (auto& val : iter::eraser(eraserTest)) 848 | { 849 | if (*val == "2") 850 | { 851 | val.mark_for_erase(); 852 | foundIndex = val.index(); 853 | break; 854 | } 855 | } 856 | if (!IsSame(eraserTest, T{"1", "3", "4"}) || 857 | foundIndex != 1) 858 | { 859 | std::cout << "Eraser test failed\n"; 860 | return false; 861 | } 862 | } 863 | 864 | { 865 | size_t foundIndex = 0; 866 | T eraserTest = { "1","2","3","4" }; 867 | for (auto& val : iter::eraser(eraserTest)) 868 | { 869 | if (*val == "3") 870 | { 871 | val.mark_for_erase(); 872 | foundIndex = val.index(); 873 | break; 874 | } 875 | } 876 | if (!IsSame(eraserTest, T{"1", "2", "4"}) || 877 | foundIndex != 2) 878 | { 879 | std::cout << "Eraser test failed\n"; 880 | return false; 881 | } 882 | } 883 | { 884 | size_t foundIndex = 0; 885 | T eraserTest = { "1","2","3","4" }; 886 | for (auto& val : iter::eraser(eraserTest)) 887 | { 888 | if (*val == "4") 889 | { 890 | val.mark_for_erase(); 891 | foundIndex = val.index(); 892 | break; 893 | } 894 | } 895 | if (!IsSame(eraserTest, T{"1", "2", "3"}) || 896 | foundIndex != 3) 897 | { 898 | std::cout << "Eraser test failed\n"; 899 | return false; 900 | } 901 | } 902 | 903 | { 904 | T eraserTest = { "1","2","3","4" }; 905 | for (auto& val : iter::eraser(eraserTest)) 906 | { 907 | if (*val == "4") 908 | { 909 | break; 910 | } 911 | else 912 | { 913 | val.mark_for_erase(); 914 | } 915 | } 916 | if (!IsSame(eraserTest, T{ "4" })) 917 | { 918 | std::cout << "Eraser test failed\n"; 919 | return false; 920 | } 921 | } 922 | 923 | // Eraser unordered tests 924 | { 925 | T eraserTest = { "1","2","3","4" }; 926 | for (auto& val : iter::unordered_eraser(eraserTest)) 927 | { 928 | if (*val == "1") 929 | { 930 | val.mark_for_erase(); 931 | } 932 | } 933 | if (!ContainsSame(eraserTest, T{"2", "3", "4"})) 934 | { 935 | std::cout << "Eraser test failed\n"; 936 | return false; 937 | } 938 | } 939 | { 940 | T eraserTest = { "1","2","3","4" }; 941 | for (auto& val : iter::unordered_eraser(eraserTest)) 942 | { 943 | if (*val == "2") 944 | { 945 | val.mark_for_erase(); 946 | } 947 | } 948 | if (!ContainsSame(eraserTest, T{"1", "3", "4"})) 949 | { 950 | std::cout << "Eraser test failed\n"; 951 | return false; 952 | } 953 | } 954 | 955 | { 956 | T eraserTest = { "1","2","3","4" }; 957 | for (auto& val : iter::unordered_eraser(eraserTest)) 958 | { 959 | if (*val == "3") 960 | { 961 | val.mark_for_erase(); 962 | } 963 | } 964 | if (!ContainsSame(eraserTest, T{"1", "2", "4"})) 965 | { 966 | std::cout << "Eraser test failed\n"; 967 | return false; 968 | } 969 | } 970 | { 971 | T eraserTest = { "1","2","3","4" }; 972 | for (auto& val : iter::unordered_eraser(eraserTest)) 973 | { 974 | if (*val == "4") 975 | { 976 | val.mark_for_erase(); 977 | } 978 | } 979 | if (!ContainsSame(eraserTest, T{"1", "2", "3"})) 980 | { 981 | std::cout << "Eraser test failed\n"; 982 | return false; 983 | } 984 | } 985 | 986 | // Eraser unordered break early tests 987 | { 988 | T eraserTest = { "1","2","3","4" }; 989 | for (auto& val : iter::unordered_eraser(eraserTest)) 990 | { 991 | if (*val == "1") 992 | { 993 | val.mark_for_erase(); 994 | break; 995 | } 996 | } 997 | if (!ContainsSame(eraserTest, T{"2", "3", "4"})) 998 | { 999 | std::cout << "Eraser test failed\n"; 1000 | return false; 1001 | } 1002 | } 1003 | { 1004 | T eraserTest = { "1","2","3","4" }; 1005 | for (auto& val : iter::unordered_eraser(eraserTest)) 1006 | { 1007 | if (*val == "2") 1008 | { 1009 | val.mark_for_erase(); 1010 | break; 1011 | } 1012 | } 1013 | if (!ContainsSame(eraserTest, T{"1", "3", "4"})) 1014 | { 1015 | std::cout << "Eraser test failed\n"; 1016 | return false; 1017 | } 1018 | } 1019 | 1020 | { 1021 | T eraserTest = { "1","2","3","4" }; 1022 | for (auto& val : iter::unordered_eraser(eraserTest)) 1023 | { 1024 | if (*val == "3") 1025 | { 1026 | val.mark_for_erase(); 1027 | break; 1028 | } 1029 | } 1030 | if (!ContainsSame(eraserTest, T{"1", "2", "4"})) 1031 | { 1032 | std::cout << "Eraser test failed\n"; 1033 | return false; 1034 | } 1035 | } 1036 | { 1037 | T eraserTest = { "1","2","3","4" }; 1038 | for (auto& val : iter::unordered_eraser(eraserTest)) 1039 | { 1040 | if (*val == "4") 1041 | { 1042 | val.mark_for_erase(); 1043 | break; 1044 | } 1045 | } 1046 | if (!ContainsSame(eraserTest, T{"1", "2", "3"})) 1047 | { 1048 | std::cout << "Eraser test failed\n"; 1049 | return false; 1050 | } 1051 | } 1052 | 1053 | return true; 1054 | } 1055 | 1056 | template 1057 | bool IndexerStringTests() 1058 | { 1059 | // Indexer tests 1060 | { 1061 | size_t foundIndex = 0; 1062 | T indexer = { "1","2","3","4" }; 1063 | for (auto& val : iter::indexer(indexer)) 1064 | { 1065 | if (*val == "1") 1066 | { 1067 | foundIndex = val.index(); 1068 | } 1069 | } 1070 | if (!IsSame(indexer, T{ "1", "2", "3", "4" }) || 1071 | foundIndex != 0) 1072 | { 1073 | std::cout << "Indexer test failed\n"; 1074 | return false; 1075 | } 1076 | } 1077 | 1078 | { 1079 | size_t foundIndex = 0; 1080 | const T indexer = { "1","2","3","4" }; 1081 | for (auto& val : iter::indexer(indexer)) 1082 | { 1083 | if (*val == "1") 1084 | { 1085 | foundIndex = val.index(); 1086 | } 1087 | } 1088 | if (!IsSame(indexer, T{ "1", "2", "3", "4" }) || 1089 | foundIndex != 0) 1090 | { 1091 | std::cout << "Indexer test failed\n"; 1092 | return false; 1093 | } 1094 | } 1095 | 1096 | { 1097 | size_t foundIndex = 0; 1098 | const T indexer = { "1","2","3","4" }; 1099 | for (auto& val : iter::indexer(indexer)) 1100 | { 1101 | if (*val == "2") 1102 | { 1103 | foundIndex = val.index(); 1104 | } 1105 | } 1106 | if (!IsSame(indexer, T{ "1", "2", "3", "4" }) || 1107 | foundIndex != 1) 1108 | { 1109 | std::cout << "Indexer test failed\n"; 1110 | return false; 1111 | } 1112 | } 1113 | 1114 | { 1115 | size_t foundIndex = 0; 1116 | const T indexer = { "1","2","3","4" }; 1117 | for (auto& val : iter::indexer(indexer)) 1118 | { 1119 | if (*val == "3") 1120 | { 1121 | foundIndex = val.index(); 1122 | } 1123 | } 1124 | if (!IsSame(indexer, T{ "1", "2", "3", "4" }) || 1125 | foundIndex != 2) 1126 | { 1127 | std::cout << "Indexer test failed\n"; 1128 | return false; 1129 | } 1130 | } 1131 | 1132 | { 1133 | size_t foundIndex = 0; 1134 | const T indexer = { "1","2","3","4" }; 1135 | for (auto& val : iter::indexer(indexer)) 1136 | { 1137 | if (*val == "4") 1138 | { 1139 | foundIndex = val.index(); 1140 | } 1141 | } 1142 | if (!IsSame(indexer, T{ "1", "2", "3", "4" }) || 1143 | foundIndex != 3) 1144 | { 1145 | std::cout << "Indexer test failed\n"; 1146 | return false; 1147 | } 1148 | } 1149 | 1150 | { 1151 | size_t foundIndex = 10; 1152 | const T indexer = { }; 1153 | for (auto& val : iter::indexer(indexer)) 1154 | { 1155 | if (*val == "1") 1156 | { 1157 | foundIndex = val.index(); 1158 | } 1159 | } 1160 | if (!IsSame(indexer, T{ }) || 1161 | foundIndex != 10) 1162 | { 1163 | std::cout << "Indexer test failed\n"; 1164 | return false; 1165 | } 1166 | } 1167 | 1168 | { 1169 | size_t foundIndex = 10; 1170 | const T indexer = { "1" }; 1171 | for (auto& val : iter::indexer(indexer)) 1172 | { 1173 | if (*val == "1") 1174 | { 1175 | foundIndex = val.index(); 1176 | } 1177 | } 1178 | if (!IsSame(indexer, T{"1"}) || 1179 | foundIndex != 0) 1180 | { 1181 | std::cout << "Indexer test failed\n"; 1182 | return false; 1183 | } 1184 | } 1185 | 1186 | { 1187 | size_t foundIndex = 10; 1188 | const T indexer = { "1" }; 1189 | for (const auto& val : iter::indexer(indexer)) 1190 | { 1191 | if (*val == "1") 1192 | { 1193 | foundIndex = val.index(); 1194 | } 1195 | } 1196 | if (!IsSame(indexer, T{ "1" }) || 1197 | foundIndex != 0) 1198 | { 1199 | std::cout << "Indexer test failed\n"; 1200 | return false; 1201 | } 1202 | } 1203 | 1204 | { 1205 | size_t foundIndex = 10; 1206 | const T indexer = { "1" }; 1207 | for (const auto val : iter::indexer(indexer)) 1208 | { 1209 | if (*val == "1") 1210 | { 1211 | foundIndex = val.index(); 1212 | } 1213 | } 1214 | if (!IsSame(indexer, T{ "1" }) || 1215 | foundIndex != 0) 1216 | { 1217 | std::cout << "Indexer test failed\n"; 1218 | return false; 1219 | } 1220 | } 1221 | 1222 | { 1223 | size_t foundIndex = 10; 1224 | const T indexer = { "1", "2" }; 1225 | for (auto val : iter::indexer(indexer)) 1226 | { 1227 | if (*val == "2") 1228 | { 1229 | foundIndex = val.index(); 1230 | } 1231 | } 1232 | if (!IsSame(indexer, T{ "1", "2" }) || 1233 | foundIndex != 1) 1234 | { 1235 | std::cout << "Indexer test failed\n"; 1236 | return false; 1237 | } 1238 | } 1239 | 1240 | { 1241 | size_t foundIndex = 10; 1242 | const T indexer = { "1", "2", "3" }; 1243 | for (auto val : iter::indexer(indexer)) 1244 | { 1245 | foundIndex = val.index(); 1246 | } 1247 | if (!IsSame(indexer, T{ "1", "2", "3" }) || 1248 | foundIndex != 2) 1249 | { 1250 | std::cout << "Indexer test failed\n"; 1251 | return false; 1252 | } 1253 | } 1254 | 1255 | return true; 1256 | } 1257 | 1258 | template 1259 | bool TypeAppendIntTests() 1260 | { 1261 | // Eraser tests 1262 | { 1263 | T eraserTest = { 1,2,3,4 }; 1264 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1265 | { 1266 | *val; 1267 | } 1268 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 })) 1269 | { 1270 | std::cout << "Eraser test failed\n"; 1271 | return false; 1272 | } 1273 | } 1274 | 1275 | { 1276 | size_t foundIndex = 0; 1277 | T eraserTest = { 1,2,3,4 }; 1278 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1279 | { 1280 | if (*val == 1) 1281 | { 1282 | foundIndex = val.index(); 1283 | } 1284 | } 1285 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 1286 | foundIndex != 0) 1287 | { 1288 | std::cout << "Eraser test failed\n"; 1289 | return false; 1290 | } 1291 | } 1292 | 1293 | { 1294 | size_t foundIndex = 0; 1295 | T eraserTest = { 1,2,3,4 }; 1296 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1297 | { 1298 | if (*val == 2) 1299 | { 1300 | foundIndex = val.index(); 1301 | } 1302 | } 1303 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 1304 | foundIndex != 1) 1305 | { 1306 | std::cout << "Eraser test failed\n"; 1307 | return false; 1308 | } 1309 | } 1310 | 1311 | { 1312 | size_t foundIndex = 0; 1313 | T eraserTest = { 1,2,3,4 }; 1314 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1315 | { 1316 | if (*val == 3) 1317 | { 1318 | foundIndex = val.index(); 1319 | } 1320 | } 1321 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 1322 | foundIndex != 2) 1323 | { 1324 | std::cout << "Eraser test failed\n"; 1325 | return false; 1326 | } 1327 | } 1328 | 1329 | { 1330 | size_t foundIndex = 0; 1331 | T eraserTest = { 1,2,3,4 }; 1332 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1333 | { 1334 | if (*val == 4) 1335 | { 1336 | foundIndex = val.index(); 1337 | } 1338 | } 1339 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 1340 | foundIndex != 3) 1341 | { 1342 | std::cout << "Eraser test failed\n"; 1343 | return false; 1344 | } 1345 | } 1346 | 1347 | 1348 | //// 1349 | 1350 | { 1351 | size_t foundIndex = 0; 1352 | T eraserTest = { 1,2,3,4 }; 1353 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1354 | { 1355 | if (*val == 1) 1356 | { 1357 | foundIndex = val.index(); 1358 | break; 1359 | } 1360 | } 1361 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 1362 | foundIndex != 0) 1363 | { 1364 | std::cout << "Eraser test failed\n"; 1365 | return false; 1366 | } 1367 | } 1368 | 1369 | { 1370 | size_t foundIndex = 0; 1371 | T eraserTest = { 1,2,3,4 }; 1372 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1373 | { 1374 | if (*val == 2) 1375 | { 1376 | foundIndex = val.index(); 1377 | break; 1378 | } 1379 | } 1380 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 1381 | foundIndex != 1) 1382 | { 1383 | std::cout << "Eraser test failed\n"; 1384 | return false; 1385 | } 1386 | } 1387 | 1388 | { 1389 | size_t foundIndex = 0; 1390 | T eraserTest = { 1,2,3,4 }; 1391 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1392 | { 1393 | if (*val == 3) 1394 | { 1395 | foundIndex = val.index(); 1396 | break; 1397 | } 1398 | } 1399 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 1400 | foundIndex != 2) 1401 | { 1402 | std::cout << "Eraser test failed\n"; 1403 | return false; 1404 | } 1405 | } 1406 | 1407 | { 1408 | size_t foundIndex = 0; 1409 | T eraserTest = { 1,2,3,4 }; 1410 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1411 | { 1412 | if (*val == 4) 1413 | { 1414 | foundIndex = val.index(); 1415 | break; 1416 | } 1417 | } 1418 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4 }) || 1419 | foundIndex != 3) 1420 | { 1421 | std::cout << "Eraser test failed\n"; 1422 | return false; 1423 | } 1424 | } 1425 | 1426 | 1427 | //// 1428 | 1429 | { 1430 | size_t foundIndex = 0; 1431 | T eraserTest = { 1,2,3,4 }; 1432 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1433 | { 1434 | if (*val == 1) 1435 | { 1436 | val.mark_for_erase(); 1437 | foundIndex = val.index(); 1438 | } 1439 | } 1440 | if (!IsSame(eraserTest, T{ 2, 3, 4 }) || 1441 | foundIndex != 0) 1442 | { 1443 | std::cout << "Eraser test failed\n"; 1444 | return false; 1445 | } 1446 | } 1447 | { 1448 | size_t foundIndex = 0; 1449 | T eraserTest = { 1,2,3,4 }; 1450 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1451 | { 1452 | if (*val == 2) 1453 | { 1454 | val.mark_for_erase(); 1455 | foundIndex = val.index(); 1456 | } 1457 | } 1458 | if (!IsSame(eraserTest, T{ 1, 3, 4 }) || 1459 | foundIndex != 1) 1460 | { 1461 | std::cout << "Eraser test failed\n"; 1462 | return false; 1463 | } 1464 | } 1465 | 1466 | { 1467 | size_t foundIndex = 0; 1468 | T eraserTest = { 1,2,3,4 }; 1469 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1470 | { 1471 | if (*val == 3) 1472 | { 1473 | val.mark_for_erase(); 1474 | foundIndex = val.index(); 1475 | } 1476 | } 1477 | if (!IsSame(eraserTest, T{ 1, 2, 4 }) || 1478 | foundIndex != 2) 1479 | { 1480 | std::cout << "Eraser test failed\n"; 1481 | return false; 1482 | } 1483 | } 1484 | { 1485 | size_t foundIndex = 0; 1486 | T eraserTest = { 1,2,3,4 }; 1487 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1488 | { 1489 | if (*val == 4) 1490 | { 1491 | val.mark_for_erase(); 1492 | foundIndex = val.index(); 1493 | } 1494 | } 1495 | if (!IsSame(eraserTest, T{ 1, 2, 3 }) || 1496 | foundIndex != 3) 1497 | { 1498 | std::cout << "Eraser test failed\n"; 1499 | return false; 1500 | } 1501 | } 1502 | 1503 | // Eraser break early tests 1504 | { 1505 | size_t foundIndex = 0; 1506 | T eraserTest = { 1,2,3,4 }; 1507 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1508 | { 1509 | if (*val == 1) 1510 | { 1511 | val.mark_for_erase(); 1512 | foundIndex = val.index(); 1513 | break; 1514 | } 1515 | } 1516 | if (!IsSame(eraserTest, T{ 2, 3, 4 }) || 1517 | foundIndex != 0) 1518 | { 1519 | std::cout << "Eraser test failed\n"; 1520 | return false; 1521 | } 1522 | } 1523 | { 1524 | size_t foundIndex = 0; 1525 | T eraserTest = { 1,2,3,4 }; 1526 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1527 | { 1528 | if (*val == 2) 1529 | { 1530 | val.mark_for_erase(); 1531 | foundIndex = val.index(); 1532 | break; 1533 | } 1534 | } 1535 | if (!IsSame(eraserTest, T{ 1, 3, 4 }) || 1536 | foundIndex != 1) 1537 | { 1538 | std::cout << "Eraser test failed\n"; 1539 | return false; 1540 | } 1541 | } 1542 | 1543 | { 1544 | size_t foundIndex = 0; 1545 | T eraserTest = { 1,2,3,4 }; 1546 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1547 | { 1548 | if (*val == 3) 1549 | { 1550 | val.mark_for_erase(); 1551 | foundIndex = val.index(); 1552 | break; 1553 | } 1554 | } 1555 | if (!IsSame(eraserTest, T{ 1, 2, 4 }) || 1556 | foundIndex != 2) 1557 | { 1558 | std::cout << "Eraser test failed\n"; 1559 | return false; 1560 | } 1561 | } 1562 | { 1563 | size_t foundIndex = 0; 1564 | T eraserTest = { 1,2,3,4 }; 1565 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1566 | { 1567 | if (*val == 4) 1568 | { 1569 | val.mark_for_erase(); 1570 | foundIndex = val.index(); 1571 | break; 1572 | } 1573 | } 1574 | if (!IsSame(eraserTest, T{ 1, 2, 3 }) || 1575 | foundIndex != 3) 1576 | { 1577 | std::cout << "Eraser test failed\n"; 1578 | return false; 1579 | } 1580 | } 1581 | 1582 | { 1583 | T eraserTest = { 1,2,3,4 }; 1584 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1585 | { 1586 | if (*val == 4) 1587 | { 1588 | break; 1589 | } 1590 | else 1591 | { 1592 | val.mark_for_erase(); 1593 | } 1594 | } 1595 | if (!IsSame(eraserTest, T{ 4 })) 1596 | { 1597 | std::cout << "Eraser test failed\n"; 1598 | return false; 1599 | } 1600 | } 1601 | 1602 | // Appending eraser tests 1603 | { 1604 | T eraserTest = { 1,2,3,4 }; 1605 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1606 | { 1607 | *val; 1608 | eraserTest.push_back(5); 1609 | } 1610 | if (!IsSame(eraserTest, T{ 1, 2, 3, 4, 5, 5, 5, 5})) 1611 | { 1612 | std::cout << "Eraser test failed\n"; 1613 | return false; 1614 | } 1615 | } 1616 | 1617 | 1618 | //// 1619 | 1620 | { 1621 | size_t foundIndex = 0; 1622 | T eraserTest = { 1,2,3,4 }; 1623 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1624 | { 1625 | if (*val == 1) 1626 | { 1627 | val.mark_for_erase(); 1628 | foundIndex = val.index(); 1629 | } 1630 | eraserTest.push_back(5); 1631 | } 1632 | if (!IsSame(eraserTest, T{ 2, 3, 4, 5, 5, 5, 5 }) || 1633 | foundIndex != 0) 1634 | { 1635 | std::cout << "Eraser test failed\n"; 1636 | return false; 1637 | } 1638 | } 1639 | { 1640 | size_t foundIndex = 0; 1641 | T eraserTest = { 1,2,3,4 }; 1642 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1643 | { 1644 | if (*val == 2) 1645 | { 1646 | val.mark_for_erase(); 1647 | foundIndex = val.index(); 1648 | } 1649 | eraserTest.push_back(5); 1650 | } 1651 | if (!IsSame(eraserTest, T{ 1, 3, 4, 5, 5, 5, 5 }) || 1652 | foundIndex != 1) 1653 | { 1654 | std::cout << "Eraser test failed\n"; 1655 | return false; 1656 | } 1657 | } 1658 | 1659 | { 1660 | size_t foundIndex = 0; 1661 | T eraserTest = { 1,2,3,4 }; 1662 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1663 | { 1664 | if (*val == 3) 1665 | { 1666 | val.mark_for_erase(); 1667 | foundIndex = val.index(); 1668 | } 1669 | eraserTest.push_back(5); 1670 | } 1671 | if (!IsSame(eraserTest, T{ 1, 2, 4, 5, 5, 5, 5 }) || 1672 | foundIndex != 2) 1673 | { 1674 | std::cout << "Eraser test failed\n"; 1675 | return false; 1676 | } 1677 | } 1678 | { 1679 | size_t foundIndex = 0; 1680 | T eraserTest = { 1,2,3,4 }; 1681 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1682 | { 1683 | if (*val == 4) 1684 | { 1685 | val.mark_for_erase(); 1686 | foundIndex = val.index(); 1687 | } 1688 | eraserTest.push_back(5); 1689 | } 1690 | if (!IsSame(eraserTest, T{ 1, 2, 3, 5, 5, 5, 5 }) || 1691 | foundIndex != 3) 1692 | { 1693 | std::cout << "Eraser test failed\n"; 1694 | return false; 1695 | } 1696 | } 1697 | 1698 | // Eraser break early tests 1699 | { 1700 | size_t foundIndex = 0; 1701 | T eraserTest = { 1,2,3,4 }; 1702 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1703 | { 1704 | if (*val == 1) 1705 | { 1706 | val.mark_for_erase(); 1707 | foundIndex = val.index(); 1708 | break; 1709 | } 1710 | eraserTest.push_back(5); 1711 | } 1712 | if (!IsSame(eraserTest, T{ 2, 3, 4 }) || 1713 | foundIndex != 0) 1714 | { 1715 | std::cout << "Eraser test failed\n"; 1716 | return false; 1717 | } 1718 | } 1719 | { 1720 | size_t foundIndex = 0; 1721 | T eraserTest = { 1,2,3,4 }; 1722 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1723 | { 1724 | if (*val == 2) 1725 | { 1726 | val.mark_for_erase(); 1727 | foundIndex = val.index(); 1728 | break; 1729 | } 1730 | eraserTest.push_back(5); 1731 | } 1732 | if (!IsSame(eraserTest, T{ 1, 3, 4, 5,}) || 1733 | foundIndex != 1) 1734 | { 1735 | std::cout << "Eraser test failed\n"; 1736 | return false; 1737 | } 1738 | } 1739 | 1740 | { 1741 | size_t foundIndex = 0; 1742 | T eraserTest = { 1,2,3,4 }; 1743 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1744 | { 1745 | if (*val == 3) 1746 | { 1747 | val.mark_for_erase(); 1748 | foundIndex = val.index(); 1749 | break; 1750 | } 1751 | eraserTest.push_back(5); 1752 | } 1753 | if (!IsSame(eraserTest, T{ 1, 2, 4, 5, 5 }) || 1754 | foundIndex != 2) 1755 | { 1756 | std::cout << "Eraser test failed\n"; 1757 | return false; 1758 | } 1759 | } 1760 | { 1761 | size_t foundIndex = 0; 1762 | T eraserTest = { 1,2,3,4 }; 1763 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1764 | { 1765 | if (*val == 4) 1766 | { 1767 | val.mark_for_erase(); 1768 | foundIndex = val.index(); 1769 | break; 1770 | } 1771 | eraserTest.push_back(5); 1772 | } 1773 | if (!IsSame(eraserTest, T{ 1, 2, 3, 5, 5, 5 }) || 1774 | foundIndex != 3) 1775 | { 1776 | std::cout << "Eraser test failed\n"; 1777 | return false; 1778 | } 1779 | } 1780 | 1781 | { 1782 | T eraserTest = { 1,2,3,4 }; 1783 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1784 | { 1785 | if (*val == 4) 1786 | { 1787 | break; 1788 | } 1789 | else 1790 | { 1791 | val.mark_for_erase(); 1792 | } 1793 | eraserTest.push_back(5); 1794 | } 1795 | if (!IsSame(eraserTest, T{ 4, 5, 5, 5 })) 1796 | { 1797 | std::cout << "Eraser test failed\n"; 1798 | return false; 1799 | } 1800 | } 1801 | 1802 | return true; 1803 | } 1804 | 1805 | 1806 | template 1807 | bool TypeAppendStringTests() 1808 | { 1809 | // Eraser tests 1810 | { 1811 | size_t foundIndex = 0; 1812 | T eraserTest = { "1","2","3","4" }; 1813 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1814 | { 1815 | if (*val == "1") 1816 | { 1817 | val.mark_for_erase(); 1818 | foundIndex = val.index(); 1819 | } 1820 | } 1821 | if (!IsSame(eraserTest, T{ "2", "3", "4" }) || 1822 | foundIndex != 0) 1823 | { 1824 | std::cout << "Eraser test failed\n"; 1825 | return false; 1826 | } 1827 | } 1828 | { 1829 | size_t foundIndex = 0; 1830 | T eraserTest = { "1","2","3","4" }; 1831 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1832 | { 1833 | if (*val == "2") 1834 | { 1835 | val.mark_for_erase(); 1836 | foundIndex = val.index(); 1837 | } 1838 | } 1839 | if (!IsSame(eraserTest, T{ "1", "3", "4" }) || 1840 | foundIndex != 1) 1841 | { 1842 | std::cout << "Eraser test failed\n"; 1843 | return false; 1844 | } 1845 | } 1846 | 1847 | { 1848 | size_t foundIndex = 0; 1849 | T eraserTest = { "1","2","3","4" }; 1850 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1851 | { 1852 | if (*val == "3") 1853 | { 1854 | val.mark_for_erase(); 1855 | foundIndex = val.index(); 1856 | } 1857 | } 1858 | if (!IsSame(eraserTest, T{ "1", "2", "4" }) || 1859 | foundIndex != 2) 1860 | { 1861 | std::cout << "Eraser test failed\n"; 1862 | return false; 1863 | } 1864 | } 1865 | { 1866 | size_t foundIndex = 0; 1867 | T eraserTest = { "1","2","3","4" }; 1868 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1869 | { 1870 | if (*val == "4") 1871 | { 1872 | val.mark_for_erase(); 1873 | foundIndex = val.index(); 1874 | } 1875 | } 1876 | if (!IsSame(eraserTest, T{ "1", "2", "3" }) || 1877 | foundIndex != 3) 1878 | { 1879 | std::cout << "Eraser test failed\n"; 1880 | return false; 1881 | } 1882 | } 1883 | 1884 | // Eraser break early tests 1885 | { 1886 | size_t foundIndex = 0; 1887 | T eraserTest = { "1","2","3","4" }; 1888 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1889 | { 1890 | if (*val == "1") 1891 | { 1892 | val.mark_for_erase(); 1893 | foundIndex = val.index(); 1894 | break; 1895 | } 1896 | } 1897 | if (!IsSame(eraserTest, T{ "2", "3", "4" }) || 1898 | foundIndex != 0) 1899 | { 1900 | std::cout << "Eraser test failed\n"; 1901 | return false; 1902 | } 1903 | } 1904 | { 1905 | size_t foundIndex = 0; 1906 | T eraserTest = { "1","2","3","4" }; 1907 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1908 | { 1909 | if (*val == "2") 1910 | { 1911 | val.mark_for_erase(); 1912 | foundIndex = val.index(); 1913 | break; 1914 | } 1915 | } 1916 | if (!IsSame(eraserTest, T{ "1", "3", "4" }) || 1917 | foundIndex != 1) 1918 | { 1919 | std::cout << "Eraser test failed\n"; 1920 | return false; 1921 | } 1922 | } 1923 | 1924 | { 1925 | size_t foundIndex = 0; 1926 | T eraserTest = { "1","2","3","4" }; 1927 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1928 | { 1929 | if (*val == "3") 1930 | { 1931 | val.mark_for_erase(); 1932 | foundIndex = val.index(); 1933 | break; 1934 | } 1935 | } 1936 | if (!IsSame(eraserTest, T{ "1", "2", "4" }) || 1937 | foundIndex != 2) 1938 | { 1939 | std::cout << "Eraser test failed\n"; 1940 | return false; 1941 | } 1942 | } 1943 | { 1944 | size_t foundIndex = 0; 1945 | T eraserTest = { "1","2","3","4" }; 1946 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1947 | { 1948 | if (*val == "4") 1949 | { 1950 | val.mark_for_erase(); 1951 | foundIndex = val.index(); 1952 | break; 1953 | } 1954 | } 1955 | if (!IsSame(eraserTest, T{ "1", "2", "3" }) || 1956 | foundIndex != 3) 1957 | { 1958 | std::cout << "Eraser test failed\n"; 1959 | return false; 1960 | } 1961 | } 1962 | 1963 | { 1964 | T eraserTest = { "1","2","3","4" }; 1965 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1966 | { 1967 | if (*val == "4") 1968 | { 1969 | break; 1970 | } 1971 | else 1972 | { 1973 | val.mark_for_erase(); 1974 | } 1975 | } 1976 | if (!IsSame(eraserTest, T{ "4" })) 1977 | { 1978 | std::cout << "Eraser test failed\n"; 1979 | return false; 1980 | } 1981 | } 1982 | 1983 | // Eraser tests with append 1984 | { 1985 | size_t foundIndex = 0; 1986 | T eraserTest = { "1","2","3","4" }; 1987 | for (auto& val : iter::eraser_safe_append(eraserTest)) 1988 | { 1989 | if (*val == "1") 1990 | { 1991 | val.mark_for_erase(); 1992 | foundIndex = val.index(); 1993 | } 1994 | eraserTest.push_back("5"); 1995 | } 1996 | if (!IsSame(eraserTest, T{ "2", "3", "4", "5", "5", "5", "5" }) || 1997 | foundIndex != 0) 1998 | { 1999 | std::cout << "Eraser test failed\n"; 2000 | return false; 2001 | } 2002 | } 2003 | { 2004 | size_t foundIndex = 0; 2005 | T eraserTest = { "1","2","3","4" }; 2006 | for (auto& val : iter::eraser_safe_append(eraserTest)) 2007 | { 2008 | if (*val == "2") 2009 | { 2010 | val.mark_for_erase(); 2011 | foundIndex = val.index(); 2012 | } 2013 | eraserTest.push_back("5"); 2014 | } 2015 | if (!IsSame(eraserTest, T{ "1", "3", "4", "5", "5", "5", "5" }) || 2016 | foundIndex != 1) 2017 | { 2018 | std::cout << "Eraser test failed\n"; 2019 | return false; 2020 | } 2021 | } 2022 | 2023 | { 2024 | size_t foundIndex = 0; 2025 | T eraserTest = { "1","2","3","4" }; 2026 | for (auto& val : iter::eraser_safe_append(eraserTest)) 2027 | { 2028 | if (*val == "3") 2029 | { 2030 | val.mark_for_erase(); 2031 | foundIndex = val.index(); 2032 | } 2033 | eraserTest.push_back("5"); 2034 | } 2035 | if (!IsSame(eraserTest, T{ "1", "2", "4", "5", "5", "5", "5" }) || 2036 | foundIndex != 2) 2037 | { 2038 | std::cout << "Eraser test failed\n"; 2039 | return false; 2040 | } 2041 | } 2042 | { 2043 | size_t foundIndex = 0; 2044 | T eraserTest = { "1","2","3","4" }; 2045 | for (auto& val : iter::eraser_safe_append(eraserTest)) 2046 | { 2047 | if (*val == "4") 2048 | { 2049 | val.mark_for_erase(); 2050 | foundIndex = val.index(); 2051 | } 2052 | eraserTest.push_back("5"); 2053 | } 2054 | if (!IsSame(eraserTest, T{ "1", "2", "3", "5", "5", "5", "5" }) || 2055 | foundIndex != 3) 2056 | { 2057 | std::cout << "Eraser test failed\n"; 2058 | return false; 2059 | } 2060 | } 2061 | 2062 | // Eraser break early tests 2063 | { 2064 | size_t foundIndex = 0; 2065 | T eraserTest = { "1","2","3","4" }; 2066 | for (auto& val : iter::eraser_safe_append(eraserTest)) 2067 | { 2068 | if (*val == "1") 2069 | { 2070 | val.mark_for_erase(); 2071 | foundIndex = val.index(); 2072 | break; 2073 | } 2074 | eraserTest.push_back("5"); 2075 | } 2076 | if (!IsSame(eraserTest, T{ "2", "3", "4" }) || 2077 | foundIndex != 0) 2078 | { 2079 | std::cout << "Eraser test failed\n"; 2080 | return false; 2081 | } 2082 | } 2083 | { 2084 | size_t foundIndex = 0; 2085 | T eraserTest = { "1","2","3","4" }; 2086 | for (auto& val : iter::eraser_safe_append(eraserTest)) 2087 | { 2088 | if (*val == "2") 2089 | { 2090 | val.mark_for_erase(); 2091 | foundIndex = val.index(); 2092 | break; 2093 | } 2094 | eraserTest.push_back("5"); 2095 | } 2096 | if (!IsSame(eraserTest, T{ "1", "3", "4", "5" }) || 2097 | foundIndex != 1) 2098 | { 2099 | std::cout << "Eraser test failed\n"; 2100 | return false; 2101 | } 2102 | } 2103 | 2104 | { 2105 | size_t foundIndex = 0; 2106 | T eraserTest = { "1","2","3","4" }; 2107 | for (auto& val : iter::eraser_safe_append(eraserTest)) 2108 | { 2109 | if (*val == "3") 2110 | { 2111 | val.mark_for_erase(); 2112 | foundIndex = val.index(); 2113 | break; 2114 | } 2115 | eraserTest.push_back("5"); 2116 | } 2117 | if (!IsSame(eraserTest, T{ "1", "2", "4", "5", "5" }) || 2118 | foundIndex != 2) 2119 | { 2120 | std::cout << "Eraser test failed\n"; 2121 | return false; 2122 | } 2123 | } 2124 | { 2125 | size_t foundIndex = 0; 2126 | T eraserTest = { "1","2","3","4" }; 2127 | for (auto& val : iter::eraser_safe_append(eraserTest)) 2128 | { 2129 | if (*val == "4") 2130 | { 2131 | val.mark_for_erase(); 2132 | foundIndex = val.index(); 2133 | break; 2134 | } 2135 | eraserTest.push_back("5"); 2136 | } 2137 | if (!IsSame(eraserTest, T{ "1", "2", "3", "5", "5", "5" }) || 2138 | foundIndex != 3) 2139 | { 2140 | std::cout << "Eraser test failed\n"; 2141 | return false; 2142 | } 2143 | } 2144 | 2145 | { 2146 | T eraserTest = { "1","2","3","4" }; 2147 | for (auto& val : iter::eraser_safe_append(eraserTest)) 2148 | { 2149 | if (*val == "4") 2150 | { 2151 | break; 2152 | } 2153 | else 2154 | { 2155 | val.mark_for_erase(); 2156 | } 2157 | eraserTest.push_back("5"); 2158 | } 2159 | if (!IsSame(eraserTest, T{ "4", "5", "5", "5" })) 2160 | { 2161 | std::cout << "Eraser test failed\n"; 2162 | return false; 2163 | } 2164 | } 2165 | 2166 | // Large append test 2167 | { 2168 | T eraserTest = { "1","2","3","4" }; 2169 | for (auto& val : iter::eraser_safe_append(eraserTest)) 2170 | { 2171 | if (*val == "4") 2172 | { 2173 | break; 2174 | } 2175 | else 2176 | { 2177 | val.mark_for_erase(); 2178 | } 2179 | for (int i = 0; i < 10; i++) 2180 | { 2181 | eraserTest.push_back("5"); 2182 | } 2183 | } 2184 | if (!IsSame(eraserTest, T{ "4", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5" })) 2185 | { 2186 | std::cout << "Eraser test failed\n"; 2187 | return false; 2188 | } 2189 | } 2190 | 2191 | return true; 2192 | } 2193 | 2194 | 2195 | bool Iterator_UnitTests() 2196 | { 2197 | // Reverse and counter tests 2198 | if (!ReverseTests() || 2199 | !CounterTests()) 2200 | { 2201 | return false; 2202 | } 2203 | 2204 | // Eraser type Tests 2205 | if (!TypeIntTests>() || 2206 | !TypeIntTests>()) 2207 | { 2208 | return false; 2209 | } 2210 | 2211 | if (!TypeStringTests>() || 2212 | !TypeStringTests>()) 2213 | { 2214 | return false; 2215 | } 2216 | 2217 | // Indexer tests 2218 | if (!IndexerStringTests>() || 2219 | !IndexerStringTests>()) 2220 | { 2221 | return false; 2222 | } 2223 | 2224 | // Eraser append type Tests 2225 | if (!TypeAppendIntTests>()) 2226 | { 2227 | return false; 2228 | } 2229 | 2230 | if (!TypeAppendStringTests>()) 2231 | { 2232 | return false; 2233 | } 2234 | 2235 | return true; 2236 | } 2237 | 2238 | -------------------------------------------------------------------------------- /Tests/Iterator_old.h: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Copyright (C) 2016 Damian Trebilco 3 | // Licensed under the MIT license - See LICENSE.txt for details. 4 | //============================================================================= 5 | 6 | // This is the original version written, but was discarded based on profiling it's performance. 7 | 8 | #include 9 | 10 | /// \brief Helper methods/structures when using for-range iteration 11 | namespace iter 12 | { 13 | template 14 | struct eraser_wrapper 15 | { 16 | using IterType = decltype(std::declval().begin()); 17 | using ValueRef = decltype(std::declval().operator*()); 18 | using ValueType = typename std::remove_reference::type; 19 | 20 | struct Value 21 | { 22 | protected: 23 | eraser_wrapper& m_parent; //!< The parent which holds the iteration data 24 | 25 | Value(eraser_wrapper& a_parent) : m_parent(a_parent) {} 26 | 27 | public: 28 | /// \brief Get the value from the container element 29 | inline ValueType& value() { return *m_parent.m_current; } 30 | inline const ValueType& value() const { return *m_parent.m_current; } 31 | 32 | /// \brief Get the value from the container element 33 | inline ValueType& operator *() { return *m_parent.m_current; } 34 | inline const ValueType& operator *() const { return *m_parent.m_current; } 35 | 36 | /// \brief Get the value from the container element 37 | inline ValueType* operator ->() { return &*m_parent.m_current; } 38 | inline const ValueType* operator ->() const { return &*m_parent.m_current; } 39 | 40 | /// \brief Mark the current item to be erased from the parent container at a later stage. 41 | /// Can be called multiple times and value will still be valid until the next iteration. 42 | inline void mark_for_erase() const { m_parent.m_markRemove = true; } 43 | 44 | /// \brief Get the index of the this value in the parent container. Item may be shifted later if previous elements are erased. 45 | inline size_t index() const { return std::distance(m_parent.m_data.begin(), m_parent.m_current); } 46 | }; 47 | 48 | struct Iterator : public Value 49 | { 50 | Iterator(eraser_wrapper& a_parent) : Value(a_parent) {} 51 | 52 | inline Iterator& operator++() 53 | { 54 | auto& parent = Value::m_parent; 55 | if (parent.m_markRemove) 56 | { 57 | parent.m_markRemove = false; 58 | } 59 | else 60 | { 61 | // Move the existing value to the new position 62 | if (std::is_pod::value || 63 | parent.m_eraseStart != parent.m_current) 64 | { 65 | *parent.m_eraseStart = std::move(*parent.m_current); 66 | } 67 | ++parent.m_eraseStart; 68 | } 69 | 70 | ++parent.m_current; 71 | return *this; 72 | } 73 | 74 | inline bool operator != (const Iterator&) const { return Value::m_parent.m_current != Value::m_parent.m_end; } 75 | inline Value& operator *() { return *this; } 76 | }; 77 | 78 | inline eraser_wrapper(T& a_data) : m_data(a_data) 79 | { 80 | m_current = m_data.begin(); 81 | m_end = m_data.end(); 82 | m_eraseStart = m_current; 83 | } 84 | 85 | inline ~eraser_wrapper() 86 | { 87 | // If aborted mid iteration (via break), still remove if flagged 88 | if (m_markRemove) 89 | { 90 | ++m_current; 91 | } 92 | 93 | m_data.erase(m_eraseStart, m_current); 94 | } 95 | 96 | inline Iterator begin() { return Iterator(*this); } 97 | inline Iterator end() { return Iterator(*this); } 98 | 99 | private: 100 | 101 | T& m_data; //!< The data structure being processed 102 | bool m_markRemove = false; //!< If the item pointed to by m_current is to be removed 103 | 104 | IterType m_current; //!< The current iterator position 105 | IterType m_eraseStart; //!< The erase start position 106 | IterType m_end; //!< The end marker 107 | 108 | }; 109 | 110 | /// \brief A iterator modifier that allows elements to be erased from the underlying type during iteration. 111 | /// Order of elements in the container type is preserved during deletion. Deletion of elements will occur in order 112 | /// and by the exit of the iteration, but may be delayed. 113 | /// Container type must support an erase() method similar to std::vector::erase() 114 | /// 115 | /// Usage: for(auto& value : iter::eraser(vector)) 116 | /// { 117 | /// if(someCondition == *value) 118 | /// { 119 | /// value.mark_for_erase(); // Item will still be valid for remainer of iteration and will be erased in a future iteration 120 | /// 121 | /// Note that the value returned is a smart pointer that needs to be de-referenced to access the value 122 | /// (either with *value or value-> ) 123 | /// The index of the item in the array can also be retrieved by calling value.index(). This may not be optimal on some container types. 124 | /// 125 | /// IMPORTANT: Do not store a pointer to any data inside the array being iterated on outside the for-loop scope - data may be moved between each iteration. 126 | /// See iter::unordered_eraser() for a relaxation of this rule 127 | /// Eg. Do not do this: 128 | /// Type* foundItem = nullptr; 129 | /// for(auto& value : iter::eraser(vector)) 130 | /// { 131 | /// ///.. erase value code ... 132 | /// foundItem = &*value; 133 | /// } 134 | /// foundItem->blah; // Data may not be valid 135 | /// 136 | template 137 | eraser_wrapper eraser(T& v) 138 | { 139 | return eraser_wrapper(v); 140 | } 141 | 142 | template 143 | struct unordered_eraser_wrapper 144 | { 145 | using IterType = decltype(std::declval().begin()); 146 | using ValueRef = decltype(std::declval().operator*()); 147 | using ValueType = typename std::remove_reference::type; 148 | 149 | struct Value 150 | { 151 | protected: 152 | unordered_eraser_wrapper& m_parent; //!< The parent which holds the iteration data 153 | 154 | Value(unordered_eraser_wrapper& a_parent) : m_parent(a_parent) {} 155 | 156 | public: 157 | /// \brief Get the value from the container element 158 | inline ValueType& value() { return *m_parent.m_current; } 159 | inline const ValueType& value() const { return *m_parent.m_current; } 160 | 161 | /// \brief Get the value from the container element 162 | inline ValueType& operator *() { return *m_parent.m_current; } 163 | inline const ValueType& operator *() const { return *m_parent.m_current; } 164 | 165 | /// \brief Get the value from the container element 166 | inline ValueType* operator ->() { return &*m_parent.m_current; } 167 | inline const ValueType* operator ->() const { return &*m_parent.m_current; } 168 | 169 | /// \brief Mark the current item to be erased from the parent container at a later stage. 170 | /// Can be called multiple times and value will still be valid until the next iteration. 171 | inline void mark_for_erase() const { m_parent.m_markRemove = true; } 172 | 173 | /// \brief Get the index of the loop counter - useful for debugging 174 | inline size_t loop_index() const { return std::distance(m_parent.m_data.begin(), m_parent.m_current) + 175 | std::distance(m_parent.m_eraseStart, m_parent.m_end); } 176 | }; 177 | 178 | struct Iterator : public Value 179 | { 180 | Iterator(unordered_eraser_wrapper& a_parent) : Value(a_parent) {} 181 | 182 | inline Iterator& operator++() 183 | { 184 | auto& parent = Value::m_parent; 185 | // If removing - swap with last 186 | if (parent.m_markRemove) 187 | { 188 | parent.m_markRemove = false; 189 | --parent.m_eraseStart; 190 | if (std::is_pod::value || 191 | parent.m_current != parent.m_eraseStart) 192 | { 193 | *parent.m_current = std::move(*parent.m_eraseStart); 194 | } 195 | } 196 | else 197 | { 198 | ++parent.m_current; 199 | } 200 | 201 | return *this; 202 | } 203 | 204 | inline bool operator != (const Iterator&) const { return Value::m_parent.m_current != Value::m_parent.m_eraseStart; } 205 | inline Value& operator *() { return *this; } 206 | }; 207 | 208 | 209 | inline unordered_eraser_wrapper(T& a_data) : m_data(a_data) 210 | { 211 | m_current = m_data.begin(); 212 | m_end = m_data.end(); 213 | m_eraseStart = m_end; 214 | } 215 | 216 | inline ~unordered_eraser_wrapper() 217 | { 218 | // If aborted mid iteration (via break), still remove if flagged 219 | if (m_markRemove) 220 | { 221 | --m_eraseStart; 222 | if (std::is_pod::value || 223 | m_current != m_eraseStart) 224 | { 225 | *m_current = std::move(*m_eraseStart); 226 | } 227 | } 228 | 229 | m_data.erase(m_eraseStart, m_end); 230 | } 231 | 232 | inline Iterator begin() { return Iterator(*this); } 233 | inline Iterator end() { return Iterator(*this); } 234 | 235 | private: 236 | 237 | T& m_data; //!< The data structure being processed 238 | bool m_markRemove = false; //!< If the item pointed to by m_current is to be removed 239 | 240 | IterType m_current; //!< The current iterator position 241 | IterType m_eraseStart; //!< The erase start position 242 | IterType m_end; //!< The end marker 243 | 244 | }; 245 | 246 | /// \brief A iterator modifier that allows elements to be erased from the underlying type during iteration. 247 | /// Order of elements in the container type is NOT preserved during deletion. Deletion of elements will occur in order 248 | /// and by the exit of the iteration, but may be delayed. 249 | /// Container type must support an erase() method similar to std::vector::erase() 250 | /// 251 | /// Usage: for(auto& value : iter::unordered_eraser(vector)) 252 | /// { 253 | /// if(someCondition == *value) 254 | /// { 255 | /// value.mark_for_erase(); // Item will still be valid for remainer of iteration and will be erased in a future iteration 256 | /// 257 | /// Note that the value returned is a smart pointer that needs to be de-referenced to access the value 258 | /// (either with *value or value-> ) 259 | /// 260 | /// IMPORTANT: Do not store a pointer to deleted data inside the array being iterated on outside the for-loop scope - data may be moved between each iteration. 261 | /// Unlike iter::eraser(), it is valid (but not recommended) to store a pointer to data that is not going to be erased. 262 | /// Eg. Do not do this: 263 | /// Type* foundItem = nullptr; 264 | /// for(auto& value : iter::eraser(vector)) 265 | /// { 266 | /// value.mark_for_delete(); 267 | /// foundItem = &*value; 268 | /// } 269 | /// foundItem->blah; // Data is not valid 270 | /// 271 | template 272 | unordered_eraser_wrapper unordered_eraser(T& v) 273 | { 274 | return unordered_eraser_wrapper(v); 275 | } 276 | 277 | } 278 | 279 | 280 | --------------------------------------------------------------------------------