├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── benchmarks ├── CMakeLists.txt └── benchmarks.cpp ├── erased ├── CMakeLists.txt └── include │ └── erased │ ├── erased.h │ ├── ref.h │ └── utils │ └── utils.h └── tests ├── CMakeLists.txt └── tests.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | *.slo 3 | *.lo 4 | *.o 5 | *.a 6 | *.la 7 | *.lai 8 | *.so 9 | *.so.* 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | object_script.*.Release 15 | object_script.*.Debug 16 | *_plugin_import.cpp 17 | /.qmake.cache 18 | /.qmake.stash 19 | *.pro.user 20 | *.pro.user.* 21 | *.qbs.user 22 | *.qbs.user.* 23 | *.moc 24 | moc_*.cpp 25 | moc_*.h 26 | qrc_*.cpp 27 | ui_*.h 28 | *.qmlc 29 | *.jsc 30 | Makefile* 31 | *build-* 32 | *.qm 33 | *.prl 34 | 35 | # Qt unit tests 36 | target_wrapper.* 37 | 38 | # QtCreator 39 | *.autosave 40 | 41 | # QtCreator Qml 42 | *.qmlproject.user 43 | *.qmlproject.user.* 44 | 45 | # QtCreator CMake 46 | CMakeLists.txt.user* 47 | 48 | # QtCreator 4.8< compilation database 49 | compile_commands.json 50 | 51 | # QtCreator local machine specific files for imported projects 52 | *creator.user* 53 | 54 | *_qmlcache.qrc 55 | build/ 56 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.30) 2 | include(FetchContent) 3 | 4 | project(erased LANGUAGES CXX) 5 | 6 | add_subdirectory(erased) 7 | add_subdirectory(tests) 8 | add_subdirectory(benchmarks) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Erased 2 | Erased is your constexpr friendly type erasure wrapper type. 3 | It is a C++ type-erasure implementation developed with performance and ease of use in mind. 4 | 5 | It is heavily inspired by [AnyAny](https://github.com/kelbon/AnyAny). 6 | 7 | The [tested compilers](https://godbolt.org/z/GMe3EPvbd) are: 8 | 1. MSVC (19.32 and above): `constexpr` does not work since MSVC does not yet handle `virtual` functions in a constexpr context. 9 | 2. Clang (19.1 and above) 10 | 3. GCC (14.1 and above) 11 | 12 | ## Erased types 13 | Erased provides two main types inside the namespace `erased`: 14 | 15 | 1. erased: It is a owning type-erasure type 16 | 2. ref: It is a non owning type-erasure type 17 | 18 | Both are meant to be used the same way (except you are responsible for the lifetime of `ref` object). 19 | 20 | ## `erased::erased` 21 | Here are the currently supported features 22 | 1. `constexpr` friendly 23 | 2. Small Object Optimization 24 | 3. Copy and `noexcept` move operations 25 | 4. Extendible with multiple behaviors 26 | 5. Macros helper to remove more and more boilerplate. 27 | 6. Natural syntax at call site. 28 | 29 | Here is an example of use: 30 | 31 | ```cpp 32 | struct Draw { 33 | constexpr static void invoker(const auto &self, std::ostream &stream) { 34 | return self.draw(stream); 35 | } 36 | 37 | // not necessary, but makes the client code easier to write 38 | constexpr void draw(this const auto &erased, std::ostream &stream) { 39 | return erased.invoke(Draw{}, stream); 40 | } 41 | }; 42 | 43 | using Drawable = erased::erased; 44 | 45 | void render(const Drawable &x) { 46 | x.draw(std::cout); 47 | } 48 | 49 | struct Circle { 50 | void draw(std::ostream &stream) const { 51 | stream << "Circle\n"; 52 | } 53 | }; 54 | 55 | struct Rectangle { 56 | void draw(std::ostream &stream) const { 57 | stream << "Rectangle\n"; 58 | } 59 | }; 60 | 61 | int main() { 62 | Circle c; 63 | Rectangle r; 64 | render(c); 65 | render(r); 66 | } 67 | ``` 68 | 69 |
70 | Here is the same with macros 71 | 72 | ```cpp 73 | ERASED_MAKE_BEHAVIOR(Draw, draw, 74 | (const &self, std::ostream &stream) requires(self.draw(), stream)->void); 75 | 76 | using Drawable = erased::erased; 77 | ``` 78 |
79 | 80 | ## `erased::ref` 81 | 82 | ```cpp 83 | using DrawableRef = erased::ref; 84 | 85 | void renderRef(DrawableRef ref) { 86 | ref.draw(std::cout); 87 | } 88 | 89 | int main() { 90 | Circle c; 91 | Rectangle r; 92 | renderRef(c); 93 | renderRef(r); 94 | } 95 | ``` 96 | 97 | ## Erased provided behaviors 98 | The `erased::erased` type has only a constructor and destructor by default. We provide these behaviors to extend easily the given type: 99 | 1. Copy: Add copy constructor and copy assignment operator 100 | 2. Move: Add noexcept move constructor and noexcept move assignment operator 101 | 102 | For example, if you want to have a copyable and movable Drawable, you can do: 103 | 104 | ```cpp 105 | // Draw behavior 106 | using Drawable = erased::erased; 107 | ``` 108 | 109 | We plan to add new default behaviors such as stream operators, arithmetic operators, or `toString` behaviors. 110 | 111 | 112 | ## Thanks 113 | Here is the list of people who help me to develop and test this library: 114 | 1. [Théo Devaucoup](https://github.com/theo-dep) -------------------------------------------------------------------------------- /benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | FetchContent_Declare(googlebench 2 | GIT_REPOSITORY https://github.com/google/benchmark 3 | GIT_TAG v1.9.1 4 | ) 5 | 6 | set(BENCHMARK_ENABLE_TESTING OFF) 7 | 8 | FetchContent_MakeAvailable(googlebench) 9 | 10 | add_executable(Benchmarks benchmarks.cpp) 11 | target_link_libraries(Benchmarks PRIVATE erased::erased erased::warnings benchmark::benchmark) 12 | 13 | -------------------------------------------------------------------------------- /benchmarks/benchmarks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace er { 6 | struct ComputeArea { 7 | constexpr static double invoker(const auto &self) { 8 | return self.computeArea(); 9 | } 10 | 11 | // not necessary, but makes the client code easier to write 12 | constexpr double computeArea(this const auto &erased) { 13 | return erased.invoke(ComputeArea{}); 14 | } 15 | }; 16 | 17 | struct Perimeter { 18 | constexpr static double invoker(const auto &self) { return self.perimeter(); } 19 | 20 | // not necessary, but makes the client code easier to write 21 | constexpr double perimeter(this const auto &erased) { 22 | return erased.invoke(Perimeter{}); 23 | } 24 | }; 25 | 26 | struct Circle { 27 | constexpr double computeArea() const { return m_radius * m_radius * 3.14; } 28 | constexpr double perimeter() const { return m_radius * 6.28; } 29 | 30 | double m_radius = 1.0; 31 | }; 32 | 33 | struct Rectangle { 34 | constexpr double computeArea() const { return 1.0; } 35 | constexpr double perimeter() const { return 4.0; } 36 | }; 37 | 38 | struct BigCircle { 39 | std::byte padding[100]; 40 | double m_radius = 1.0; 41 | constexpr double computeArea() const { return m_radius * m_radius * 3.14; } 42 | constexpr double perimeter() const { return m_radius * 6.28; } 43 | }; 44 | 45 | struct BigRectangle { 46 | std::byte padding[100]; 47 | constexpr double computeArea() const { return 1.0; } 48 | constexpr double perimeter() const { return 4.0; } 49 | }; 50 | 51 | using Surface = erased::erased; 52 | 53 | template auto createSurfaces() { 54 | return std::array{Surface(Ts())...}; 55 | } 56 | 57 | auto createLotSurfaces() { 58 | std::vector surfaces; 59 | for (int i = 0; i < 1000; ++i) 60 | surfaces.emplace_back(std::in_place_type); 61 | return surfaces; 62 | } 63 | 64 | } // namespace er 65 | 66 | namespace vt { 67 | struct ISurface { 68 | constexpr virtual ~ISurface() = default; 69 | constexpr virtual double computeArea() const = 0; 70 | constexpr virtual double perimeter() const = 0; 71 | }; 72 | 73 | struct Circle : ISurface { 74 | constexpr double computeArea() const { return m_radius * m_radius * 3.14; } 75 | constexpr double perimeter() const { return m_radius * 6.28; } 76 | 77 | double m_radius = 1.0; 78 | }; 79 | 80 | struct Rectangle : ISurface { 81 | constexpr double computeArea() const override { return 1.0; } 82 | constexpr double perimeter() const override { return 4.0; } 83 | }; 84 | 85 | struct BigCircle : ISurface { 86 | std::byte padding[100]; 87 | double m_radius = 1.0; 88 | constexpr double computeArea() const { return m_radius * m_radius * 3.14; } 89 | constexpr double perimeter() const { return m_radius * 6.28; } 90 | }; 91 | struct BigRectangle : ISurface { 92 | std::byte padding[100]; 93 | constexpr double computeArea() const override { return 1.0; } 94 | constexpr double perimeter() const override { return 4.0; } 95 | }; 96 | 97 | template auto createSurfaces() { 98 | return std::array, sizeof...(Ts)>{ 99 | std::make_unique()...}; 100 | } 101 | 102 | auto createLotSurfaces() { 103 | std::vector> surfaces; 104 | for (int i = 0; i < 1000; ++i) 105 | surfaces.emplace_back(std::make_unique()); 106 | return surfaces; 107 | } 108 | 109 | } // namespace vt 110 | 111 | template void testConstructErased(benchmark::State &state) { 112 | for (auto &&_ : state) 113 | benchmark::DoNotOptimize(er::createSurfaces()); 114 | } 115 | 116 | template void testConstructVTable(benchmark::State &state) { 117 | for (auto &&_ : state) 118 | benchmark::DoNotOptimize(vt::createSurfaces()); 119 | } 120 | 121 | template void testCallErased(benchmark::State &state) { 122 | auto surfaces = er::createSurfaces(); 123 | 124 | for (auto &&_ : state) { 125 | for (auto &&surface : surfaces) 126 | benchmark::DoNotOptimize(surface.computeArea() + surface.perimeter()); 127 | } 128 | } 129 | 130 | template void testCallVTable(benchmark::State &state) { 131 | auto surfaces = vt::createSurfaces(); 132 | 133 | for (auto &&_ : state) { 134 | for (auto &&surface : surfaces) 135 | benchmark::DoNotOptimize(surface->computeArea() + surface->perimeter()); 136 | } 137 | } 138 | 139 | void testCallLotErased(benchmark::State &state) { 140 | auto surfaces = er::createLotSurfaces(); 141 | for (auto &&_ : state) { 142 | for (auto &&surface : surfaces) 143 | benchmark::DoNotOptimize(surface.computeArea() + surface.perimeter()); 144 | } 145 | } 146 | 147 | void testCallLotVTable(benchmark::State &state) { 148 | auto surfaces = vt::createLotSurfaces(); 149 | for (auto &&_ : state) { 150 | for (auto &&surface : surfaces) 151 | benchmark::DoNotOptimize(surface->computeArea() + surface->perimeter()); 152 | } 153 | } 154 | 155 | BENCHMARK(testConstructErased); 156 | BENCHMARK(testConstructVTable); 157 | BENCHMARK(testCallErased); 158 | BENCHMARK(testCallVTable); 159 | 160 | BENCHMARK(testConstructErased); 161 | BENCHMARK(testConstructVTable); 162 | BENCHMARK(testCallErased); 163 | BENCHMARK(testCallVTable); 164 | 165 | BENCHMARK(testCallLotErased); 166 | BENCHMARK(testCallLotVTable); 167 | 168 | BENCHMARK_MAIN(); 169 | -------------------------------------------------------------------------------- /erased/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(erased INTERFACE) 2 | 3 | target_compile_features(erased INTERFACE cxx_std_26) 4 | target_include_directories(erased INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) 5 | 6 | target_sources(erased 7 | PUBLIC 8 | FILE_SET erased_headers 9 | TYPE HEADERS 10 | BASE_DIRS ./include/ 11 | FILES 12 | include/erased/erased.h 13 | include/erased/ref.h 14 | include/erased/utils/utils.h 15 | ) 16 | 17 | set(clang_cxx "$") 18 | set(gcc_cxx "$") 19 | set(msvc_cxx "$") 20 | 21 | add_library(erased_sanitizer INTERFACE) 22 | 23 | target_compile_options(erased_sanitizer INTERFACE 24 | $<${clang_cxx}:-fsanitize=address;-fsanitize=undefined> 25 | $<${msvc_cxx}:/fsanitize=address;/Zc:preprocessor> 26 | ) 27 | 28 | target_link_options(erased_sanitizer INTERFACE 29 | $<${clang_cxx}:-fsanitize=address> 30 | ) 31 | 32 | target_compile_options(erased_sanitizer INTERFACE 33 | $<${msvc_cxx}:-D_DISABLE_VECTOR_ANNOTATION;-D_DISABLE_STRING_ANNOTATION> 34 | ) 35 | 36 | add_library(erased_warnings INTERFACE) 37 | 38 | target_compile_options(erased_warnings INTERFACE 39 | $<${clang_cxx}:-Wall -Wextra -Werror> 40 | $<${gcc_cxx}:-Wall -Wextra -Werror> 41 | ) 42 | 43 | add_library(erased::erased ALIAS erased) 44 | add_library(erased::sanitizer ALIAS erased_sanitizer) 45 | add_library(erased::warnings ALIAS erased_warnings) 46 | 47 | -------------------------------------------------------------------------------- /erased/include/erased/erased.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "utils/utils.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define fwd(x) static_cast(x) 10 | 11 | namespace erased { 12 | 13 | struct Copy { 14 | template 15 | static constexpr void *invoker(const T &object, void *sooPtr, 16 | std::size_t sooSize) { 17 | if (sizeof object > sooSize || std::is_constant_evaluated()) 18 | return new T{object}; 19 | else { 20 | return new (sooPtr) T{object}; 21 | } 22 | } 23 | }; 24 | 25 | struct Move { 26 | template 27 | static constexpr void *invoker(T &object, void *sooPtr, std::size_t sooSize) { 28 | if (sizeof object > sooSize || std::is_constant_evaluated()) 29 | return new T{std::move(object)}; 30 | else { 31 | return new (sooPtr) T{std::move(object)}; 32 | } 33 | } 34 | }; 35 | 36 | namespace details { 37 | template struct Destructor { 38 | template static constexpr void invoker(T &, Soo *soo) { 39 | soo->template destroy(); 40 | } 41 | }; 42 | 43 | template struct soo { 44 | using vtable = details::vtable>; 45 | static constexpr auto buffer_size = 46 | Size - sizeof(void *) - sizeof(const vtable *); 47 | std::array m_buffer; 48 | void *ptr = nullptr; 49 | const vtable *vtable_ptr = nullptr; 50 | 51 | template 52 | constexpr T *construct(Args &&...args) { 53 | static_assert(sizeof(soo) == Size); 54 | if constexpr (sizeof(T) <= buffer_size) { 55 | if (std::is_constant_evaluated()) { 56 | ptr = new T{fwd(args)...}; 57 | } else { 58 | ptr = new (m_buffer.data()) T{fwd(args)...}; 59 | } 60 | } else { 61 | ptr = new T{fwd(args)...}; 62 | } 63 | vtable_ptr = vtable::template construct_for(); 64 | return static_cast(ptr); 65 | } 66 | 67 | template constexpr T *get() noexcept { 68 | return static_cast(ptr); 69 | } 70 | 71 | template constexpr const T *get() const noexcept { 72 | return static_cast(ptr); 73 | } 74 | 75 | template constexpr void destroy() noexcept { 76 | T *ptr = get(); 77 | if constexpr (sizeof(T) <= buffer_size) { 78 | if (std::is_constant_evaluated()) { 79 | delete ptr; 80 | } else { 81 | std::destroy_at(ptr); 82 | } 83 | } else { 84 | delete ptr; 85 | } 86 | } 87 | }; 88 | 89 | template constexpr bool contains() { 90 | return (std::is_same_v || ...); 91 | } 92 | } // namespace details 93 | 94 | template struct basic_erased; 95 | 96 | template struct is_erased : std::false_type {}; 97 | 98 | template 99 | struct is_erased> : std::true_type {}; 100 | 101 | template constexpr bool is_erased_v = is_erased::value; 102 | 103 | template 104 | concept erased_concept = is_erased_v>; 105 | 106 | template 107 | struct alignas(Size) basic_erased : public Methods... { 108 | using soo = details::soo; 109 | 110 | static constexpr bool copyable = details::contains(); 111 | static constexpr bool movable = details::contains(); 112 | 113 | template 114 | constexpr basic_erased(std::in_place_type_t, auto &&...args) noexcept { 115 | m_soo.template construct(fwd(args)...); 116 | } 117 | 118 | template 119 | constexpr basic_erased(T x) noexcept 120 | : basic_erased{std::in_place_type, static_cast(x)} {} 121 | 122 | template 123 | constexpr decltype(auto) invoke(Method, auto &&...xs) const { 124 | return m_soo.vtable_ptr->template get()( 125 | static_cast(m_soo.ptr), fwd(xs)...); 126 | } 127 | 128 | template 129 | constexpr decltype(auto) invoke(Method, auto &&...xs) { 130 | return m_soo.vtable_ptr->template get()(m_soo.ptr, fwd(xs)...); 131 | } 132 | 133 | constexpr basic_erased(basic_erased &&other) noexcept 134 | requires movable 135 | { 136 | m_soo.vtable_ptr = other.m_soo.vtable_ptr; 137 | m_soo.ptr = other.invoke(Move{}, static_cast(m_soo.m_buffer.data()), 138 | soo::buffer_size); 139 | } 140 | 141 | constexpr basic_erased &operator=(basic_erased &&other) noexcept 142 | requires movable 143 | { 144 | destroy(); 145 | m_soo.vtable_ptr = other.m_soo.vtable_ptr; 146 | m_soo.ptr = other.invoke(Move{}, static_cast(m_soo.m_buffer.data()), 147 | soo::buffer_size); 148 | return *this; 149 | } 150 | 151 | constexpr basic_erased(const basic_erased &other) 152 | requires copyable 153 | { 154 | m_soo.vtable_ptr = other.m_soo.vtable_ptr; 155 | m_soo.ptr = other.invoke(Copy{}, static_cast(m_soo.m_buffer.data()), 156 | soo::buffer_size); 157 | } 158 | 159 | constexpr basic_erased &operator=(const basic_erased &other) 160 | requires copyable 161 | { 162 | destroy(); 163 | m_soo.vtable_ptr = other.m_soo.vtable_ptr; 164 | m_soo.ptr = other.invoke(Copy{}, static_cast(m_soo.m_buffer.data()), 165 | soo::buffer_size); 166 | return *this; 167 | } 168 | 169 | constexpr void destroy() { 170 | invoke(details::Destructor{}, &m_soo); 171 | } 172 | 173 | constexpr ~basic_erased() { destroy(); } 174 | 175 | template 176 | friend constexpr bool is(const basic_erased &object); 177 | 178 | template 179 | friend constexpr auto *any_cast(Erased *object); 180 | 181 | template 182 | friend constexpr auto &&any_cast(Erased &&object); 183 | 184 | private: 185 | soo m_soo; 186 | }; 187 | 188 | template using erased = basic_erased<32, Methods...>; 189 | 190 | template 191 | constexpr bool is(const basic_erased &object) { 192 | using soo = typename basic_erased::soo; 193 | using vtable = typename soo::vtable; 194 | 195 | return object.m_soo.vtable_ptr == vtable::template construct_for(); 196 | } 197 | 198 | template 199 | constexpr auto *any_cast(Erased *object) { 200 | if (is(*object)) 201 | return object->m_soo.template get(); 202 | return decltype(object->m_soo.template get()){nullptr}; 203 | } 204 | 205 | template 206 | constexpr auto &&any_cast(Erased &&object) { 207 | if (is(object)) 208 | return std::forward_like(*object.m_soo.template get()); 209 | throw std::bad_cast(); 210 | } 211 | } // namespace erased 212 | 213 | #undef fwd 214 | 215 | #define ERASED_HEAD(a, ...) a 216 | #define ERASED_TAIL(a, ...) __VA_ARGS__ 217 | #define ERASED_EAT(...) 218 | #define ERASED_EXPAND(...) __VA_ARGS__ 219 | 220 | #define ERASED_REMOVE_PARENTHESIS_IMPL(...) __VA_ARGS__ 221 | #define ERASED_REMOVE_PARENTHESIS(...) \ 222 | ERASED_EXPAND(ERASED_REMOVE_PARENTHESIS_IMPL __VA_ARGS__) 223 | 224 | #define ERASED_ADD_COMA_AFTER_PARENTHESIS_IMPL(...) (__VA_ARGS__), 225 | #define ERASED_ADD_COMA_AFTER_PARENTHESIS(...) \ 226 | (ERASED_EXPAND(ERASED_ADD_COMA_AFTER_PARENTHESIS_IMPL __VA_ARGS__)) 227 | 228 | #define ERASED_REMOVE_AFTER_PARENTHESIS(...) \ 229 | ERASED_REMOVE_PARENTHESIS( \ 230 | ERASED_HEAD ERASED_ADD_COMA_AFTER_PARENTHESIS(__VA_ARGS__)) 231 | 232 | #define ERASED_CAT_IMPL(a, b) a##b 233 | #define ERASED_CAT(...) ERASED_CAT_IMPL(__VA_ARGS__) 234 | #define ERASED_GET_AFTER_requires(...) (__VA_ARGS__) 235 | #define ERASED_GET_INSIDE_REQUIRES(...) \ 236 | ERASED_REMOVE_AFTER_PARENTHESIS( \ 237 | ERASED_CAT(ERASED_GET_AFTER_, ERASED_EAT __VA_ARGS__)) 238 | 239 | #define ERASED_GET_TRAILING_RETURN(...) \ 240 | ERASED_EXPAND(ERASED_TAIL ERASED_ADD_COMA_AFTER_PARENTHESIS( \ 241 | ERASED_CAT(ERASED_GET_AFTER_, ERASED_EAT __VA_ARGS__))) 242 | 243 | #define ERASED_MAKE_BEHAVIOR(Name, name, signature) \ 244 | struct Name { \ 245 | static constexpr auto \ 246 | invoker(auto ERASED_REMOVE_AFTER_PARENTHESIS(signature)) \ 247 | ERASED_GET_TRAILING_RETURN(signature) { \ 248 | return ERASED_GET_INSIDE_REQUIRES(signature); \ 249 | } \ 250 | \ 251 | constexpr decltype(auto) name(this auto &&self, auto &&...args) { \ 252 | return self.invoke(Name{}, static_cast(args)...); \ 253 | } \ 254 | } 255 | 256 | #undef fwd 257 | -------------------------------------------------------------------------------- /erased/include/erased/ref.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "utils/utils.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace erased { 10 | 11 | template class ref; 12 | 13 | template struct is_ref : std::false_type {}; 14 | 15 | template 16 | struct is_ref> : std::true_type {}; 17 | 18 | template constexpr auto is_ref_v = is_ref::value; 19 | 20 | template 21 | concept ref_concept = is_ref_v>; 22 | 23 | template class ref : public Methods... { 24 | static constexpr auto all_const = 25 | (details::method_to_trait_t::is_const && ...); 26 | 27 | using vtable = details::vtable; 28 | 29 | public: 30 | template 31 | constexpr ref(T &object) noexcept 32 | : m_ptr{std::addressof(object)}, 33 | m_vtable{vtable::template construct_for()} {} 34 | 35 | template 36 | constexpr decltype(auto) invoke(Method, Args &&...args) const { 37 | return m_vtable->template get()(m_ptr, static_cast(args)...); 38 | } 39 | 40 | template 41 | friend constexpr bool is(const ref &object); 42 | 43 | template 44 | friend constexpr auto *any_cast(Erased *object); 45 | 46 | template 47 | friend constexpr auto &any_cast(Erased &&object); 48 | 49 | private: 50 | details::fast_conditional::template apply 51 | m_ptr; 52 | const vtable *m_vtable; 53 | }; 54 | 55 | template 56 | constexpr bool is(const ref &object) { 57 | return object.m_vtable == 58 | ref::vtable::template construct_for(); 59 | } 60 | 61 | template 62 | constexpr auto *any_cast(Erased *object) { 63 | if constexpr (std::decay_t::all_const || 64 | std::is_const_v>) { 65 | if (is(*object)) { 66 | return static_cast(object->m_ptr); 67 | } 68 | return static_cast(nullptr); 69 | } else { 70 | if (is(*object)) 71 | return static_cast(object->m_ptr); 72 | return static_cast(nullptr); 73 | } 74 | } 75 | 76 | template 77 | constexpr auto &any_cast(Erased &&object) { 78 | if constexpr (std::decay_t::all_const || 79 | std::is_const_v>) { 80 | if (is(object)) 81 | return *static_cast(object.m_ptr); 82 | } else { 83 | if (is(object)) 84 | return *static_cast(object.m_ptr); 85 | } 86 | throw std::bad_cast{}; 87 | } 88 | 89 | } // namespace erased 90 | -------------------------------------------------------------------------------- /erased/include/erased/utils/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace erased::details { 8 | struct erased_type_t {}; 9 | 10 | template struct fast_conditional { 11 | template using apply = T; 12 | }; 13 | 14 | template <> struct fast_conditional { 15 | template using apply = F; 16 | }; 17 | 18 | template struct method_to_trait; 19 | 20 | template 22 | struct method_to_trait { 23 | static constexpr bool is_const = std::is_const_v; 24 | 25 | using first_argument = 26 | typename fast_conditional::template apply; 27 | 28 | using type = ReturnType (*)(first_argument, Args...); 29 | 30 | template static constexpr auto create_invoker_for() { 31 | return +[](first_argument first, Args... args) { 32 | if constexpr (is_const) { 33 | return Method::invoker(*static_cast(first), 34 | static_cast(args)...); 35 | } else { 36 | return Method::invoker(*static_cast(first), 37 | static_cast(args)...); 38 | } 39 | }; 40 | } 41 | }; 42 | 43 | template 44 | using method_to_trait_t = 45 | method_to_trait)>; 46 | 47 | template 48 | using method_ptr = typename method_to_trait_t::type; 49 | 50 | template constexpr int index_in_list() { 51 | std::array array = {std::is_same_v...}; 52 | for (int i = 0; i < static_cast(array.size()); ++i) 53 | if (array[i]) 54 | return i; 55 | return -1; 56 | } 57 | 58 | template struct vtable { 59 | constexpr vtable(method_ptr... ptrs) : m_functions{ptrs...} {} 60 | 61 | template 62 | static constexpr const vtable *construct_for() noexcept { 63 | static constexpr vtable vtable( 64 | method_to_trait_t::template create_invoker_for()...); 65 | return &vtable; 66 | } 67 | 68 | template constexpr auto *get() const noexcept { 69 | constexpr int index = index_in_list(); 70 | return std::get(m_functions); 71 | } 72 | 73 | std::tuple...> m_functions; 74 | }; 75 | } // namespace erased::details 76 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | FetchContent_Declare(googletest 2 | GIT_REPOSITORY https://github.com/google/googletest 3 | GIT_TAG v1.15.2 4 | ) 5 | 6 | FetchContent_MakeAvailable(googletest) 7 | 8 | add_executable(Tests tests.cpp) 9 | target_link_libraries(Tests PRIVATE erased::erased erased::sanitizer erased::warnings gtest_main gtest) 10 | -------------------------------------------------------------------------------- /tests/tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | ERASED_MAKE_BEHAVIOR(ComputeArea, computeArea, 6 | (&self) requires(self.computeArea())->double); 7 | ERASED_MAKE_BEHAVIOR(Perimeter, perimeter, 8 | (const &self) requires(self.perimeter())->double); 9 | 10 | using Surface = 11 | erased::erased; 12 | 13 | using SurfaceRef = erased::ref; 14 | 15 | struct Circle { 16 | constexpr double computeArea() { return radius * radius * 3.14; } 17 | constexpr double perimeter() const { return radius * 6.28; } 18 | 19 | double radius = 1.0; 20 | }; 21 | 22 | struct Rectangle { 23 | constexpr double computeArea() { return a * b; } 24 | constexpr double perimeter() const { return 2.0 * (a + b); } 25 | 26 | double a = 1.0; 27 | double b = 1.0; 28 | }; 29 | 30 | using MoveOnlySurface = erased::erased; 31 | using CopyOnlySurface = erased::erased; 32 | 33 | using OnlySurface = erased::erased; 34 | 35 | static_assert(std::is_nothrow_move_constructible_v); 36 | static_assert(std::is_nothrow_move_assignable_v); 37 | static_assert(std::is_copy_constructible_v); 38 | static_assert(std::is_copy_assignable_v); 39 | 40 | static_assert(std::is_nothrow_move_constructible_v); 41 | static_assert(std::is_nothrow_move_assignable_v); 42 | static_assert(!std::is_copy_constructible_v); 43 | static_assert(!std::is_copy_assignable_v); 44 | 45 | static_assert(!std::is_nothrow_move_constructible_v); 46 | static_assert(!std::is_nothrow_move_assignable_v); 47 | static_assert(std::is_move_constructible_v); 48 | static_assert(std::is_move_assignable_v); 49 | static_assert(std::is_copy_constructible_v); 50 | static_assert(std::is_copy_assignable_v); 51 | 52 | static_assert(!std::is_nothrow_move_constructible_v); 53 | static_assert(!std::is_nothrow_move_assignable_v); 54 | static_assert(!std::is_move_constructible_v); 55 | static_assert(!std::is_move_assignable_v); 56 | static_assert(!std::is_copy_constructible_v); 57 | static_assert(!std::is_copy_assignable_v); 58 | 59 | constexpr double simpleComputation(Surface x) { 60 | return x.perimeter() + x.computeArea(); 61 | } 62 | 63 | constexpr double simpleMove() { 64 | Surface x = Circle(); 65 | x = Rectangle(); 66 | 67 | return simpleComputation(std::move(x)); 68 | } 69 | 70 | constexpr double simpleCopy() { 71 | Surface x = Circle(5.0); 72 | Surface y(x); 73 | Surface z = Rectangle(10.0); 74 | x = z; 75 | return simpleComputation(y) + simpleComputation(x); 76 | } 77 | 78 | constexpr double in_place_construction() { 79 | Surface x(std::in_place_type, 10.0); 80 | Surface y(std::in_place_type, 10.0, 5.0); 81 | 82 | return y.computeArea() + x.perimeter(); 83 | } 84 | 85 | constexpr double simpleComputationRef(SurfaceRef ref) { 86 | return ref.perimeter() + ref.computeArea(); 87 | } 88 | 89 | constexpr auto castRefTest() { 90 | Surface x = Circle(1.0); 91 | auto &ref_x = erased::any_cast(x); 92 | auto &cref_x = erased::any_cast(std::as_const(x)); 93 | 94 | static_assert(std::is_same_v); 95 | static_assert(std::is_same_v); 96 | 97 | return ref_x.computeArea() + cref_x.perimeter(); 98 | } 99 | 100 | constexpr auto castPtrTest() { 101 | Surface x = Circle(1.0); 102 | auto ref_x = erased::any_cast(&x); 103 | auto cref_x = erased::any_cast(&std::as_const(x)); 104 | 105 | static_assert(std::is_same_v); 106 | static_assert(std::is_same_v); 107 | 108 | return ref_x->computeArea() + cref_x->perimeter(); 109 | } 110 | 111 | constexpr auto castPtrFailTest() { 112 | Surface x = Circle(1.0); 113 | return any_cast(&x); 114 | } 115 | 116 | ERASED_MAKE_BEHAVIOR( 117 | Computer, compute, 118 | (const &self, int value) requires(self.compute(value))->int); 119 | 120 | using Computable = erased::erased; 121 | 122 | constexpr int compute(Computable x, int value) { return x.compute(value); } 123 | 124 | struct Double { 125 | constexpr int compute(int value) const { return value + value; } 126 | }; 127 | 128 | struct Square { 129 | constexpr int compute(int value) const { return value * value; } 130 | }; 131 | 132 | #ifndef _MSC_VER 133 | TEST(Tests, CompileTimeTestsErased) { 134 | static_assert(simpleComputation(Circle(2.0)) == 135 | Circle(2.0).computeArea() + Circle(2.0).perimeter()); 136 | static_assert(simpleComputation(Rectangle(3.0)) == 137 | Rectangle(3.0).computeArea() + Rectangle(3.0).perimeter()); 138 | 139 | static_assert(simpleMove() == 1.0 + 4.0); 140 | 141 | static_assert(simpleCopy() == 142 | (Circle(5.0).perimeter() + Circle(5.0).computeArea()) + 143 | Rectangle(10.0).perimeter() + 144 | Rectangle(10.0).computeArea()); 145 | 146 | static_assert(in_place_construction() == 147 | (Rectangle(10, 5).computeArea() + Circle(10.0).perimeter())); 148 | 149 | static_assert(compute(Double{}, 10) == 20); 150 | static_assert(compute(Square{}, 10) == 100); 151 | 152 | static_assert(castRefTest() == 153 | Circle(1.0).computeArea() + Circle(1.0).perimeter()); 154 | static_assert(castPtrTest() == 155 | Circle(1.0).computeArea() + Circle(1.0).perimeter()); 156 | 157 | static_assert(castPtrFailTest() == nullptr); 158 | } 159 | 160 | constexpr auto simpleComputationRefCircle() { 161 | Circle circle{1.0}; 162 | return simpleComputationRef(circle); 163 | } 164 | 165 | constexpr auto simpleComputationRefRectangle() { 166 | Rectangle rectangle{10.0, 5.0}; 167 | return simpleComputationRef(rectangle); 168 | } 169 | 170 | constexpr auto refCastRefTest() { 171 | auto x = Circle(1.0); 172 | SurfaceRef ref = x; 173 | auto &ref_x = erased::any_cast(ref); 174 | auto &cref_x = erased::any_cast(std::as_const(ref)); 175 | 176 | static_assert(std::is_same_v); 177 | static_assert(std::is_same_v); 178 | 179 | return &ref_x == &cref_x && &ref_x == &x; 180 | } 181 | 182 | constexpr auto refCastPtrTest() { 183 | auto x = Circle(1.0); 184 | SurfaceRef ref = x; 185 | auto ref_x = erased::any_cast(&ref); 186 | auto cref_x = erased::any_cast(&std::as_const(ref)); 187 | 188 | static_assert(std::is_same_v); 189 | static_assert(std::is_same_v); 190 | 191 | return ref_x == cref_x && ref_x == &x; 192 | } 193 | 194 | constexpr auto refCastPtrFailTest() { 195 | auto x = Circle(1.0); 196 | SurfaceRef ref = x; 197 | return any_cast(&ref); 198 | } 199 | 200 | TEST(Tests, CompileTimeTestsRef) { 201 | static_assert(simpleComputationRefCircle() == 202 | Circle{1.0}.perimeter() + Circle{1.0}.computeArea()); 203 | static_assert(simpleComputationRefRectangle() == 204 | Rectangle{10.0, 5.0}.perimeter() + 205 | Rectangle{10.0, 5.0}.computeArea()); 206 | 207 | static_assert(refCastRefTest()); 208 | static_assert(refCastPtrTest()); 209 | static_assert(refCastPtrFailTest() == nullptr); 210 | } 211 | #endif 212 | 213 | TEST(Tests, tests) { 214 | ASSERT_EQ(simpleComputation(Circle(2.0)), 215 | Circle(2.0).computeArea() + Circle(2.0).perimeter()); 216 | 217 | ASSERT_EQ(simpleComputation(Rectangle(3.0)), 218 | Rectangle(3.0).computeArea() + Rectangle(3.0).perimeter()); 219 | 220 | ASSERT_EQ(simpleMove(), 1.0 + 4.0); 221 | 222 | ASSERT_EQ(simpleCopy(), 223 | (Circle(5.0).perimeter() + Circle(5.0).computeArea()) + 224 | Rectangle(10.0).perimeter() + Rectangle(10.0).computeArea()); 225 | 226 | ASSERT_EQ(in_place_construction(), 227 | (Rectangle(10, 5).computeArea() + Circle(10.0).perimeter())); 228 | 229 | ASSERT_EQ(compute(Double{}, 10), 20); 230 | ASSERT_EQ(compute(Square{}, 10), 100); 231 | 232 | ASSERT_EQ(castRefTest(), Circle(1.0).computeArea() + Circle(1.0).perimeter()); 233 | ASSERT_EQ(castPtrTest(), Circle(1.0).computeArea() + Circle(1.0).perimeter()); 234 | 235 | ASSERT_EQ(castPtrFailTest(), nullptr); 236 | } 237 | --------------------------------------------------------------------------------