├── src ├── dyn │ ├── CMakeLists.txt │ ├── preprocessor_utils.h │ └── dyn.h └── CMakeLists.txt ├── .github ├── scripts │ └── build.sh └── workflows │ └── ci.yml ├── CMakeLists.txt ├── Tests ├── CMakeLists.txt └── tests.cpp ├── .gitignore ├── LICENSE ├── .clang-format └── README.md /src/dyn/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(DYN PUBLIC 2 | dyn.h 3 | preprocessor_utils.h 4 | ) 5 | -------------------------------------------------------------------------------- /.github/scripts/build.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DDYN_BUILD_TESTS=ON 3 | cmake --build build --config Release -j 8 -v 4 | cd build/Tests/ 5 | ctest -C Release -VV 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project("Cpp Dyn" 4 | VERSION 1.0.0 5 | LANGUAGES CXX) 6 | 7 | option(DYN_BUILD_TESTS "Enable DYN tests" OFF) 8 | 9 | add_subdirectory(src/) 10 | 11 | if(DYN_BUILD_TESTS) 12 | message("Build tests") 13 | add_subdirectory(Tests/) 14 | endif() 15 | -------------------------------------------------------------------------------- /Tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ---- Tests ---- 2 | 3 | include(FetchContent) 4 | 5 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 6 | FetchContent_Declare(googletest 7 | GIT_REPOSITORY "https://github.com/google/googletest.git" 8 | GIT_TAG e2239ee6043f73722e7aa812a459f54a28552929 9 | UPDATE_DISCONNECTED YES) 10 | FetchContent_MakeAvailable(googletest) 11 | 12 | enable_testing() 13 | 14 | add_executable(Tests tests.cpp) 15 | target_link_libraries(Tests PRIVATE DYN::DYN gtest_main) 16 | add_test(NAME Tests COMMAND Tests) 17 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(DYN INTERFACE) 2 | 3 | add_subdirectory(dyn) 4 | 5 | set(CMAKE_CXX_EXTENSIONS false) 6 | 7 | #useful for IDEs 8 | set_property( 9 | TARGET DYN 10 | PROPERTY INTERFACE_SOURCES) 11 | 12 | target_include_directories(DYN INTERFACE 13 | "$") 14 | 15 | target_compile_features(DYN INTERFACE cxx_std_20) 16 | 17 | if(MSVC) 18 | target_compile_options(DYN INTERFACE /Zc:preprocessor /Zc:__cplusplus /permissive- /bigobj) 19 | endif() 20 | 21 | add_library(DYN::DYN ALIAS DYN) 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | *.txt.user 41 | 42 | # xemacs temporary files 43 | *.flc 44 | 45 | # Vim temporary files 46 | .*.swp 47 | 48 | # Visual Studio generated files 49 | *.ib_pdb_index 50 | *.idb 51 | *.ilk 52 | *.pdb 53 | *.sln 54 | *.suo 55 | *.vcproj 56 | *vcproj.*.*.user 57 | *.ncb 58 | *.sdf 59 | *.opensdf 60 | *.vcxproj 61 | *vcxproj.* 62 | 63 | # MinGW generated files 64 | *.Debug 65 | *.Release 66 | 67 | # Python byte code 68 | *.pyc 69 | 70 | # Binaries 71 | # -------- 72 | *.dll 73 | *.exe 74 | 75 | build-* 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Antoine MORRIER 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 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | builds: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | config: [ 17 | {"CC":gcc-10, "CXX":g++-10, "os":ubuntu-latest, "package": gcc-10 g++-10}, 18 | {"CC":gcc-11, "CXX":g++-11, "os":ubuntu-latest, "package": gcc-11 g++-11}, 19 | {"CC":clang-11, "CXX":clang++-11, "os":ubuntu-latest, "package": clang-11}, 20 | {"CC":clang-12, "CXX":clang++-12, "os":ubuntu-latest, "package": clang-12}, 21 | {"CC":cl, "CXX":cl, "os":windows-latest}] 22 | 23 | runs-on: ${{ matrix.config.os }} 24 | 25 | steps: 26 | - uses: actions/checkout@v2 27 | 28 | - if: ${{ matrix.config.os == 'ubuntu-latest' }} 29 | name: Preparation 30 | run: sudo apt install ${{ matrix.config.package }} 31 | 32 | - name: Launch 33 | env: 34 | CC: ${{ matrix.config.CC }} 35 | CXX: ${{ matrix.config.CXX }} 36 | run: ./.github/scripts/build.sh 37 | shell: bash 38 | -------------------------------------------------------------------------------- /src/dyn/preprocessor_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define DYN_STRINGIFY_IMPL(...) #__VA_ARGS__ 4 | #define DYN_STRINGIFY(...) DYN_STRINGIFY_IMPL(__VA_ARGS__) 5 | 6 | #define DYN_CAT_IMPL(x, ...) x##__VA_ARGS__ 7 | #define DYN_CAT(...) DYN_CAT_IMPL(__VA_ARGS__) 8 | 9 | #define DYN_EMPTY() 10 | #define DYN_EAT(...) 11 | #define DYN_DEFER(...) __VA_ARGS__ DYN_EMPTY() 12 | 13 | #define DYN_EXPAND(...) __VA_ARGS__ 14 | #define DYN_EVAL1(...) DYN_EXPAND(DYN_EXPAND(__VA_ARGS__)) 15 | #define DYN_EVAL2(...) DYN_EVAL1(DYN_EVAL1(__VA_ARGS__)) 16 | #define DYN_EVAL3(...) DYN_EVAL2(DYN_EVAL2(__VA_ARGS__)) 17 | #define DYN_EVAL4(...) DYN_EVAL3(DYN_EVAL3(__VA_ARGS__)) 18 | #define DYN_EVAL5(...) DYN_EVAL4(DYN_EVAL4(__VA_ARGS__)) 19 | #define DYN_EVAL6(...) DYN_EVAL5(DYN_EVAL5(__VA_ARGS__)) 20 | #define DYN_EVAL(...) DYN_EVAL6(DYN_EVAL6(__VA_ARGS__)) 21 | 22 | #define DYN_INC0 1 23 | #define DYN_INC1 2 24 | #define DYN_INC2 3 25 | #define DYN_INC3 4 26 | #define DYN_INC4 5 27 | #define DYN_INC5 6 28 | #define DYN_INC6 7 29 | #define DYN_INC7 8 30 | #define DYN_INC8 9 31 | #define DYN_INC(x) DYN_CAT(DYN_INC, x) 32 | 33 | #define DYN_HEAD(x, ...) x 34 | #define DYN_TAIL(x, ...) __VA_ARGS__ 35 | 36 | #define DYN_STRIP_PARENTHESIS(list) DYN_EXPAND list 37 | 38 | #define DYN_FOR_EACH_IMPL(macro, x, ...) macro(x) __VA_OPT__(DYN_DEFER(DYN_FOR_EACH_I)()(macro, __VA_ARGS__)) 39 | #define DYN_FOR_EACH_I() DYN_FOR_EACH_IMPL 40 | #define DYN_TRY_FOR_EACH(macro, ...) __VA_OPT__(DYN_FOR_EACH_IMPL(macro, __VA_ARGS__)) 41 | #define DYN_FOR_EACH(macro, list) DYN_EVAL(DYN_DEFER(DYN_TRY_FOR_EACH)(macro, DYN_STRIP_PARENTHESIS(list))) 42 | 43 | #define DYN_MAP_IMPL(macro, x, ...) macro(x) __VA_OPT__(DYN_DEFER(DYN_MAP_I)()(macro, __VA_ARGS__)) 44 | #define DYN_MAP_I() , DYN_MAP_IMPL 45 | #define DYN_TRY_MAP(macro, ...) __VA_OPT__(DYN_MAP_IMPL(macro, __VA_ARGS__)) 46 | #define DYN_MAP(macro, list) DYN_DEFER(DYN_TRY_MAP)(macro, DYN_STRIP_PARENTHESIS(list)) 47 | 48 | #define DYN_ENUMERATE_IMPL(n, x, ...) (n, x) __VA_OPT__(DYN_DEFER(DYN_ENUMERATE_I)()(DYN_INC(n), __VA_ARGS__)) 49 | #define DYN_ENUMERATE_I() , DYN_ENUMERATE_IMPL 50 | #define DYN_TRY_ENUMERATE(...) __VA_OPT__(DYN_ENUMERATE_IMPL(0, __VA_ARGS__)) 51 | #define DYN_ENUMERATE(list) (DYN_DEFER(DYN_TRY_ENUMERATE)(DYN_STRIP_PARENTHESIS(list))) 52 | -------------------------------------------------------------------------------- /Tests/tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | DYN_MAKE_INTERFACE(Openable, // 9 | (bool, open, (const std::string &, int)), // 10 | (bool, close, ()), // 11 | (bool, isOpen, () const)); 12 | 13 | struct Test1 { 14 | void open() {} 15 | }; 16 | 17 | struct Test2 { 18 | void open(std::string) {} 19 | void close(); 20 | }; 21 | 22 | struct Test3 { 23 | bool open(std::string, int) { return true; } 24 | }; 25 | 26 | struct Test4 { 27 | bool open(std::string, int) { return true; } 28 | bool close() { return true; } 29 | bool isOpen() const { return true; } 30 | }; 31 | 32 | static_assert(!Openable); 33 | static_assert(!Openable); 34 | static_assert(!Openable); 35 | static_assert(Openable); 36 | 37 | DYN_MAKE_INTERFACE(Computer, (int, compute, (int) const)); 38 | 39 | TEST(DYN_Tests, test_return) { 40 | struct C1 { 41 | int compute(int a) const { return a * 2; } 42 | }; 43 | 44 | struct C2 { 45 | int compute(int a) const { return a / 2; } 46 | }; 47 | 48 | dyn_Computer c1 = C1{}; 49 | dyn_Computer c2 = C2{}; 50 | 51 | ASSERT_EQ(c1.compute(10), 20); 52 | ASSERT_EQ(c2.compute(10), 5); 53 | } 54 | 55 | TEST(DYN_Tests, test_stack_storage) { 56 | struct C1 { 57 | int compute(int a) const { return a * 2; } 58 | }; 59 | 60 | struct C2 { 61 | int compute(int a) const { return a / 2; } 62 | }; 63 | 64 | dyn_Computer c1 = C1{}; 65 | dyn_Computer c2 = C2{}; 66 | 67 | ASSERT_EQ(c1.compute(10), 20); 68 | ASSERT_EQ(c2.compute(10), 5); 69 | } 70 | 71 | TEST(DYN_Tests, test_small_object_storage) { 72 | struct C1 { 73 | int compute(int a) const { return a * 2; } 74 | }; 75 | 76 | struct C2 { 77 | std::array unused; 78 | int compute(int a) const { return a / 2; } 79 | }; 80 | 81 | dyn_Computer c1 = C1{}; 82 | dyn_Computer::template storage> c2 = C2{}; 83 | 84 | ASSERT_EQ(c1.compute(10), 20); 85 | ASSERT_EQ(c2.compute(10), 5); 86 | } 87 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Attach 41 | BreakBeforeInheritanceComma: true 42 | BreakInheritanceList: AfterColon 43 | BreakBeforeTernaryOperators: true 44 | BreakConstructorInitializersBeforeComma: true 45 | BreakConstructorInitializers: AfterColon 46 | BreakAfterJavaFieldAnnotations: false 47 | BreakStringLiterals: true 48 | ColumnLimit: 120 49 | CommentPragmas: '^ IWYU pragma:' 50 | CompactNamespaces: false 51 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 52 | ConstructorInitializerIndentWidth: 4 53 | ContinuationIndentWidth: 4 54 | Cpp11BracedListStyle: true 55 | DerivePointerAlignment: false 56 | DisableFormat: false 57 | ExperimentalAutoDetectBinPacking: false 58 | FixNamespaceComments: true 59 | ForEachMacros: 60 | - foreach 61 | - Q_FOREACH 62 | - BOOST_FOREACH 63 | IncludeBlocks: Preserve 64 | IncludeCategories: 65 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 66 | Priority: 2 67 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 68 | Priority: 3 69 | - Regex: '.*' 70 | Priority: 1 71 | IncludeIsMainRegex: '(Test)?$' 72 | IndentCaseLabels: false 73 | IndentPPDirectives: None 74 | IndentWidth: 4 75 | IndentWrappedFunctionNames: false 76 | JavaScriptQuotes: Leave 77 | JavaScriptWrapImports: true 78 | KeepEmptyLinesAtTheStartOfBlocks: true 79 | MacroBlockBegin: '' 80 | MacroBlockEnd: '' 81 | MaxEmptyLinesToKeep: 1 82 | NamespaceIndentation: None 83 | ObjCBinPackProtocolList: Auto 84 | ObjCBlockIndentWidth: 2 85 | ObjCSpaceAfterProperty: false 86 | ObjCSpaceBeforeProtocolList: true 87 | PenaltyBreakAssignment: 2 88 | PenaltyBreakBeforeFirstCallParameter: 19 89 | PenaltyBreakComment: 300 90 | PenaltyBreakFirstLessLess: 120 91 | PenaltyBreakString: 1000 92 | PenaltyBreakTemplateDeclaration: 10 93 | PenaltyExcessCharacter: 1000000 94 | PenaltyReturnTypeOnItsOwnLine: 60 95 | PointerAlignment: Right 96 | ReflowComments: true 97 | SortIncludes: false 98 | SortUsingDeclarations: true 99 | SpaceAfterCStyleCast: false 100 | SpaceAfterTemplateKeyword: true 101 | SpaceBeforeAssignmentOperators: true 102 | SpaceBeforeCpp11BracedList: false 103 | SpaceBeforeCtorInitializerColon: true 104 | SpaceBeforeInheritanceColon: true 105 | SpaceBeforeParens: ControlStatements 106 | SpaceBeforeRangeBasedForLoopColon: true 107 | SpaceInEmptyParentheses: false 108 | SpacesBeforeTrailingComments: 1 109 | SpacesInAngles: false 110 | SpacesInContainerLiterals: true 111 | SpacesInCStyleCastParentheses: false 112 | SpacesInParentheses: false 113 | SpacesInSquareBrackets: false 114 | Standard: Cpp11 115 | StatementMacros: 116 | - Q_UNUSED 117 | - QT_REQUIRE_VERSION 118 | TabWidth: 4 119 | UseTab: Never 120 | ... 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cpp Dyn 2 | Cpp-Dyn tries to improve C++ runtime polymorphism. 3 | Indeed, C++ runtime polymorphism, originally, uses inheritance and virtual methods. 4 | Sean Parent, at least I believe he is the inventor of this idea, uses type erasure for runtime polymorphism. It is better because we can use value instead of pointer/reference, and we don't depend anymore on a base class. 5 | 6 | ## The Sean Parent Type Erasure Polymorphism 7 | Say we have a Drawable interface, the original way to handle such a thing is something close to 8 | ```cpp 9 | struct Drawable { 10 | void draw(std::ostream &stream) const = 0; 11 | }; 12 | 13 | struct Square : Drawable { 14 | void draw(std::ostream &stream) const override; 15 | }; 16 | 17 | struct Circle : Drawable { 18 | void draw(std::ostream &stream) const override; 19 | }; 20 | ``` 21 | 22 | Sean Parent proposes something close to: 23 | ```cpp 24 | struct Drawable { 25 | struct Concept { 26 | virtual void draw(std::ostream &stream) const = 0; 27 | }; 28 | 29 | template 30 | struct Model { 31 | Model(T x) : m_object{std::move(x)}{} 32 | void draw(std::ostream &stream) const override { 33 | m_object.draw(stream); 34 | } 35 | T m_object; 36 | }; 37 | 38 | template 39 | Drawable(T x) : m_ptr{std::make_unique>(std::move(x)){} 40 | }; 41 | 42 | struct Rectangle { 43 | void draw(std::ostream &stream) const; 44 | }; 45 | 46 | struct Circle { 47 | void draw(std::ostream &stream) const; 48 | }; 49 | 50 | Drawable rect = Rectangle{}; 51 | Drawable circle = Circle{}; 52 | ``` 53 | 54 | As we can see, it does not use inheritance, and we can use directly `Drawable` in a vector instead of a `unique_ptr`. 55 | 56 | ## In rust 57 | I am not a rust developer, but I like the idea behind runtime polymorphism in rust. You can achieve it through trait and `dyn` keyword. 58 | 59 | For example: 60 | ```rust 61 | pub trait Drawable { 62 | fn draw(&self); 63 | } 64 | 65 | Box drawable; 66 | ``` 67 | 68 | ## CppDyn 69 | **CppDyn** tries to bring the best from the two worlds. One issue with the Sean Parent Type Erasure is that it requires a lot of boilerplates... Metaclass may solve this issue, but it is not in C++ 20 and it is not merged (yet?) in C++ 23 either. **CppDyn** will generate the `Concept`and `Model` ~~class~~ boilerplates for you. 70 | We had two ways to generate such code: Python and extensive use of Macro. 71 | The advantage of Python (scripting in general) is the ease to maintain the code even if it means having another language to use. We could generate the code from a JSON file. However, the disadvantage is that the build becomes more complex to understand, and it will be more difficult to integrate it into other projects. 72 | The main issue with Macro is readability, however, all other issues are solved with this method. So, **CppDyn** uses Macro to generate the code. 73 | 74 | ### Basics 75 | **CppDyn** will generate one `concept` and one class that embeds the dynamic behavior. This class is a template. The template parameter allows you to set a storage _policy_. As such, the storage may be on the heap, on the stack, or a mix depending on the size of the object. 76 | 77 | ### Create your first interface 78 | The macro 79 | ```cpp 80 | #define DYN_MAKE_INTERFACE(name, methods...) 81 | ``` 82 | creates an interface name with the methods. 83 | The methods must be declared is as following: `(returnType, name, (args...) qualifier)` 84 | Let's say we want to create an `Openable` interface. This interface will embed three methods : 85 | ```cpp 86 | bool open(const std::string &path, int mode); 87 | bool close(); 88 | bool isOpen() const; 89 | ``` 90 | You are going to call the macro as follows: 91 | ```cpp 92 | DYN_MAKE_INTERFACE(Openable, // 93 | (bool, open, (const std::string&, int)), // 94 | (bool, close, ()), // 95 | (bool, isOpen, () const)); 96 | ``` 97 | As explained prior, this will generate 98 | 99 | 1. The concept Openable 100 | 2. The class template dyn_Openable 101 | 102 | Now, you can write such things : 103 | 104 | ```cpp 105 | struct AnOpenableThing{ 106 | /// code 107 | }; 108 | 109 | void f(Openable auto x) {} 110 | void g(dyn_Openable x) {} 111 | ``` 112 | The function `f` will be used for static polymorphism, the function `g` will be used for dynamic one. 113 | 114 | ### Some storage policies 115 | Here are the storage policies provided by **CppDyn** 116 | 117 | 1. `dyn::heap_storage`: It is a wrapper over a `unique_ptr` and allocates everything on the heap. 118 | 2. `dyn::stack_storage`: The maximum size of the `dyn_Object` will be N bytes. Generally, it will accept every object smaller than `N - sizeof(void*)`bytes. 119 | 3. `dyn::full_stack_storage`: Equivalent to `stack_storage`. Thus, it will accept every object smaller than N bytes. 120 | 4. `dyn::small_object_storage`: It is a mix between `full_stack_storage` and `heap_storage`. Every object smaller than N bytes will be allocated on the stack, and other ones will be allocated on the heap. 121 | 122 | Objects that are too heavy for any of stack storage will raise a compile-time error. 123 | For every storage policy but the `heap_storage`, **CppDyn** provides some macros to instance them easily. 124 | 125 | ```cpp 126 | #define DYN_STACK_STORAGE(size) 127 | #define DYN_FULL_STACK_STORAGE(size) 128 | #define DYN_SMALL_OBJECT_STORAGE(size) 129 | ``` 130 | Knowing that, you can write : 131 | ```cpp 132 | using HeapOpenable = dyn_Openable; 133 | using StackOpenable = dyn_Openable; 134 | using StackSmallOptimizedOpenable = dyn_Openable; 135 | ``` 136 | ### Create an interface with a custom storage 137 | You may want to use the following macro: 138 | ```cpp 139 | #define DYN_MAKE_INTERFACE_WITH_STORAGE(name, storage, methods...) 140 | ``` 141 | 142 | If you experience any bugs, feel free to report them, or to fix them. 143 | If you have any comments to say, feel free to report them too. 144 | 145 | Thanks a lot :) -------------------------------------------------------------------------------- /src/dyn/dyn.h: -------------------------------------------------------------------------------- 1 | #include "preprocessor_utils.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define DYN_FWD(x) ::std::forward(x) 9 | 10 | namespace dyn { 11 | template typename Model> 12 | class heap_storage { 13 | public: 14 | template 15 | constexpr heap_storage(T &&x) : m_ptr{std::make_unique>>(DYN_FWD(x))} {} 16 | constexpr Base *operator->() { return m_ptr.get(); } 17 | constexpr const Base *operator->() const { return m_ptr.get(); } 18 | 19 | private: 20 | std::unique_ptr m_ptr; 21 | }; 22 | 23 | namespace details { 24 | template typename Model> 25 | class stack_storage_impl { 26 | static_assert(Size > sizeof(Base *)); 27 | inline static constexpr auto true_size = Size - sizeof(Base *); 28 | inline static constexpr auto deleter = [](Base *base) { base->~Base(); }; 29 | 30 | public: 31 | template 32 | requires(sizeof(T) <= true_size) constexpr stack_storage_impl(T &&x) : 33 | m_ptr{new (m_storage.data()) Model>(DYN_FWD(x))} {} 34 | 35 | constexpr Base *operator->() { return m_ptr.get(); } 36 | constexpr const Base *operator->() const { return m_ptr.get(); } 37 | 38 | private: 39 | std::array m_storage; 40 | std::unique_ptr m_ptr; 41 | }; 42 | } // namespace details 43 | 44 | template 45 | struct stack_storage { 46 | template typename Model> 47 | using storage = details::stack_storage_impl; 48 | }; 49 | 50 | template 51 | using full_stack_storage = stack_storage; 52 | 53 | namespace details { 54 | template typename Model> 55 | class small_object_storage_impl { 56 | static_assert(Size >= sizeof(std::unique_ptr)); 57 | 58 | struct vtable_t { 59 | void (*dtor)(void *); 60 | Base *(*get)(void *); 61 | const Base *(*get_const)(const void *); 62 | }; 63 | 64 | template 65 | struct make_vtable; 66 | 67 | template 68 | struct make_vtable { 69 | static constexpr auto dtor = [](void *p) { static_cast> *>(p)->~unique_ptr(); }; 70 | 71 | static constexpr auto get = [](void *p) { 72 | return static_cast(static_cast> *>(p)->get()); 73 | }; 74 | 75 | static constexpr auto get_const = [](const void *p) { 76 | return static_cast(static_cast> *>(p)->get()); 77 | }; 78 | 79 | static constexpr auto vtable = vtable_t{dtor, get, get_const}; 80 | }; 81 | 82 | template 83 | struct make_vtable { 84 | static constexpr auto dtor = [](void *p) { static_cast *>(p)->~Model(); }; 85 | 86 | static constexpr auto get = [](void *p) { return static_cast(static_cast *>(p)); }; 87 | 88 | static constexpr auto get_const = [](const void *p) { 89 | return static_cast(static_cast *>(p)); 90 | }; 91 | 92 | static constexpr auto vtable = vtable_t{dtor, get, get_const}; 93 | }; 94 | 95 | const vtable_t *m_vtable; 96 | std::array m_storage; 97 | 98 | public: 99 | template 100 | constexpr small_object_storage_impl(T &&t) : m_vtable{&make_vtable>::vtable} { 101 | if constexpr (sizeof(t) <= Size) { 102 | new (m_storage.data()) Model{DYN_FWD(t)}; 103 | } else { 104 | new (m_storage.data()) std::unique_ptr>{std::make_unique>(DYN_FWD(t))}; 105 | } 106 | } 107 | 108 | constexpr Base *operator->() { return m_vtable->get(m_storage.data()); } 109 | constexpr const Base *operator->() const { return m_vtable->get_const(m_storage.data()); } 110 | }; 111 | } // namespace details 112 | 113 | template 114 | struct small_object_storage { 115 | template typename Model> 116 | using storage = details::small_object_storage_impl; 117 | }; 118 | 119 | } // namespace dyn 120 | 121 | #define DYN_KEEP_CONST(...) DYN_EAT __VA_ARGS__ 122 | #define DYN_REMOVE_CONST_IMPL(...) __VA_ARGS__ DYN_EAT ( 123 | #define DYN_REMOVE_CONST(...) (DYN_REMOVE_CONST_IMPL __VA_ARGS__ ) ) 124 | 125 | #define DYN_MAKE_DECLVAL_CALL(type) ::std::declval() 126 | #define DYN_MAKE_CONCEPT_INTERFACE_IMPL(r, name, args) \ 127 | { \ 128 | std::declval().name(DYN_MAP(DYN_MAKE_DECLVAL_CALL, DYN_REMOVE_CONST(args))) \ 129 | } -> ::std::same_as; 130 | 131 | #define DYN_MAKE_CONCEPT_INTERFACE(...) DYN_MAKE_CONCEPT_INTERFACE_IMPL __VA_ARGS__ 132 | 133 | #define DYN_MAKE_CONCEPT(nameConcept, listInterface) \ 134 | template \ 135 | concept nameConcept = requires() { \ 136 | DYN_FOR_EACH(DYN_MAKE_CONCEPT_INTERFACE, listInterface) \ 137 | } 138 | 139 | #define DYN_MAKE_INTERFACE_VIRTUAL_IMPL(r, name, args) virtual r name DYN_REMOVE_CONST(args) DYN_KEEP_CONST(args) = 0; 140 | #define DYN_MAKE_INTERFACE_VIRTUAL(...) DYN_MAKE_INTERFACE_VIRTUAL_IMPL __VA_ARGS__ 141 | 142 | #define DYN_MAKE_ARGUMENT_IMPL(n, type) type DYN_CAT(_, n) 143 | #define DYN_MAKE_ARGUMENT(nAndType) DYN_MAKE_ARGUMENT_IMPL nAndType 144 | 145 | #define DYN_MAKE_FWD_ARGUMENT_IMPL(n, type) DYN_FWD(DYN_CAT(_, n)) 146 | #define DYN_MAKE_FWD_ARGUMENT(nAndType) DYN_MAKE_FWD_ARGUMENT_IMPL nAndType 147 | 148 | #define DYN_MAKE_INTERFACE_IMPLEMENTATION_IMPL(r, name, args) \ 149 | r name(DYN_MAP(DYN_MAKE_ARGUMENT, DYN_ENUMERATE(DYN_REMOVE_CONST(args)))) DYN_KEEP_CONST(args) override { \ 150 | return object.name(DYN_MAP(DYN_MAKE_FWD_ARGUMENT, DYN_ENUMERATE(DYN_REMOVE_CONST(args)))); \ 151 | } 152 | #define DYN_MAKE_INTERFACE_IMPLEMENTATION(...) DYN_MAKE_INTERFACE_IMPLEMENTATION_IMPL __VA_ARGS__ 153 | 154 | #define DYN_MAKE_INTERFACE_IMPLEMENTATION_PTR_IMPL(r, name, args) \ 155 | r name(DYN_MAP(DYN_MAKE_ARGUMENT, DYN_ENUMERATE(DYN_REMOVE_CONST(args)))) DYN_KEEP_CONST(args) { \ 156 | return m_storage->name(DYN_MAP(DYN_MAKE_FWD_ARGUMENT, DYN_ENUMERATE(DYN_REMOVE_CONST(args)))); \ 157 | } 158 | #define DYN_MAKE_INTERFACE_IMPLEMENTATION_PTR(...) DYN_MAKE_INTERFACE_IMPLEMENTATION_PTR_IMPL __VA_ARGS__ 159 | 160 | #define DYN_MAKE_INTERFACE_CLASS(name, nameConcept, storage, listInterface) \ 161 | template