├── include ├── .DS_Store └── asenum │ └── asenum.h ├── .gitmodules ├── LICENSE ├── CMakeLists.txt ├── README.md └── tests └── AsEnumTest.cpp /include/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alkenso/asenum/HEAD/include/.DS_Store -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdParty/googletest"] 2 | path = 3rdParty/googletest 3 | url = https://github.com/google/googletest.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Vladimir (Alkenso) 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 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.2) 2 | 3 | project(asenum DESCRIPTION "AssociatedEnum: header-only library for C++ for enumerations with associated values.") 4 | 5 | if (NOT WIN32) 6 | add_compile_options(-std=c++11) 7 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 8 | add_compile_options(-stdlib=libc++) 9 | endif() 10 | endif() 11 | 12 | if (APPLE) 13 | set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9") 14 | endif() 15 | 16 | OPTION(ASENUM_TESTING_ENABLE "Build AssEnum's unit-tests." OFF) 17 | 18 | ### asenum library ### 19 | 20 | add_library(asenum INTERFACE) 21 | 22 | target_include_directories(asenum INTERFACE "include") 23 | 24 | 25 | ### asenum unit-tests ### 26 | 27 | if (ASENUM_TESTING_ENABLE) 28 | set(TEST_SOURCES 29 | tests/AsEnumTest.cpp 30 | ) 31 | add_executable(asenum_tests ${TEST_SOURCES}) 32 | 33 | # setup 3rdParty 34 | add_subdirectory(3rdParty/googletest) 35 | set_target_properties(gmock PROPERTIES FOLDER 3rdParty) 36 | set_target_properties(gmock_main PROPERTIES FOLDER 3rdParty) 37 | set_target_properties(gtest PROPERTIES FOLDER 3rdParty) 38 | set_target_properties(gtest_main PROPERTIES FOLDER 3rdParty) 39 | 40 | target_link_libraries(asenum_tests asenum gtest gmock gmock_main) 41 | endif() 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # asenum 2 | **AssociatedEnum** is a header-only library for C++ for enumerations with associated values 3 | 4 | asenum is C++ implementation of very neat enums from Swift language (https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html, chapter 'Associated Values'). 5 | 6 | asenum combines Enum and Variant: it allows to create lighweight wrapper around plain C++ enum and add ability to assign different value types to different cases. 7 | 8 | ## Features 9 | - each enum case can be associated with any type 10 | - values are immutable: totally thread-safe 11 | - simple interface 12 | - lightweight, header-only, single file library 13 | - requires only C++11 14 | - convenient switch, map, equality, comparison 15 | 16 | ## Usage & Install 17 | Simply copy file 'include/asenum/asenum.h' to your project. 18 | 19 | ## Simple example 20 | ``` 21 | // More examples you can find in tests 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | // ===== DECLARATION ===== 29 | enum class ErrorCode 30 | { 31 | Unknown, 32 | Success, 33 | Timeout 34 | }; 35 | 36 | // Associate enum 'ErrorCode' with AsEnum 'AnyError' 37 | using AnyError = asenum::AsEnum< 38 | asenum::Case11, 39 | asenum::Case11, 40 | asenum::Case11 41 | >; 42 | 43 | // ===== USAGE ===== 44 | void LogError(const AnyError& error) 45 | { 46 | error.doSwitch() 47 | .ifCase([] (const std::string& value) { 48 | std::cout << "Unknown error: " << value << "\n"; 49 | }) 50 | .ifCase([] { 51 | std::cout << "Success\n"; 52 | }) 53 | .ifCase([] (const std::chrono::seconds& value) { 54 | std::cout << "Timed out after: " << value.count() << "\n"; 55 | }) 56 | .ifDefault([] { 57 | std::cout << "Default\n"; 58 | }); 59 | } 60 | 61 | int main() 62 | { 63 | // ===== CREATION ===== 64 | LogError(AnyError::create("test.api.com")); 65 | LogError(AnyError::create()); 66 | LogError(AnyError::create(std::chrono::seconds(1))); 67 | 68 | return 0; 69 | } 70 | ``` 71 | 72 | ## Mapping 73 | AsEnum supports mapping to any desired type 74 | *Note: if all cases covered by mapping, it doesn't require 'default' case* 75 | ``` 76 | // ===== Mapping ====== 77 | std::string ErrorToString(const AnyError& error) 78 | { 79 | // All cases covered 80 | const auto stringRepresentation = error.doMap() 81 | .ifCase([] (const std::string& value) { 82 | return value; 83 | }) 84 | .ifCase([] { 85 | return "Success"; 86 | }) 87 | .ifCase([] (const std::chrono::seconds& value) { 88 | return "Timed out after: " + std::to_string(value.count()); 89 | }); 90 | 91 | return stringRepresentation; 92 | } 93 | 94 | // ===== Partial Mapping ====== 95 | std::string ErrorToString2(const AnyError& error) 96 | { 97 | // All cases covered 98 | const auto stringRepresentation = error.doMap() 99 | .ifCase([] { 100 | return "Success"; 101 | }) 102 | .ifDefault([] { 103 | return "Unknown error"; 104 | }); 105 | 106 | return stringRepresentation; 107 | } 108 | ``` 109 | 110 | ## Equality and Comparison 111 | AsEnum provides native way of comparing values 112 | ``` 113 | void Equality(const AnyError& error1, const AnyError& error2) 114 | { 115 | // We can check if error1 == error2 116 | // Cases differ => NOT Equal 117 | // Cases same AND underlying values same => Equal 118 | 119 | const bool equal = error1 == error2; 120 | } 121 | 122 | void Comparability(const AnyError& error1, const AnyError& error2) 123 | { 124 | // We can check how error1 relates to error2 125 | // Cases of error1 and error2 differ => Compare case ordering 126 | // Cases same => Compare underlying values 127 | 128 | const bool less = error1 < error2; 129 | const bool lessEqual = error1 <= error2; 130 | const bool greater = error1 > error2; 131 | const bool greaterEqual = error1 >= error2; 132 | } 133 | ``` 134 | 135 | ## Some usage examples 136 | ### Square equation roots 137 | ``` 138 | #include 139 | 140 | #include 141 | #include 142 | #include 143 | #include 144 | 145 | 146 | enum class RootsType 147 | { 148 | None, 149 | Single, 150 | Pair 151 | }; 152 | 153 | using Roots = asenum::AsEnum< 154 | asenum::Case11, 155 | asenum::Case11, 156 | asenum::Case11> 157 | >; 158 | 159 | 160 | Roots FindRoots(const double a, const double b, const double c) 161 | { 162 | const double d = (b * b) - (4 * a * c); 163 | if (d < 0) 164 | { 165 | return Roots::create(); 166 | } 167 | 168 | const double x1 = (-b + sqrt(d)) / (2 * a); 169 | const double x2 = (-b - sqrt(d)) / (2 * a); 170 | 171 | return x1 == x2 ? Roots::create(x1) : Roots::create(std::make_pair(x1, x2)); 172 | } 173 | 174 | double ReadK(const std::string& name) 175 | { 176 | double k = 0; 177 | std::cout << "Input " << name << ": "; 178 | std::cin >> k; 179 | 180 | return k; 181 | } 182 | 183 | int main() 184 | { 185 | const double a = ReadK("a"); 186 | const double b = ReadK("b"); 187 | const double c = ReadK("c"); 188 | 189 | const Roots roots = FindRoots(a, b, c); 190 | roots.doSwitch() 191 | .ifCase([] { 192 | std::cout << "No roots\n"; 193 | }) 194 | .ifCase([] (const double x) { 195 | std::cout << "Single (equal) roots: " << x; 196 | }) 197 | .ifCase([] (const std::pair& roots) { 198 | std::cout << "Different roots. x1 = " << roots.first << "; x2 = " << roots.second << ".\n"; 199 | }); 200 | 201 | return 0; 202 | } 203 | ``` 204 | 205 | ### BarCode convertor 206 | ``` 207 | #include 208 | 209 | #include 210 | #include 211 | 212 | 213 | enum class BarCodeType 214 | { 215 | UPC, 216 | QrCode 217 | }; 218 | 219 | using BarCode = asenum::AsEnum< 220 | asenum::Case11>, 221 | asenum::Case11>> 222 | >; 223 | 224 | std::string ConvertUPCToLink(const std::vector& photo) 225 | { 226 | std::string link; 227 | // perform some computations... 228 | 229 | return link; 230 | } 231 | 232 | std::string ConvertQrCodeToLink(const std::vector& photo) 233 | { 234 | std::string link; 235 | // perform some computations... 236 | 237 | return link; 238 | } 239 | 240 | std::string ConvertBarCodeToLink(const BarCode& barCode) 241 | { 242 | return barCode.doMap() 243 | .ifCase(ConvertUPCToLink) 244 | .ifCase(ConvertQrCodeToLink); 245 | } 246 | ``` 247 | 248 | ### Downloader 249 | ``` 250 | enum class DownloadResultType 251 | { 252 | Data, 253 | Error, 254 | Canceled 255 | }; 256 | 257 | using DownloadResult = asenum::AsEnum< 258 | asenum::Case11, 259 | asenum::Case11, 260 | asenum::Case11 261 | >; 262 | 263 | DownloadResult DownloadFileFromLink(const std::string& link) 264 | { 265 | // perform HTTP(s) request 266 | 267 | // return response data on success, ... 268 | return DownloadResult::create("..."); 269 | 270 | // ... or return server error, ... 271 | return DownloadResult::create(404); 272 | 273 | // ... or inform caller that request has been canceled. 274 | return DownloadResult::create(); 275 | } 276 | 277 | int main() 278 | { 279 | const DownloadResult result = DownloadFileFromLink("http://test.api.com/download/file1.txt"); 280 | 281 | result.doSwitch() 282 | .ifCase([] (const std::string& content) { 283 | std::cout << "Downloaded file content: " << content << "\n"; 284 | }) 285 | .ifCase([] (const int errorCode) { 286 | std::cout << "Request did fail with error: " << errorCode << "\n"; 287 | }) 288 | .ifCase([] { 289 | std::cout << "Request has been canceled by user\n"; 290 | }); 291 | 292 | return 0; 293 | } 294 | ``` 295 | -------------------------------------------------------------------------------- /tests/AsEnumTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2019 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | 27 | #include 28 | 29 | #include 30 | 31 | using namespace ::testing; 32 | 33 | namespace 34 | { 35 | enum class TestEnum 36 | { 37 | StringOpt1, 38 | VoidOpt2, 39 | Unknown3 40 | }; 41 | 42 | using TestAsEnum = asenum::AsEnum< 43 | asenum::Case11, 44 | asenum::Case11, 45 | asenum::Case11 46 | >; 47 | 48 | static_assert(std::is_same, int>::value, "Invalid underlying type"); 49 | static_assert(std::is_same, std::string>::value, "Invalid underlying type"); 50 | static_assert(std::is_same, void>::value, "Invalid underlying type"); 51 | 52 | static_assert(GTEST_ARRAY_SIZE_(TestAsEnum::AllCases) == 3, "Invalid number of cases"); 53 | static_assert(TestAsEnum::AllCases[0] == TestEnum::Unknown3, "Invalid enum case"); 54 | static_assert(TestAsEnum::AllCases[1] == TestEnum::StringOpt1, "Invalid enum case"); 55 | static_assert(TestAsEnum::AllCases[2] == TestEnum::VoidOpt2, "Invalid enum case"); 56 | 57 | 58 | enum class SomeVoidEnum 59 | { 60 | Opt1, 61 | Opt2 62 | }; 63 | 64 | using SomeVoidAsEnum = asenum::AsEnum< 65 | asenum::Case11, 66 | asenum::Case11 67 | >; 68 | } 69 | 70 | TEST(AsEnum, IfCase) 71 | { 72 | const TestAsEnum value1 = TestAsEnum::create("test"); 73 | const TestAsEnum value2 = TestAsEnum::create(); 74 | const TestAsEnum value3 = TestAsEnum::create(-100500); 75 | 76 | MockFunction handler1; 77 | MockFunction handler2; 78 | MockFunction handler3; 79 | 80 | EXPECT_CALL(handler1, Call("test")) 81 | .WillOnce(Return()); 82 | EXPECT_CALL(handler2, Call()) 83 | .WillOnce(Return()); 84 | EXPECT_CALL(handler3, Call(-100500)) 85 | .WillOnce(Return()); 86 | 87 | EXPECT_TRUE(value1.ifCase(handler1.AsStdFunction())); 88 | EXPECT_FALSE(value1.ifCase(handler2.AsStdFunction())); 89 | EXPECT_FALSE(value1.ifCase(handler3.AsStdFunction())); 90 | 91 | EXPECT_FALSE(value2.ifCase(handler1.AsStdFunction())); 92 | EXPECT_TRUE(value2.ifCase(handler2.AsStdFunction())); 93 | EXPECT_FALSE(value2.ifCase(handler3.AsStdFunction())); 94 | 95 | EXPECT_FALSE(value3.ifCase(handler1.AsStdFunction())); 96 | EXPECT_FALSE(value3.ifCase(handler2.AsStdFunction())); 97 | EXPECT_TRUE(value3.ifCase(handler3.AsStdFunction())); 98 | } 99 | 100 | TEST(AsEnum, IsCase) 101 | { 102 | const TestAsEnum value1 = TestAsEnum::create("test"); 103 | const TestAsEnum value2 = TestAsEnum::create(); 104 | const TestAsEnum value3 = TestAsEnum::create(-100500); 105 | 106 | EXPECT_EQ(value1.enumCase(), TestEnum::StringOpt1); 107 | EXPECT_EQ(value2.enumCase(), TestEnum::VoidOpt2); 108 | EXPECT_EQ(value3.enumCase(), TestEnum::Unknown3); 109 | 110 | EXPECT_TRUE(value1.isCase()); 111 | EXPECT_FALSE(value1.isCase()); 112 | EXPECT_FALSE(value1.isCase()); 113 | 114 | EXPECT_FALSE(value2.isCase()); 115 | EXPECT_TRUE(value2.isCase()); 116 | EXPECT_FALSE(value2.isCase()); 117 | 118 | EXPECT_FALSE(value3.isCase()); 119 | EXPECT_FALSE(value3.isCase()); 120 | EXPECT_TRUE(value3.isCase()); 121 | } 122 | 123 | TEST(AsEnum, Switch_Full) 124 | { 125 | const TestAsEnum value = TestAsEnum::create("test"); 126 | 127 | MockFunction handler; 128 | 129 | EXPECT_CALL(handler, Call("test")) 130 | .WillOnce(Return()); 131 | 132 | value.doSwitch() 133 | .ifCase(handler.AsStdFunction()) 134 | .ifCase([] { 135 | EXPECT_TRUE(false); 136 | }) 137 | .ifCase([] (const int& value) { 138 | EXPECT_TRUE(false); 139 | }) 140 | .ifDefault([] () { 141 | EXPECT_TRUE(false); 142 | }); 143 | } 144 | 145 | TEST(AsEnum, Switch_Partial) 146 | { 147 | const TestAsEnum value = TestAsEnum::create("test"); 148 | 149 | MockFunction handler; 150 | 151 | EXPECT_CALL(handler, Call("test")) 152 | .WillOnce(Return()); 153 | 154 | value.doSwitch() 155 | .ifCase(handler.AsStdFunction()) 156 | .ifCase([] { 157 | EXPECT_TRUE(false); 158 | }); 159 | } 160 | 161 | TEST(AsEnum, Switch_Default) 162 | { 163 | const TestAsEnum value = TestAsEnum::create("test"); 164 | 165 | MockFunction handler; 166 | 167 | EXPECT_CALL(handler, Call()) 168 | .WillOnce(Return()); 169 | 170 | value.doSwitch() 171 | .ifCase([] (const int& value) { 172 | EXPECT_TRUE(false); 173 | }) 174 | .ifCase([] { 175 | EXPECT_TRUE(false); 176 | }) 177 | .ifDefault(handler.AsStdFunction()); 178 | } 179 | 180 | TEST(AsEnum, Map_With_Default) 181 | { 182 | const TestAsEnum value = TestAsEnum::create("test"); 183 | 184 | const bool vv = value.doMap() 185 | .ifCase([] (const std::string& value) { 186 | return true; 187 | }) 188 | .ifCase([] { 189 | return false; 190 | }) 191 | .ifDefault([] { 192 | return false; 193 | }); 194 | 195 | EXPECT_TRUE(vv); 196 | } 197 | 198 | TEST(AsEnum, Map_All_Cases) 199 | { 200 | const TestAsEnum value = TestAsEnum::create("test"); 201 | 202 | const bool vv = value.doMap() 203 | .ifCase([] (const int& value) { 204 | return false; 205 | }) 206 | .ifCase([] { 207 | return false; 208 | }) 209 | .ifCase([] (const std::string& value) { 210 | return true; 211 | }); 212 | 213 | EXPECT_TRUE(vv); 214 | } 215 | 216 | TEST(AsEnum, ForceAsCase) 217 | { 218 | const TestAsEnum value1 = TestAsEnum::create("test"); 219 | // TestEnum::VoidOpt doesn't have 'forceAsEnum' method because associated type is 'void'. 220 | const TestAsEnum value3 = TestAsEnum::create(-100500); 221 | 222 | EXPECT_EQ(value1.forceAsCase(), "test"); 223 | EXPECT_THROW(value1.forceAsCase(), std::invalid_argument); 224 | 225 | EXPECT_THROW(value3.forceAsCase(), std::invalid_argument); 226 | EXPECT_EQ(value3.forceAsCase(), -100500); 227 | } 228 | 229 | TEST(AsEnum, Equality) 230 | { 231 | const TestAsEnum value1 = TestAsEnum::create("test"); 232 | const TestAsEnum value2 = TestAsEnum::create("test"); 233 | const TestAsEnum value3 = TestAsEnum::create("test2"); 234 | const TestAsEnum value4 = TestAsEnum::create(); 235 | const TestAsEnum value5 = TestAsEnum::create(-100500); 236 | 237 | EXPECT_EQ(value1, value1); 238 | EXPECT_EQ(value1, value2); 239 | EXPECT_NE(value1, value3); 240 | EXPECT_NE(value1, value4); 241 | EXPECT_NE(value1, value5); 242 | 243 | EXPECT_EQ(value4, TestAsEnum::create()); 244 | } 245 | 246 | TEST(AsEnum, Equality_Void) 247 | { 248 | const SomeVoidAsEnum value1 = SomeVoidAsEnum::create(); 249 | const SomeVoidAsEnum value2 = SomeVoidAsEnum::create(); 250 | const SomeVoidAsEnum value3 = SomeVoidAsEnum::create(); 251 | 252 | EXPECT_EQ(value1, value1); 253 | EXPECT_EQ(value1, value2); 254 | EXPECT_NE(value1, value3); 255 | } 256 | 257 | TEST(AsEnum, Compare_SameCase) 258 | { 259 | const TestAsEnum value1 = TestAsEnum::create("test"); 260 | const TestAsEnum value2 = TestAsEnum::create("test"); 261 | const TestAsEnum value3 = TestAsEnum::create("test2"); 262 | 263 | EXPECT_LT(value1, value3); 264 | EXPECT_LE(value1, value2); 265 | EXPECT_GT(value3, value1); 266 | EXPECT_GE(value1, value2); 267 | } 268 | 269 | TEST(AsEnum, Compare_SameCase_Void) 270 | { 271 | const SomeVoidAsEnum value1 = SomeVoidAsEnum::create(); 272 | const SomeVoidAsEnum value2 = SomeVoidAsEnum::create(); 273 | const SomeVoidAsEnum value3 = SomeVoidAsEnum::create(); 274 | 275 | EXPECT_LT(value1, value3); 276 | EXPECT_LE(value1, value2); 277 | EXPECT_GT(value3, value1); 278 | EXPECT_GE(value1, value2); 279 | } 280 | 281 | TEST(AsEnum, Compare_RandomCase) 282 | { 283 | const TestAsEnum value1 = TestAsEnum::create("test"); 284 | const TestAsEnum value2 = TestAsEnum::create(); 285 | const TestAsEnum value3 = TestAsEnum::create(-100500); 286 | 287 | EXPECT_LT(value1, value2); 288 | EXPECT_LT(value2, value3); 289 | 290 | EXPECT_LE(value1, value1); 291 | EXPECT_LE(value1, value2); 292 | EXPECT_LE(value1, value3); 293 | EXPECT_LE(value2, value2); 294 | EXPECT_LE(value2, value3); 295 | EXPECT_LE(value3, value3); 296 | 297 | EXPECT_GT(value3, value2); 298 | EXPECT_GT(value3, value1); 299 | 300 | EXPECT_GE(value3, value3); 301 | EXPECT_GE(value3, value2); 302 | EXPECT_GE(value3, value1); 303 | EXPECT_GE(value2, value2); 304 | EXPECT_GE(value2, value1); 305 | EXPECT_GE(value1, value1); 306 | } 307 | -------------------------------------------------------------------------------- /include/asenum/asenum.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2019 Alkenso (Vladimir Vashurkin) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | #include 29 | 30 | namespace asenum 31 | { 32 | /// Case descriptor of single Associated Enum case. 33 | template 34 | struct Case11 35 | { 36 | using Enum = T_Enum; 37 | static constexpr Enum Code = T_Code; 38 | using Type = T; 39 | }; 40 | 41 | #if __cplusplus > 201402L 42 | ///Case descriptor of single Associated Enum case. Convenient use with C++17 compiler. 43 | template 44 | using Case = Case11; 45 | #endif 46 | 47 | namespace details 48 | { 49 | template 50 | struct CaseSet; 51 | 52 | template 53 | struct UnderlyingTypeResolver; 54 | 55 | template 56 | class AsSwitch; 57 | 58 | template 59 | class AsMap; 60 | 61 | template class Cmp, typename... T_Cases> 62 | struct Comparator; 63 | } 64 | 65 | /** 66 | Associated Enum type. 67 | AsEnum should be specialized with single or multiple 'Case/Case11' types that represent associations. 68 | */ 69 | template 70 | class AsEnum 71 | { 72 | static_assert(sizeof...(T_Cases) > 0, "Failed to instantiate AsEnum with no cases."); 73 | 74 | public: 75 | /// Related enum type. 76 | using Enum = typename details::CaseSet::Enum; 77 | 78 | /// Specific 'using' that allows to get accociated type with specific enum case. 79 | template 80 | using UnderlyingType = typename details::UnderlyingTypeResolver::type; 81 | 82 | /// Array of all cases associated with concrete AsEnum. 83 | static constexpr Enum AllCases[] = { T_Cases::Code... }; 84 | 85 | /** 86 | Creates AsEnum instance of specific case. 87 | 88 | @param value Value related to specified enum case. 89 | @return AsEnum instance holding value of specified case. 90 | */ 91 | template , void>::value>::type> 92 | static AsEnum create(UnderlyingType value) { return createImpl>(std::move(value)); } 93 | 94 | /** 95 | Creates AsEnum instance of specific case with 'void' associated type. 96 | 97 | @return AsEnum instance holding value of specified case. 98 | */ 99 | template , void>::value>::type> 100 | static AsEnum create(); 101 | 102 | /** 103 | @return enum case of current instance of AsEnum. 104 | */ 105 | Enum enumCase() const; 106 | 107 | /** 108 | @return Boolean indicates if current instance of AsEnum holds exactly specified case...or not. 109 | */ 110 | template 111 | bool isCase() const; 112 | 113 | /** 114 | Unwraps AsEnum and provides access to value that it holds. 115 | 116 | @param handler Function or functional object of signature void(UnderlyingType) 117 | that accepts value/reference of type associated with specified case. 118 | @return Boolean indicates if handler has been called. 119 | */ 120 | template 121 | bool ifCase(const Handler& handler) const; 122 | 123 | /** 124 | @warning Usually ou don't want to use this method. Use safer 'ifCase'. 125 | Force unwraps AsEnum and provides direct access to value that it holds. 126 | 127 | @return Const reference to underlying value. 128 | @throws std::invalid_argument exception if 'Case' doesn't correspond to stored case. 129 | */ 130 | template , typename = typename std::enable_if::value>::type> 131 | const R& forceAsCase() const; 132 | 133 | /** 134 | Performs switch-like action allowing to wotk with values of different cases. 135 | */ 136 | details::AsSwitch> doSwitch() const; 137 | 138 | /** 139 | Maps (converts) AsEnum value depends on stored case to type 'T'. 140 | */ 141 | template 142 | details::AsMap> doMap() const; 143 | 144 | /** 145 | Check for equality two AsEnum instances. Instance meant to be equal if and only if 146 | 1) Underlying enum cases are equal; 147 | 2) Underlying values are equal. 148 | */ 149 | bool operator==(const AsEnum& other) const; 150 | bool operator!=(const AsEnum& other) const; 151 | 152 | /** 153 | Compares two AsEnum instances. 154 | 1) First compare types. If types differ, applies comparison to type itself; 155 | 2) If types equal, compares underlying values. 156 | */ 157 | bool operator<(const AsEnum& other) const; 158 | bool operator<=(const AsEnum& other) const; 159 | bool operator>(const AsEnum& other) const; 160 | bool operator>=(const AsEnum& other) const; 161 | 162 | private: 163 | AsEnum(const Enum relatedCase, std::shared_ptr value); 164 | 165 | template 166 | static AsEnum createImpl(T&& value); 167 | 168 | template 169 | static typename std::enable_if::value, void>::type call(const void*, const Handler& handler); 170 | 171 | template 172 | static typename std::enable_if::value, void>::type call(const void* value, const Handler& handler); 173 | 174 | private: 175 | Enum m_enumCase; 176 | std::shared_ptr m_value; 177 | }; 178 | 179 | 180 | // Private details 181 | 182 | namespace details 183 | { 184 | template 185 | constexpr size_t ArraySize(T (&array)[N]) 186 | { 187 | return sizeof(array) / sizeof(array[0]); 188 | } 189 | 190 | template 191 | struct AsMapResultMaker; 192 | 193 | template 194 | struct AsMapResultMaker 195 | { 196 | template 197 | static T 198 | makeResult(const ConcreteAsEnum&, std::unique_ptr result) 199 | { 200 | if (!result) 201 | { 202 | throw std::logic_error("Unexpected empty result. Please contact author and attach an example."); 203 | } 204 | 205 | return std::move(*result); 206 | } 207 | }; 208 | 209 | template 210 | struct AsMapResultMaker 211 | { 212 | template 213 | static AsMap 214 | makeResult(const ConcreteAsEnum& asEnum, std::unique_ptr result) 215 | { 216 | return AsMap(asEnum, std::move(result)); 217 | } 218 | }; 219 | 220 | 221 | template struct Contains; 222 | template struct Contains { static const bool value = false; }; 223 | template struct Contains { static const bool value = T_type1 == T_type2; }; 224 | 225 | template 226 | struct Contains { static const bool value = Contains::value || Contains::value; }; 227 | 228 | 229 | template 230 | class AsSwitch 231 | { 232 | public: 233 | AsSwitch(const ConcreteAsEnum& asEnum, const bool handled); 234 | explicit AsSwitch(const ConcreteAsEnum& asEnum); 235 | 236 | template 237 | AsSwitch ifCase(const CaseHandler& handler); 238 | 239 | template 240 | void ifDefault(const Handler& handler); 241 | 242 | private: 243 | const ConcreteAsEnum& m_asEnum; 244 | bool m_handled; 245 | }; 246 | 247 | template 248 | class AsMap 249 | { 250 | static constexpr size_t AllCaseCount = ArraySize(ConcreteAsEnum::AllCases); 251 | static constexpr size_t CurrentCaseCount = sizeof...(Types); 252 | static constexpr bool IsPreLastCase = AllCaseCount == CurrentCaseCount + 1; 253 | using ResultMaker = AsMapResultMaker; 254 | 255 | template 256 | using IfCaseResult = typename std::conditional>::type; 257 | 258 | template 259 | using UnderlyingType = typename ConcreteAsEnum::template UnderlyingType; 260 | 261 | public: 262 | AsMap(const ConcreteAsEnum& asEnum, std::unique_ptr result); 263 | explicit AsMap(const ConcreteAsEnum& asEnum); 264 | 265 | template > 266 | static typename std::enable_if::value, void>::type 267 | ifCaseCall(const ConcreteAsEnum& asEnum, std::unique_ptr& result, const Handler& handler); 268 | 269 | template > 270 | static typename std::enable_if::value, void>::type 271 | ifCaseCall(const ConcreteAsEnum& asEnum, std::unique_ptr& result, const Handler& handler); 272 | 273 | template 274 | T ifDefault(const Handler& handler); 275 | 276 | template > 277 | R ifCase(const CaseHandler& handler); 278 | 279 | private: 280 | std::unique_ptr m_result; 281 | const ConcreteAsEnum& m_asEnum; 282 | }; 283 | 284 | template 285 | struct CaseSet 286 | { 287 | using Enum = typename Case::Enum; 288 | static_assert(std::is_enum::value, "All cases must relate to enum values."); 289 | }; 290 | 291 | template 292 | struct CaseSet 293 | { 294 | static_assert(std::is_same::Enum, typename CaseSet::Enum>::value, "All cases must relate to the same enum."); 295 | using Enum = typename CaseSet::Enum; 296 | }; 297 | 298 | 299 | template 300 | struct UnderlyingTypeResolver 301 | { 302 | template 303 | struct TypeMap; 304 | 305 | template 306 | struct TypeMap 307 | { 308 | using type = typename std::conditional::type>::type; 309 | }; 310 | 311 | template 312 | struct TypeMap 313 | { 314 | using type = typename std::conditional::type; 315 | }; 316 | 317 | 318 | struct Dummy {}; 319 | using type = typename TypeMap::type; 320 | static_assert(!std::is_same::value, "Type is missing for specified enum value."); 321 | }; 322 | 323 | 324 | template class Cmp, typename T_Case> 325 | struct Comparator 326 | { 327 | template 328 | static typename std::enable_if::value, bool>::type 329 | compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second); 330 | 331 | template 332 | static typename std::enable_if::value, bool>::type 333 | compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second); 334 | 335 | static bool compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second, bool& finalVerdict); 336 | static bool compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second); 337 | }; 338 | 339 | template class Cmp, typename T_Case, typename... T_Cases> 340 | struct Comparator 341 | { 342 | static bool compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second); 343 | }; 344 | } 345 | } 346 | 347 | 348 | // AsEnum public 349 | 350 | template 351 | constexpr typename asenum::AsEnum::Enum asenum::AsEnum::AllCases[]; 352 | 353 | template 354 | template ::Enum Case, typename T> 355 | asenum::AsEnum asenum::AsEnum::createImpl(T&& value) 356 | { 357 | std::shared_ptr internalValue(new T(std::forward(value)), [] (void* ptr) { 358 | if (ptr) 359 | { 360 | delete reinterpret_cast(ptr); 361 | } 362 | }); 363 | 364 | return asenum::AsEnum(Case, internalValue); 365 | } 366 | 367 | template 368 | template ::Enum Case, typename T> 369 | asenum::AsEnum asenum::AsEnum::create() 370 | { 371 | return asenum::AsEnum(Case, nullptr); 372 | } 373 | 374 | template 375 | typename asenum::AsEnum::Enum asenum::AsEnum::enumCase() const 376 | { 377 | return m_enumCase; 378 | } 379 | 380 | template 381 | template ::Enum Case> 382 | bool asenum::AsEnum::isCase() const 383 | { 384 | return Case == m_enumCase; 385 | } 386 | 387 | template 388 | template ::Enum Case, typename Handler> 389 | bool asenum::AsEnum::ifCase(const Handler& handler) const 390 | { 391 | const bool isType = isCase(); 392 | if (isType) 393 | { 394 | call>(m_value.get(), handler); 395 | } 396 | 397 | return isType; 398 | } 399 | 400 | template 401 | template ::Enum Case, typename R, typename> 402 | const R& asenum::AsEnum::forceAsCase() const 403 | { 404 | if (!isCase()) 405 | { 406 | throw std::invalid_argument("Unwrapping case does not correspond to stored case."); 407 | } 408 | 409 | return *reinterpret_cast*>(m_value.get()); 410 | } 411 | 412 | template 413 | asenum::details::AsSwitch::Enum, asenum::AsEnum> asenum::AsEnum::doSwitch() const 414 | { 415 | return details::AsSwitch>(*this); 416 | } 417 | 418 | template 419 | template 420 | asenum::details::AsMap::Enum, asenum::AsEnum> asenum::AsEnum::doMap() const 421 | { 422 | return details::AsMap>(*this); 423 | } 424 | 425 | template 426 | bool asenum::AsEnum::operator==(const AsEnum& other) const 427 | { 428 | return details::Comparator::compare(*this, other); 429 | } 430 | 431 | template 432 | bool asenum::AsEnum::operator!=(const AsEnum& other) const 433 | { 434 | return details::Comparator::compare(*this, other); 435 | } 436 | 437 | template 438 | bool asenum::AsEnum::operator<(const AsEnum& other) const 439 | { 440 | return details::Comparator::compare(*this, other); 441 | } 442 | 443 | template 444 | bool asenum::AsEnum::operator<=(const AsEnum& other) const 445 | { 446 | return details::Comparator::compare(*this, other); 447 | } 448 | 449 | template 450 | bool asenum::AsEnum::operator>(const AsEnum& other) const 451 | { 452 | return details::Comparator::compare(*this, other); 453 | } 454 | 455 | template 456 | bool asenum::AsEnum::operator>=(const AsEnum& other) const 457 | { 458 | return details::Comparator::compare(*this, other); 459 | } 460 | 461 | 462 | // AsEnum private 463 | 464 | template 465 | asenum::AsEnum::AsEnum(const Enum relatedCase, std::shared_ptr value) 466 | : m_enumCase(relatedCase) 467 | , m_value(value) 468 | {} 469 | 470 | template 471 | template 472 | typename std::enable_if::value, void>::type asenum::AsEnum::call(const void*, const Handler& handler) 473 | { 474 | handler(); 475 | } 476 | 477 | template 478 | template 479 | typename std::enable_if::value, void>::type asenum::AsEnum::call(const void* value, const Handler& handler) 480 | { 481 | handler(*reinterpret_cast(value)); 482 | } 483 | 484 | // Private details - AsSwitch 485 | 486 | template 487 | asenum::details::AsSwitch::AsSwitch(const ConcreteAsEnum& asEnum, const bool handled) 488 | : m_asEnum(asEnum) 489 | , m_handled(handled) 490 | {} 491 | 492 | template 493 | asenum::details::AsSwitch::AsSwitch(const ConcreteAsEnum& asEnum) 494 | : AsSwitch(asEnum, false) 495 | {} 496 | 497 | template 498 | template 499 | asenum::details::AsSwitch asenum::details::AsSwitch::ifCase(const CaseHandler& handler) 500 | { 501 | static_assert(!Contains::value, "Duplicated switch case."); 502 | 503 | if (!m_handled) 504 | { 505 | m_handled = m_asEnum.template ifCase(handler); 506 | } 507 | 508 | return AsSwitch(m_asEnum, m_handled); 509 | } 510 | 511 | template 512 | template 513 | void asenum::details::AsSwitch::ifDefault(const Handler& handler) 514 | { 515 | if (!m_handled) 516 | { 517 | m_handled = true; 518 | handler(); 519 | } 520 | } 521 | 522 | // Private details - AsMap 523 | 524 | template 525 | asenum::details::AsMap::AsMap(const ConcreteAsEnum& asEnum, std::unique_ptr result) 526 | : m_result(std::move(result)) 527 | , m_asEnum(asEnum) 528 | {} 529 | 530 | template 531 | asenum::details::AsMap::AsMap(const ConcreteAsEnum& asEnum) 532 | : m_asEnum(asEnum) 533 | {} 534 | 535 | template 536 | template 537 | typename std::enable_if::value, void>::type 538 | asenum::details::AsMap::ifCaseCall(const ConcreteAsEnum& asEnum, std::unique_ptr& result, const Handler& handler) 539 | { 540 | asEnum.template ifCase([&] { 541 | result.reset(new T(handler())); 542 | }); 543 | } 544 | 545 | template 546 | template 547 | typename std::enable_if::value, void>::type 548 | asenum::details::AsMap::ifCaseCall(const ConcreteAsEnum& asEnum, std::unique_ptr& result, const Handler& handler) 549 | { 550 | asEnum.template ifCase([&] (const UT& value) { 551 | result.reset(new T(handler(value))); 552 | }); 553 | } 554 | 555 | template 556 | template 557 | T asenum::details::AsMap::ifDefault(const Handler& handler) 558 | { 559 | return m_result ? std::move(*m_result) : handler(); 560 | } 561 | 562 | template 563 | template 564 | R asenum::details::AsMap::ifCase(const CaseHandler& handler) 565 | { 566 | static_assert(!Contains::value, "Duplicated map case"); 567 | 568 | if (!m_result) 569 | { 570 | ifCaseCall(m_asEnum, m_result, handler); 571 | } 572 | 573 | return ResultMaker::template makeResult(m_asEnum, std::move(m_result)); 574 | } 575 | 576 | // Private details - Comparator 577 | 578 | template class Cmp, typename T_Case> 579 | bool asenum::details::Comparator::compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second, bool& finalVerdict) 580 | { 581 | // If AsEnums cases are NOT equal. 582 | if (first.enumCase() != second.enumCase()) 583 | { 584 | finalVerdict = true; 585 | 586 | static constexpr Cmp s_comparator; 587 | return s_comparator(first.enumCase(), second.enumCase()); 588 | } 589 | 590 | // Both AsEnums cases are equal. Check if they are equal to Comparator's case. 591 | if (first.enumCase() == T_Case::Code) 592 | { 593 | finalVerdict = true; 594 | 595 | return compare(first, second); 596 | } 597 | 598 | return false; 599 | } 600 | 601 | template class Cmp, typename T_Case> 602 | bool asenum::details::Comparator::compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second) 603 | { 604 | bool finalVerdict = false; 605 | return compare(first, second, finalVerdict); 606 | } 607 | 608 | template class Cmp, typename T_Case> 609 | template 610 | typename std::enable_if::value, bool>::type 611 | asenum::details::Comparator::compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second) 612 | { 613 | using UT = typename ConcreteAsEnum::template UnderlyingType; 614 | static constexpr Cmp s_comparator; 615 | return s_comparator(first.template forceAsCase(), second.template forceAsCase()); 616 | } 617 | 618 | template class Cmp, typename T_Case> 619 | template 620 | typename std::enable_if::value, bool>::type 621 | asenum::details::Comparator::compare(const ConcreteAsEnum&, const ConcreteAsEnum&) 622 | { 623 | static constexpr Cmp s_comparator; 624 | return s_comparator(true, true); 625 | } 626 | 627 | template class Cmp, typename T_Case, typename... T_Cases> 628 | bool asenum::details::Comparator::compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second) 629 | { 630 | bool finalVerdict = false; 631 | const bool result = Comparator::compare(first, second, finalVerdict); 632 | if (finalVerdict) 633 | { 634 | return result; 635 | } 636 | 637 | return Comparator::compare(first, second); 638 | } 639 | --------------------------------------------------------------------------------