├── main ├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── main.cpp └── circularQueue.hpp /main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteveZhangSZ/ConstexprCircularQueue/HEAD/main -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteveZhangSZ/ConstexprCircularQueue/HEAD/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | *.cpp 3 | *.dSYM 4 | /main.dSYM/ 5 | /mainTwo.dSYM/ 6 | /mainThree.dSYM/ 7 | /main* 8 | randomStuff.txt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Steve Zhang 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Single Header Constexpr Circular Queue 2 | The max capacity must be known at compile time. This requires C++17. 3 | 4 | ## Getting Started 5 | Simply `#include "circularQueue.hpp"` into the file that you want to use this queue. 6 | 7 | ### More Info About The Circular Queue 8 | It works for any object of type T where both `std::is_trivially_destructible::value` and 9 | `std::is_trivially_copy_assignable::value` are true, because in C++17, the destructor must not be user defined 10 | and changing the active member of the union in a constexpr context can only be done through 11 | defaulted copy assignment. Said assignment only avoids deletion if T is trivially copy assignable. 12 | 13 | This queue is composed of a C style array of unions. The union has two members: an empty class and a variable 14 | whose type depends on the template argument, class T. 15 | 16 | You can change the template parameter Idxtype to another integral type, like short, if you want to save space. 17 | ``` 18 | template union Cell; //bool B == std::is_trivially_destructible::value 19 | template 20 | union Cell{ 21 | class emptyClass{} forConstexprCtor; 22 | T value; 23 | //Initializes forConstexprCtor because constexpr union constructors must initialize a member 24 | constexpr Cell() : forConstexprCtor{} {} 25 | //Initializes value with the provided parameter arguments 26 | template 27 | constexpr Cell(Args&&... args) : value((args)...) {} 28 | }; 29 | template 30 | union Cell{ 31 | class emptyClass{} forConstexprCtor; 32 | T value; 33 | constexpr Cell() : forConstexprCtor{} {} 34 | template 35 | constexpr Cell(Args&&... args) : value((args)...) {} 36 | ~Cell(){} //Included because Cell's destructor is deleted 37 | }; 38 | ``` 39 | These unions ensure that the storage for the objects of type T will have proper alignment and size. 40 | 41 | The entire queue allocates on the stack so it should be faster when you know the size at compile time and are 42 | working with a small amount of elements. 43 | 44 | ### Example Usage 45 | ``` 46 | #include "circularQueue.hpp" 47 | int main(){ 48 | circularQueue theStringCQ("First", "Second", "Third"); 49 | constexpr circularQueue theIntCQ(0, 1, 2); 50 | circularQueue onHeap(new std::string("Heap one")); 51 | delete onHeap.front(); 52 | onHeap.pop(); 53 | } 54 | ``` -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "circularQueue.hpp" 4 | void testCQ(){ 5 | std::cout << "Begin testCQ()\n"; 6 | circularQueue, 3> thePairCQ(std::pair{44,44}), secondPairCQ; 7 | thePairCQ.push({12,34}); 8 | thePairCQ.emplace(98,10); 9 | thePairCQ.pop(); 10 | circularQueue, 3> thirdPairCQ(thePairCQ); 11 | secondPairCQ = thePairCQ; //L value assignment with non trivial copy assignment 12 | secondPairCQ = std::move(thirdPairCQ); //R value assignment with non trivial copy assignment 13 | circularQueue theCharCQ('A','B'); 14 | std::cout << std::boolalpha << theCharCQ.empty() << '\n'; 15 | circularQueue secondCharCQ(theCharCQ); //copy by l value, trivially copyable type 16 | 17 | //copy by r value, trivially copyable type 18 | [[maybe_unused]] circularQueue thirdCharCQ(std::move(secondCharCQ)); 19 | theCharCQ.push('C'); 20 | circularQueue theStringCQ, secondStringCQ("Rhubarb"); 21 | theStringCQ.push("Hello"); 22 | theStringCQ.pop(); 23 | theStringCQ = secondStringCQ; //L value assignment with non trivial copy assignment 24 | 25 | //copy by l value, non trivially copyable type 26 | circularQueue copyStringCQ(secondStringCQ),thirdStringCQ; 27 | thirdStringCQ = copyStringCQ; 28 | thirdStringCQ = circularQueue("Something here"); 29 | [[maybe_unused]] const circularQueue emptyQueue; 30 | circularQueue, 2> theVecCQ; 31 | theVecCQ.emplace(4,4); 32 | std::cout << "theVecCQ.front().size() is " << theVecCQ.front().size() << '\n'; 33 | theVecCQ.pop(); 34 | theVecCQ.push({4,4}); 35 | std::cout << "theVecCQ.front().size() is " << theVecCQ.front().size() << '\n'; 36 | circularQueue onHeap(new std::string("Heap one")); 37 | delete onHeap.front(); 38 | onHeap.pop(); 39 | } 40 | 41 | void printCQ(){ 42 | std::cout << "Begin printCQ\n"; 43 | circularQueue theStringCQ("First"); 44 | for(theStringCQ.push("Second"), theStringCQ.emplace("Third"); 45 | !theStringCQ.empty(); theStringCQ.pop()){ 46 | std::cout << theStringCQ.front() << '\n'; 47 | } 48 | theStringCQ.emplace("Fourth"); 49 | std::cout << theStringCQ.back() << '\n'; 50 | 51 | } 52 | 53 | void testCopyCQ(){ 54 | std::cout << "Begin testCopyCQ\n"; 55 | struct printStruct{ 56 | int theInt; 57 | printStruct() : theInt{-1} {std::cout << "printStruct's default ctor\n";} 58 | printStruct(int a) : theInt{a} {std::cout << "printStruct's int ctor\n";} 59 | }; 60 | circularQueue firstPrintCQ((printStruct(21)),(printStruct())); 61 | std::cout << "Begin the copy\n"; 62 | circularQueue copyOfPrintCQ(firstPrintCQ); 63 | circularQueue secondCopyOfPrintCQ(std::move(copyOfPrintCQ)); 64 | } 65 | 66 | void fullCQ(){ 67 | std::cout << "Begin fullCQ\n"; 68 | circularQueue theFullCQ, anotherCQ; 69 | theFullCQ.push('A'); 70 | theFullCQ.push('B'); 71 | theFullCQ.push('C'); 72 | theFullCQ.pop(); 73 | theFullCQ.push('C'); 74 | theFullCQ.push('D'); 75 | anotherCQ = std::move(theFullCQ); 76 | } 77 | constexpr bool atCompileTimeCQ(){ 78 | circularQueue theCharCQ('A','B'), fourthCQ; 79 | circularQueue secondCharCQ(theCharCQ); //copy by l value 80 | circularQueue thirdCharCQ(std::move(secondCharCQ)); //copy by r value 81 | theCharCQ.push('C'); 82 | thirdCharCQ = circularQueue('X','Y'); //assignment via r value 83 | thirdCharCQ = theCharCQ; //assignment via l value 84 | thirdCharCQ.front() = 'D'; 85 | fourthCQ.emplace('A'); 86 | fourthCQ.pop(); 87 | [[maybe_unused]] circularQueue fifthCQ(theCharCQ), sixthCQ(circularQueue('X','Y')); 88 | return true; 89 | } 90 | 91 | int main(){ 92 | testCQ(); 93 | printCQ(); 94 | static_assert(atCompileTimeCQ()); 95 | testCopyCQ(); 96 | fullCQ(); 97 | //atCompileTimeCQ(); 98 | } 99 | //clang++ -Wall -std=c++1z -stdlib=libc++ -g main.cpp -o main && ./main -------------------------------------------------------------------------------- /circularQueue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIRCULARQUEUEHPP 2 | #define CIRCULARQUEUEHPP 3 | #include 4 | #include //For placement new 5 | #include 6 | template union Cell; //bool B == std::is_trivially_destructible::value 7 | template 8 | union Cell{ 9 | class emptyClass{} forConstexprCtor; 10 | T value; 11 | //Initializes forConstexprCtor because constexpr union constructors must initialize a member 12 | constexpr Cell() : forConstexprCtor{} {} 13 | //Initializes value with the provided parameter arguments 14 | template 15 | constexpr Cell(Args&&... args) : value(std::forward(args)...) {} 16 | }; 17 | template 18 | union Cell{ 19 | class emptyClass{} forConstexprCtor; 20 | T value; 21 | constexpr Cell() : forConstexprCtor{} {} 22 | template 23 | constexpr Cell(Args&&... args) : value(std::forward(args)...) {} 24 | ~Cell(){} //Included because Cell's destructor is deleted 25 | }; 26 | template struct theQueue; 27 | 28 | template 29 | struct theQueue{ 30 | constexpr theQueue() = default; //Default constructor 31 | //Copy constructor 32 | constexpr theQueue(const theQueue& other) : head(other.head), tail(other.tail), theSize(other.theSize){ 33 | std::size_t originalHead(other.head); 34 | //If other is full, there's a chance that other.head == other.tail 35 | if(other.head > other.tail || (other.head == other.tail && other.theSize == N)){ 36 | for(; originalHead < N; ++originalHead){ 37 | if constexpr(std::is_trivially_copy_assignable::value){ 38 | theArray[originalHead] = other.theArray[originalHead]; 39 | } else { 40 | new(&theArray[originalHead].value)T(other.theArray[originalHead].value); 41 | } 42 | } 43 | originalHead = 0; 44 | } 45 | for(; originalHead < other.tail; ++originalHead){ 46 | if constexpr(std::is_trivially_copy_assignable::value){ 47 | theArray[originalHead] = other.theArray[originalHead]; 48 | } else { 49 | new(&theArray[originalHead].value)T(other.theArray[originalHead].value); 50 | } 51 | } 52 | } 53 | constexpr theQueue(theQueue&& other) : head(other.head), tail(std::move(other.tail)), 54 | theSize(std::move(other.theSize)){ //Move constructor 55 | std::size_t originalHead(std::move(other.head)); 56 | //If other is full, there's a chance that other.head == other.tail 57 | if(other.head > other.tail || (other.head == other.tail && other.theSize == N)){ 58 | for(; originalHead < N; ++originalHead){ 59 | if constexpr(std::is_trivially_copy_assignable::value){ 60 | theArray[originalHead] = std::move(other.theArray[originalHead]); 61 | } else { 62 | new(&theArray[originalHead].value)T(std::move(other.theArray[originalHead].value)); 63 | } 64 | } 65 | originalHead = 0; 66 | } 67 | for(; originalHead < other.tail; ++originalHead){ 68 | if constexpr(std::is_trivially_copy_assignable::value){ 69 | theArray[originalHead] = std::move(other.theArray[originalHead]); 70 | } else { 71 | new(&theArray[originalHead].value)T(std::move(other.theArray[originalHead].value)); 72 | } 73 | } 74 | } 75 | //Constructor which accepts arguments to construct theArray 76 | template)>::type > 78 | explicit constexpr theQueue(Args&&... theList) : head{0}, tail(sizeof...(theList)), theSize(sizeof...(theList)), 79 | theArray{std::forward(theList)...}{} 80 | constexpr theQueue& operator=(const theQueue& other){//Copy assignment 81 | std::size_t originalHead(head = other.head); 82 | if constexpr(!std::is_trivially_destructible::value){ 83 | clear(); 84 | } 85 | if(other.head > other.tail || (other.head == other.tail && other.theSize == N)){ 86 | for(; originalHead < N; ++originalHead){ 87 | if constexpr(std::is_trivially_copy_assignable::value){ 88 | theArray[originalHead] = other.theArray[originalHead]; 89 | } else { 90 | new(&theArray[originalHead].value)T(other.theArray[originalHead].value); 91 | } 92 | } 93 | originalHead = 0; 94 | } 95 | for(; originalHead < other.tail; ++originalHead){ 96 | if constexpr(std::is_trivially_copy_assignable::value){ 97 | theArray[originalHead] = other.theArray[originalHead]; 98 | } else { 99 | new(&theArray[originalHead].value)T(other.theArray[originalHead].value); 100 | } 101 | } 102 | tail = other.tail; 103 | theSize = other.theSize; 104 | return *this; 105 | } 106 | constexpr theQueue& operator=(theQueue&& other){//Move assignment 107 | std::size_t originalHead(head = other.head); 108 | if constexpr(!std::is_trivially_destructible::value){ 109 | clear(); 110 | } 111 | if(other.head > other.tail || (other.head == other.tail && other.theSize == N)){ 112 | for(; originalHead < N; ++originalHead){ 113 | if constexpr(std::is_trivially_copy_assignable::value){ 114 | theArray[originalHead] = std::move(other.theArray[originalHead]); 115 | } else { 116 | new(&theArray[originalHead].value)T(std::move(other.theArray[originalHead].value)); 117 | } 118 | } 119 | originalHead = 0; 120 | } 121 | for(; originalHead < other.tail; ++originalHead){ 122 | if constexpr(std::is_trivially_copy_assignable::value){ 123 | theArray[originalHead] = std::move(other.theArray[originalHead]); 124 | } else { 125 | new(&theArray[originalHead].value)T(std::move(other.theArray[originalHead].value)); 126 | } 127 | } 128 | tail = std::move(other.tail); 129 | theSize = std::move(other.theSize); 130 | return *this; 131 | } 132 | //Container modifying functions 133 | constexpr bool push(const T& theObj){//Pushes the given element value to the end of the queue 134 | if(!checkSizeAndIndex()) return false; 135 | if constexpr(std::is_trivially_copy_assignable::value){ 136 | theArray[tail++] = Cell(theObj); 137 | } else { 138 | new(&theArray[tail++].value)T(theObj); 139 | } 140 | return ++theSize; //++theSize always > 0. Return true 141 | } 142 | constexpr bool push(T&& theObj){//Pushes the given element value to the end of the queue 143 | if(!checkSizeAndIndex()) return false; 144 | if constexpr(std::is_trivially_copy_assignable::value){ 145 | theArray[tail++] = Cell(std::move(theObj)); 146 | } else { 147 | new(&theArray[tail++].value)T(std::move(theObj)); 148 | } 149 | return ++theSize; //++theSize always > 0. Return true 150 | } 151 | template 152 | constexpr bool emplace(Args&&... args){ //Same as push, but the element is constructed in-place 153 | if(!checkSizeAndIndex()) return false; 154 | if constexpr(std::is_trivially_copy_assignable::value){ 155 | theArray[tail++] = Cell(std::forward(args)...); 156 | } else { 157 | new(&theArray[tail++].value)T(std::forward(args)...); 158 | } 159 | return ++theSize; 160 | } 161 | constexpr bool pop() noexcept{ //Removes the element at the queue's front 162 | if(!theSize) return false; //If it's empty, pop fails 163 | if constexpr(std::is_trivially_destructible::value){ 164 | (head == N - 1 ? head = 0 : ++head); 165 | } else { 166 | if(head == N - 1){ 167 | theArray[head].value.~T(); 168 | head = 0; 169 | } else { 170 | theArray[head++].value.~T(); 171 | } 172 | } 173 | --theSize; 174 | return true; 175 | } 176 | 177 | //Capacity Methods 178 | constexpr bool full() const noexcept {return theSize == N;} //Check if queue is full 179 | constexpr bool empty() const noexcept {return !theSize;} //Check if queue is empty 180 | constexpr Idxtype size() const noexcept {return theSize;} //Returns the queue's current size 181 | //Element Access functions 182 | //Returns the max number of elements the queue may hold 183 | constexpr std::size_t capacity() const noexcept {return N;} 184 | //Returns the element next to be popped. Undefined behavior if queue is empty 185 | constexpr const T& front() const {return theArray[head].value;} 186 | constexpr T& front() {return theArray[head].value;} 187 | //Returns the element last to be popped. Undefined behavior if queue is empty 188 | constexpr const T& back() const {return theArray[tail - 1].value;} 189 | constexpr T& back() {return theArray[tail - 1].value;} 190 | protected: 191 | Idxtype head{0}, tail{0}, theSize{0}; 192 | Cell::value> theArray[N]; 193 | constexpr void clear(){ //Destroys value in the queue when value is the active member 194 | if(head > tail || (head == tail && theSize == N)){ 195 | for(; head < N; ++head){ 196 | theArray[head].value.~T(); 197 | } 198 | head = 0; 199 | } 200 | for(; head < tail; ++head){ 201 | theArray[head].value.~T(); 202 | } 203 | } 204 | //If it's full, nothing is added to the queue. 205 | //If it reaches the array's end, construct T at index 0 206 | constexpr bool checkSizeAndIndex(){ 207 | if(theSize == N){ 208 | return false;//queue is full 209 | } 210 | if(tail == N){ 211 | tail = 0; 212 | } 213 | return true; 214 | } 215 | }; 216 | template 217 | struct theQueue : public theQueue{ 218 | template 219 | theQueue(Args&&... theList) : theQueue(std::forward(theList)...) {} 220 | 221 | ~theQueue(){this->clear();} 222 | }; 223 | template 224 | using circularQueue = theQueue::value, Idxtype>; 225 | #endif //CIRCULARQUEUEHPP --------------------------------------------------------------------------------