From 3b070156074330fbc0ca9e31b8598da0c9df73fe Mon Sep 17 00:00:00 2001 From: Tsche Date: Sat, 20 Apr 2024 08:07:06 +0200 Subject: [PATCH 01/41] first implementation --- .clang-format | 29 +++++ .clang-tidy | 24 ++++ .editorconfig | 17 +++ .gitattributes | 3 + .gitignore | 15 +++ CMakeLists.txt | 79 ++++++++++++ CMakeUserPresets.json | 9 ++ benchmark/control.cpp | 129 +++++++++++++++++++ benchmark/profiles.toml | 14 +++ benchmark/underlying.cpp | 59 +++++++++ benchmark/util.h | 9 ++ cmake/warnings.cmake | 100 +++++++++++++++ conanfile.py | 65 ++++++++++ include/slo/impl/common.h | 69 +++++++++++ include/slo/impl/union/recursive.h | 193 +++++++++++++++++++++++++++++ include/slo/impl/union/tree.h | 156 +++++++++++++++++++++++ include/slo/impl/visit.h | 43 +++++++ include/slo/impl/wrapper.h | 41 ++++++ include/slo/union.h | 115 +++++++++++++++++ include/slo/util/compat.h | 44 +++++++ include/slo/util/concepts.h | 17 +++ include/slo/util/list.h | 67 ++++++++++ include/slo/util/warning.h | 34 +++++ include/slo/variant.h | 121 ++++++++++++++++++ palgen.toml | 17 +++ plugins/benchmark.py | 155 +++++++++++++++++++++++ test/CMakeLists.txt | 9 ++ test/common/lifetime.h | 88 +++++++++++++ test/main.cpp | 24 ++++ test/slo/CMakeLists.txt | 1 + test/slo/lifetime.cpp | 116 +++++++++++++++++ test/union/CMakeLists.txt | 1 + test/union/recursive.cpp | 0 test/union/tree.cpp | 0 test/util/CMakeLists.txt | 1 + test/util/forward_like.cpp | 66 ++++++++++ test/util/list.cpp | 0 37 files changed, 1930 insertions(+) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 CMakeLists.txt create mode 100644 CMakeUserPresets.json create mode 100644 benchmark/control.cpp create mode 100644 benchmark/profiles.toml create mode 100644 benchmark/underlying.cpp create mode 100644 benchmark/util.h create mode 100644 cmake/warnings.cmake create mode 100644 conanfile.py create mode 100644 include/slo/impl/common.h create mode 100644 include/slo/impl/union/recursive.h create mode 100644 include/slo/impl/union/tree.h create mode 100644 include/slo/impl/visit.h create mode 100644 include/slo/impl/wrapper.h create mode 100644 include/slo/union.h create mode 100644 include/slo/util/compat.h create mode 100644 include/slo/util/concepts.h create mode 100644 include/slo/util/list.h create mode 100644 include/slo/util/warning.h create mode 100644 include/slo/variant.h create mode 100644 palgen.toml create mode 100644 plugins/benchmark.py create mode 100644 test/CMakeLists.txt create mode 100644 test/common/lifetime.h create mode 100644 test/main.cpp create mode 100644 test/slo/CMakeLists.txt create mode 100644 test/slo/lifetime.cpp create mode 100644 test/union/CMakeLists.txt create mode 100644 test/union/recursive.cpp create mode 100644 test/union/tree.cpp create mode 100644 test/util/CMakeLists.txt create mode 100644 test/util/forward_like.cpp create mode 100644 test/util/list.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ac4444e --- /dev/null +++ b/.clang-format @@ -0,0 +1,29 @@ +BasedOnStyle: Chromium +IndentWidth: 2 +ColumnLimit: 120 + +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowAllArgumentsOnNextLine: false +SortIncludes: false + +# Available starting from clang_format 18 +# AllowShortCompoundRequirementOnASingleLine: true + +AccessModifierOffset: '-2' +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: true +AlignConsecutiveAssignments: true +AlignEscapedNewlines: Left +AlignTrailingComments: true +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true +BinPackArguments: true +BinPackParameters: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +ConstructorInitializerAllOnOneLineOrOnePerLine: true +IndentPPDirectives: AfterHash +SortUsingDeclarations: true \ No newline at end of file diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..45e8d5f --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,24 @@ +--- +Checks: "*, + -abseil-*, + -altera-*, + -android-*, + -fuchsia-*, + -google-*, + -llvm*, + -modernize-use-trailing-return-type, + -zircon-*, + -readability-else-after-return, + -readability-static-accessed-through-instance, + -readability-avoid-const-params-in-decls, + -cppcoreguidelines-non-private-member-variables-in-classes, + -misc-non-private-member-variables-in-classes, + -hicpp-named-parameter, + -clang-diagnostic-unknown-attributes, + -readability-named-parameter, + -*-avoid-c-arrays, + -*-magic-numbers +" +WarningsAsErrors: '' +HeaderFilterRegex: '' +FormatStyle: none \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9cb2f56 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# Rules in this file were initially inferred by Visual Studio IntelliCode from the Y:\che\palliate codebase based on best match to current usage at 16/01/2022 +# You can modify the rules from these initially generated values to suit your own policies +# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference + +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +end_of_line = lf + +[*.py] +indent_style = space +indent_size = 4 +charset = utf-8 +end_of_line = lf \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dcf18cf --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +*.h linguist-language=cpp + +* text=auto eol=lf diff --git a/.gitignore b/.gitignore index 728d411..a7277e7 100644 --- a/.gitignore +++ b/.gitignore @@ -31,5 +31,20 @@ *.out *.app +# Python +*.pyc +**/__pycache__/ + # IDE files .vscode + +# Build files +**/build/ + +# misc +compile_commands.json +**/scratchpad.* + +# Caches +.cache +.pytest_cache \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..211bfad --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 3.15) +include(cmake/warnings.cmake) + +project(slo CXX) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +add_library(slo INTERFACE) + +set_property(TARGET slo PROPERTY CXX_STANDARD 23) + +target_include_directories(slo INTERFACE + $ + $) + + +option(BUILD_TESTING "Enable tests" ON) +option(ENABLE_SANITIZERS "Enable asan and ubsan" OFF) +option(ENABLE_COVERAGE "Enable coverage instrumentation" OFF) +option(ENABLE_BENCHMARK "Enable benchmarks" OFF) + +if(NOT BUILD_TESTING STREQUAL OFF) + message(STATUS "Building unit tests") + + enable_testing() + add_executable(slo_test "") + + enable_warnings(slo_test) + + find_package(GTest REQUIRED) + target_link_libraries(slo_test PRIVATE slo) + target_link_libraries(slo_test PRIVATE GTest::gtest GTest::gtest_main GTest::gmock) + + include(GoogleTest) + gtest_discover_tests(slo_test) + + add_subdirectory(test) + + if(ENABLE_COVERAGE) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(slo_test PRIVATE -g -O0 --coverage -fprofile-arcs -ftest-coverage -fprofile-abs-path) + target_link_libraries(slo_test PRIVATE gcov) + message(STATUS "Instrumenting for coverage") + else() + message(FATAL_ERROR "Currently only GCC is supported for coverage instrumentation.") + endif() + endif() + + if(ENABLE_SANITIZERS AND WIN32) + message(STATUS "Enabling sanitizers") + if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_compile_options(slo_test INTERFACE -fsanitize=address) + target_link_options(slo_test INTERFACE -fsanitize=address) + endif() + elseif(ENABLE_SANITIZERS) + message(STATUS "Enabling sanitizers") + target_compile_options(slo_test INTERFACE -fsanitize=address,undefined) + target_link_options(slo_test INTERFACE -fsanitize=address,undefined) + endif() +endif() + +if (ENABLE_BENCHMARK) + message(STATUS "Building runtime benchmarks") + add_executable(slo_benchmark "") + add_subdirectory(benchmark) + find_package(benchmark REQUIRED) + target_link_libraries(slo_benchmark PRIVATE benchmark::benchmark_main) + target_link_libraries(slo_benchmark PRIVATE slo) +endif() + +## headers +set_target_properties(slo PROPERTIES PUBLIC_HEADER "include/slo") +install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include/ + DESTINATION include) + +## binaries +install(TARGETS ${TARGET} + ARCHIVE DESTINATION lib # Windows import libraries (.lib) + RUNTIME DESTINATION bin # Windows DLLs + LIBRARY DESTINATION lib) # shared libraries (ie Linux .so) diff --git a/CMakeUserPresets.json b/CMakeUserPresets.json new file mode 100644 index 0000000..96c259d --- /dev/null +++ b/CMakeUserPresets.json @@ -0,0 +1,9 @@ +{ + "version": 4, + "vendor": { + "conan": {} + }, + "include": [ + "/home/che/src/variant/build/Debug/generators/CMakePresets.json" + ] +} \ No newline at end of file diff --git a/benchmark/control.cpp b/benchmark/control.cpp new file mode 100644 index 0000000..086d94b --- /dev/null +++ b/benchmark/control.cpp @@ -0,0 +1,129 @@ +// This is the gold standard to test against - a handwritten variant +#include +#include +#include "util.h" + +struct Variant { + union Union { + Constant<0> alternative_0; + Constant<1> alternative_1; + Constant<2> alternative_2; + Constant<3> alternative_3; + Constant<4> alternative_4; + }; + + Union data; + std::size_t index; + + explicit Variant(Constant<0> obj) : data{.alternative_0 = obj}, index{0} {} + explicit Variant(Constant<1> obj) : data{.alternative_1 = obj}, index{1} {} + explicit Variant(Constant<2> obj) : data{.alternative_2 = obj}, index{2} {} + explicit Variant(Constant<3> obj) : data{.alternative_3 = obj}, index{3} {} + explicit Variant(Constant<4> obj) : data{.alternative_4 = obj}, index{4} {} + + Variant& operator=(Constant<0> obj) { + data.alternative_0 = obj; + index = 0; + return *this; + } + + Variant& operator=(Constant<1> obj) { + data.alternative_1 = obj; + index = 1; + return *this; + } + + Variant& operator=(Constant<2> obj) { + data.alternative_2 = obj; + index = 2; + return *this; + } + + Variant& operator=(Constant<3> obj) { + data.alternative_3 = obj; + index = 3; + return *this; + } + + Variant& operator=(Constant<4> obj) { + data.alternative_4 = obj; + index = 4; + return *this; + } + + template + decltype(auto) get(this Self&& self) { + if constexpr (std::same_as>) { + if (self.index == 0){ + return self.data.alternative_0; + } + } else if constexpr (std::same_as>) { + if (self.index == 1){ + return self.data.alternative_1; + } + } else if constexpr (std::same_as>) { + if (self.index == 2){ + return self.data.alternative_2; + } + } else if constexpr (std::same_as>) { + if (self.index == 3){ + return self.data.alternative_3; + } + } else if constexpr (std::same_as>) { + if (self.index == 4){ + return self.data.alternative_4; + } + } + throw std::exception(); + } + template + decltype(auto) get_n(this Self&& self) { + if constexpr (N == 0) { + if (self.index == 0){ + return self.data.alternative_0; + } + } else if constexpr (N == 1) { + if (self.index == 1){ + return self.data.alternative_1; + } + } else if constexpr (N == 2) { + if (self.index == 2){ + return self.data.alternative_2; + } + } else if constexpr (N == 3) { + if (self.index == 3){ + return self.data.alternative_3; + } + } else if constexpr (N == 4) { + if (self.index == 4){ + return self.data.alternative_4; + } + } + throw std::exception(); + } + template + decltype(auto) visit(this Self&& self, F visitor) { + switch (self.index) { + case 0: + return visitor(self.data.alternative_0); + case 1: + return visitor(self.data.alternative_1); + case 2: + return visitor(self.data.alternative_2); + case 3: + return visitor(self.data.alternative_3); + case 4: + return visitor(self.data.alternative_4); + } + + // No visitation possible + std::unreachable(); + } +}; + +#include +int main() { + auto obj = Variant{constant<3>}; + obj = constant<2>; + obj.visit([](Constant) { printf("%d\n", V); }); +} \ No newline at end of file diff --git a/benchmark/profiles.toml b/benchmark/profiles.toml new file mode 100644 index 0000000..cd52e82 --- /dev/null +++ b/benchmark/profiles.toml @@ -0,0 +1,14 @@ +source = "underlying.cpp" + +[query] +"generate_tree" = "traceEvents[?name=='InstantiateFunction' && starts_with(@.args.detail, 'generate_union')].dur" +"total" = "traceEvents[?name=='ExecuteCompiler'].dur" + +[profile.Std] +STANDARD_VARIANT = true + +[profile.Tree] +TREE = true + +[profile.Recursive] +RECURSIVE = true diff --git a/benchmark/underlying.cpp b/benchmark/underlying.cpp new file mode 100644 index 0000000..c92cbb3 --- /dev/null +++ b/benchmark/underlying.cpp @@ -0,0 +1,59 @@ +// #include + +#include +#include +#include + +#if defined(TREE) +# include +#elif defined(RECURSIVE) +# include +#else +# include +#endif + +template +struct Constant {}; + +template +auto generate_union(std::index_sequence) { +#if defined(TREE) + using underlying = slo::util::to_cbt...>; +#elif defined(RECURSIVE) + using underlying = slo::impl::RecursiveUnion...>; +#else + using underlying = std::variant...>; +#endif + return std::type_identity{}; +} + +template +auto get_size() { +#if defined(TREE) + return T::size; +#else + return 0; +#endif +} + +template +auto generate_getters(std::index_sequence) { + ( + [] { + auto obj = T{std::in_place_index}; + #if defined(TREE) || defined(RECURSIVE) + using slo::get; + #else + using std::get; + #endif + (void)get(obj); + }(), + ...); +} + +int main() { + using TestType = typename decltype(generate_union(std::make_index_sequence<200>()))::type; + //(void)get_size(); + + generate_getters(std::make_index_sequence<200>()); +} diff --git a/benchmark/util.h b/benchmark/util.h new file mode 100644 index 0000000..97a9b15 --- /dev/null +++ b/benchmark/util.h @@ -0,0 +1,9 @@ +#pragma once + +template +struct Constant { + static constexpr auto value = V; +}; + +template +inline constexpr Constant constant; \ No newline at end of file diff --git a/cmake/warnings.cmake b/cmake/warnings.cmake new file mode 100644 index 0000000..867c14f --- /dev/null +++ b/cmake/warnings.cmake @@ -0,0 +1,100 @@ +# Build flags +# See: +# - https://github.com/aminya/project_options/blob/main/src/CompilerWarnings.cmake +# - https://github.com/cpp-best-practices/cppbestpractices/blob/master/02-Use_the_Tools_Available.md +# - https://clang.llvm.org/docs/DiagnosticsReference.html +# - https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html +# - https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-by-category?view=msvc-170 +# - https://developers.redhat.com/blog/2018/03/21/compiler-and-linker-flags-gcc + +function(enable_warnings _TARGET) + if("${MSVC_WARNINGS}" STREQUAL "") + set(MSVC_WARNINGS + /W4 # Baseline reasonable warnings + /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data + /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /w14263 # 'function': member function does not override any base class virtual member function + /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not + + # be destructed correctly + /w14287 # 'operator': unsigned/negative constant mismatch + /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside + + # the for-loop scope + /w14296 # 'operator': expression is always 'boolean_value' + /w14311 # 'variable': pointer truncation from 'type1' to 'type2' + /w14545 # expression before comma evaluates to a function which is missing an argument list + /w14546 # function call before comma missing argument list + /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect + /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? + /w14555 # expression has no effect; expected expression with side- effect + /w14619 # pragma warning: there is no warning number 'number' + /w14640 # Enable warning on thread un-safe static member initialization + /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. + /w14905 # wide string literal cast to 'LPSTR' + /w14906 # string literal cast to 'LPWSTR' + /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied + /permissive- # standards conformance mode for MSVC compiler. + ) + endif() + + set(COMMON_WARNINGS + -Wall + -Wextra # reasonable and standard + -Wextra-semi # Warn about semicolon after in-class function definition. + -Wshadow # warn the user if a variable declaration shadows one from a parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps + + # catch hard to track down memory errors + -Wold-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual function + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output (ie printf) + -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation + ) + + if("${CLANG_WARNINGS}" STREQUAL "") + set(CLANG_WARNINGS + ${COMMON_WARNINGS} + -Wno-dollar-in-identifier-extension + -Wno-unknown-attributes # ignore unknown attribute warning + ) + endif() + + if("${GCC_WARNINGS}" STREQUAL "") + set(GCC_WARNINGS + ${COMMON_WARNINGS} + -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were probably wanted + -Wuseless-cast # warn if you perform a cast to the same type + -Wno-attributes # ignore unknown attribute warning + # -fconcepts-diagnostics-depth=2 + ) + endif() + + if(MSVC) + message(STATUS "Setting warning flags for MSVC") + set(PROJECT_WARNINGS ${MSVC_WARNINGS}) + + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + message(STATUS "Setting warning flags for clang") + set(PROJECT_WARNINGS ${CLANG_WARNINGS}) + + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + message(STATUS "Setting warning flags for GCC") + set(PROJECT_WARNINGS ${GCC_WARNINGS}) + + else() + message(AUTHOR_WARNING "No compiler warnings set for CXX compiler: '${CMAKE_CXX_COMPILER_ID}'") + endif() + + target_compile_options(${_TARGET} PUBLIC ${PROJECT_WARNINGS}) +endfunction() diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..0bc7d9d --- /dev/null +++ b/conanfile.py @@ -0,0 +1,65 @@ +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout +from conan.tools.files import copy +from conan.tools.build import check_min_cppstd + + +class SloRecipe(ConanFile): + name = "slo" + version = "0.1" + package_type = "header-library" + + # Optional metadata + license = "MIT" + author = "Tsche che@palliate.io" + url = "" + description = "" + topics = ("", "", "") + + # Binary configuration + settings = "os", "compiler", "build_type", "arch" + options = { + "benchmark": [True, False], + "sanitizers": [True, False], + "coverage": [True, False], + } + default_options = {"benchmark": False, "sanitizers": False, + "coverage": False} + generators = "CMakeToolchain", "CMakeDeps" + + exports_sources = "CMakeLists.txt", "include/*" + + def validate(self): + if self.settings.compiler.get_safe("cppstd"): + check_min_cppstd(self, "23") + + def requirements(self): + if self.options.benchmark: + self.requires("benchmark/1.8.3") + + self.test_requires("gtest/1.14.0") + + def layout(self): + cmake_layout(self) + + def build(self): + if not self.conf.get("tools.build:skip_test", default=False): + cmake = CMake(self) + cmake.configure( + variables={ + "ENABLE_BENCHMARK": self.options.benchmark, + "ENABLE_SANITIZERS": self.options.sanitizers, + "ENABLE_COVERAGE": self.options.coverage, + } + ) + cmake.build() + + def package(self): + copy(self, "*.h", self.source_folder, self.package_folder) + + def package_info(self): + self.cpp_info.bindirs = [] + self.cpp_info.libdirs = [] + + def package_id(self): + self.info.clear() diff --git a/include/slo/impl/common.h b/include/slo/impl/common.h new file mode 100644 index 0000000..4d8bd19 --- /dev/null +++ b/include/slo/impl/common.h @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include +#include + +namespace slo { +namespace impl { +template +consteval std::size_t get_type_index() { + std::size_t index = 0; + (void)((!std::same_as ? ++index, false : true) || ...); + return index; +} + +template +constexpr inline std::size_t type_index = get_type_index, Ts...>(); + +template +concept has_get = requires(T obj) { obj.template get<0>(); }; +} // namespace impl + +template +struct variant_alternative; + + +template +class Variant; + +template +struct variant_alternative> { + using type = __type_pack_element; +}; + +template +struct variant_alternative const> { + using type = __type_pack_element const; +}; + + +template +union Union; + +template +struct variant_alternative> { + using type = __type_pack_element; +}; + +template +struct variant_alternative const> { + using type = std::add_const_t<__type_pack_element>; +}; + +template +using variant_alternative_t = typename variant_alternative::type; + +template +constexpr decltype(auto) get(V&& variant_) { + return std::forward(variant_).template get(); +} + +template +constexpr decltype(auto) get(V&& variant_) { + return std::forward(variant_).template get(); +} + +struct nullopt {}; + +} // namespace slo \ No newline at end of file diff --git a/include/slo/impl/union/recursive.h b/include/slo/impl/union/recursive.h new file mode 100644 index 0000000..a9fa607 --- /dev/null +++ b/include/slo/impl/union/recursive.h @@ -0,0 +1,193 @@ +#pragma once +#include +#include +#include + +namespace slo::impl { + +template +union RecursiveUnion; + +template +union RecursiveUnion { + T value; + constexpr ~RecursiveUnion() {} + + template + constexpr explicit RecursiveUnion(std::in_place_index_t<0>, Args&&... args) : value{std::forward(args)...} {} + + constexpr std::size_t index() const + requires(has_index) + { + return value.tag; + } + + template + constexpr decltype(auto) get(this Self&& self) { + static_assert(N == 0); + if constexpr (has_unwrap) { + return std::forward(self).value.unwrap(); + } else { + return compat::forward_like(std::forward(self).value); + } + } +}; + +template +union RecursiveUnion { + T value; + RecursiveUnion tail; + + constexpr ~RecursiveUnion() {} + template + constexpr explicit RecursiveUnion(std::in_place_index_t<0>, Args&&... args) : value{std::forward(args)...} {} + + template + constexpr explicit RecursiveUnion(std::in_place_index_t, Args&&... args) + : tail{std::in_place_index_t{}, std::forward(args)...} {} + + constexpr std::size_t index() const + requires(has_index) + { + if (compat::is_within_lifetime(&value.tag)) { + return value.tag; + } + return tail.index(); + } + + template + constexpr decltype(auto) get(this Self&& self) { + if constexpr (N == 0) { + if constexpr (has_unwrap) { + return std::forward(self).value.unwrap(); + } else { + return compat::forward_like(std::forward(self).value); + } + } else { + return std::forward(self).tail.template get(); + } + } +}; + +template +requires (sizeof...(Ts) != 0) +union RecursiveUnion { + T1 alternative_1; + T2 alternative_2; + RecursiveUnion tail; + + constexpr ~RecursiveUnion() {} + template + constexpr explicit RecursiveUnion(std::in_place_index_t<0>, Args&&... args) : alternative_1{std::forward(args)...} {} + + template + constexpr explicit RecursiveUnion(std::in_place_index_t<1>, Args&&... args) : alternative_2{std::forward(args)...} {} + + template + constexpr explicit RecursiveUnion(std::in_place_index_t, Args&&... args) + : tail{std::in_place_index_t{}, std::forward(args)...} {} + + constexpr std::size_t index() const + requires (has_index && has_index) + { + if (compat::is_within_lifetime(&alternative_1.tag)) { + return alternative_1.tag; + } else if (compat::is_within_lifetime(&alternative_2.tag)) { + return alternative_2.tag; + } + return tail.index(); + } + + template + constexpr decltype(auto) get(this Self&& self) { + if constexpr (N == 0) { + if constexpr (has_unwrap) { + return std::forward(self).alternative_1.unwrap(); + } else { + return compat::forward_like(std::forward(self).alternative_1); + } + } else if constexpr (N == 1) { + if constexpr (has_unwrap) { + return std::forward(self).alternative_2.unwrap(); + } else { + return compat::forward_like(std::forward(self).alternative_2); + } + } else { + return std::forward(self).tail.template get(); + } + } +}; + +template +requires (sizeof...(Ts) != 0) +union RecursiveUnion { + T1 alternative_1; + T2 alternative_2; + T3 alternative_3; + T4 alternative_4; + RecursiveUnion tail; + + constexpr ~RecursiveUnion() {} + template + constexpr explicit RecursiveUnion(std::in_place_index_t<0>, Args&&... args) : alternative_1{std::forward(args)...} {} + + template + constexpr explicit RecursiveUnion(std::in_place_index_t<1>, Args&&... args) : alternative_2{std::forward(args)...} {} + + template + constexpr explicit RecursiveUnion(std::in_place_index_t<2>, Args&&... args) : alternative_3{std::forward(args)...} {} + + template + constexpr explicit RecursiveUnion(std::in_place_index_t<3>, Args&&... args) : alternative_4{std::forward(args)...} {} + + template + constexpr explicit RecursiveUnion(std::in_place_index_t, Args&&... args) + : tail{std::in_place_index_t{}, std::forward(args)...} {} + + constexpr std::size_t index() const + requires (has_index && has_index && has_index && has_index) + { + if (compat::is_within_lifetime(&alternative_1.tag)) { + return alternative_1.tag; + } else if (compat::is_within_lifetime(&alternative_2.tag)) { + return alternative_2.tag; + } else if (compat::is_within_lifetime(&alternative_3.tag)) { + return alternative_3.tag; + } else if (compat::is_within_lifetime(&alternative_4.tag)) { + return alternative_4.tag; + } + return tail.index(); + } + + template + constexpr decltype(auto) get(this Self&& self) { + if constexpr (N == 0) { + if constexpr (has_unwrap) { + return std::forward(self).alternative_1.unwrap(); + } else { + return compat::forward_like(std::forward(self).alternative_1); + } + } else if constexpr (N == 1) { + if constexpr (has_unwrap) { + return std::forward(self).alternative_2.unwrap(); + } else { + return compat::forward_like(std::forward(self).alternative_2); + } + } else if constexpr (N == 2) { + if constexpr (has_unwrap) { + return std::forward(self).alternative_3.unwrap(); + } else { + return compat::forward_like(std::forward(self).alternative_3); + } + } else if constexpr (N == 3) { + if constexpr (has_unwrap) { + return std::forward(self).alternative_4.unwrap(); + } else { + return compat::forward_like(std::forward(self).alternative_4); + } + } else { + return std::forward(self).tail.template get(); + } + } +}; +} // namespace slo::impl \ No newline at end of file diff --git a/include/slo/impl/union/tree.h b/include/slo/impl/union/tree.h new file mode 100644 index 0000000..00aa2fb --- /dev/null +++ b/include/slo/impl/union/tree.h @@ -0,0 +1,156 @@ +#pragma once +#include +#include +#include +#include + +namespace slo::impl { +template +union TreeUnion; + +template +union TreeUnion, TreeUnion> { + constexpr static std::size_t size = TreeUnion::size + TreeUnion::size; + TreeUnion left; + TreeUnion right; + + constexpr ~TreeUnion() {} + + template + constexpr explicit TreeUnion(std::in_place_index_t idx, Args&&... args) + requires(Idx < TreeUnion::size) + : left{idx, std::forward(args)...} {} + + template + constexpr explicit TreeUnion(std::in_place_index_t, Args&&... args) + requires(Idx >= TreeUnion::size) + : right{std::in_place_index::size>, std::forward(args)...} {} + + constexpr std::size_t index() const { + if (compat::is_within_lifetime(&left)) { + return left.index(); + } + return right.index(); + } + + template + constexpr decltype(auto) get(this Self&& self) { + if constexpr (Idx < TreeUnion::size) { + return std::forward(self).left.template get(); + } else { + return std::forward(self).right.template get::size>(); + } + } +}; + +template +union TreeUnion, U> { + constexpr static std::size_t size = 1 + TreeUnion::size; + TreeUnion left; + U right; + + constexpr ~TreeUnion() {} + + template + constexpr explicit TreeUnion(std::in_place_index_t idx, Args&&... args) + requires(Idx < TreeUnion::size) + : left{idx, std::forward(args)...} {} + + template + constexpr explicit TreeUnion(std::in_place_index_t, Args&&... args) + requires(Idx == TreeUnion::size) + : right{std::forward(args)...} {} + + constexpr std::size_t index() const { + if (compat::is_within_lifetime(&right.tag)) { + return right.tag; + } + return left.index(); + } + + template + constexpr decltype(auto) get(this Self&& self) { + if constexpr (Idx < TreeUnion::size) { + return std::forward(self).left.template get(); + } else { + static_assert(Idx == TreeUnion::size, "Index out of bounds"); + if constexpr (has_unwrap) { + return std::forward(self).right.unwrap(); + } else { + return compat::forward_like(std::forward(self).right); + } + } + } +}; + +template +union TreeUnion { + T left; + U right; + + constexpr static std::size_t size = 2; + + constexpr ~TreeUnion() {} + + template + constexpr explicit TreeUnion(std::in_place_index_t<0>, Args&&... args) : left{std::forward(args)...} {} + + template + constexpr explicit TreeUnion(std::in_place_index_t<1>, Args&&... args) : right{std::forward(args)...} {} + + constexpr std::size_t index() const { + if (compat::is_within_lifetime(&left.tag)) { + return left.tag; + } else if (compat::is_within_lifetime(&right.tag)) { + return right.tag; + } + std::unreachable(); + } + + template + constexpr decltype(auto) get(this Self&& self) { + if constexpr (Idx == 0) { + if constexpr (has_unwrap) { + return std::forward(self).left.unwrap(); + } else { + return compat::forward_like(std::forward(self).left); + } + } else { + static_assert(Idx == 1, "Index out of bounds"); + if constexpr (has_unwrap) { + return std::forward(self).right.unwrap(); + } else { + return compat::forward_like(std::forward(self).right); + } + } + } +}; + +template +union TreeUnion { + T left; + constexpr static std::size_t size = 1; + + constexpr ~TreeUnion() {} + + template + constexpr explicit TreeUnion(std::in_place_index_t<0>, Args&&... args) : left{std::forward(args)...} {} + + constexpr std::size_t index() const { + if (compat::is_within_lifetime(&left.tag)) { + return left.tag; + } + std::unreachable(); + } + + template + constexpr decltype(auto) get(this Self&& self) { + static_assert(Idx == 0); + if constexpr (has_unwrap) { + return std::forward(self).left.unwrap(); + } else { + return compat::forward_like(std::forward(self).left); + } + } +}; +} // namespace slo::impl \ No newline at end of file diff --git a/include/slo/impl/visit.h b/include/slo/impl/visit.h new file mode 100644 index 0000000..09c9e15 --- /dev/null +++ b/include/slo/impl/visit.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include +#include +#include +#include "common.h" + +namespace slo { + namespace detail{ +template +constexpr decltype(auto) visit_impl(Variant&& variant, F visitor, + std::index_sequence) { + // assert(variant.index() != variant.npos); + using return_type = std::common_type_t(std::forward(variant))))...>; + if constexpr (std::same_as) { + (void)((variant.index() == Is ? visitor(slo::get(std::forward(variant))), true : false) || + ...); + } else { + union { + char dummy; + return_type value; + } ret{}; + + bool const success = + ((variant.index() == Is + ? std::construct_at(&ret.value, visitor(slo::get(std::forward(variant)))), + true : false) || + ...); + + if (success) { + return ret.value; + } + std::unreachable(); + } +} +} + + +template +constexpr decltype(auto) visit(F visitor, Variant&& variant) { + return detail::visit_impl(std::forward(variant), visitor, util::to_index_sequence{}); +} +} \ No newline at end of file diff --git a/include/slo/impl/wrapper.h b/include/slo/impl/wrapper.h new file mode 100644 index 0000000..42b1e45 --- /dev/null +++ b/include/slo/impl/wrapper.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include + +namespace slo::impl { +template +concept has_index = requires(T const& obj) { + { obj.index() } -> std::convertible_to; +}; + +template +concept has_unwrap = requires(T obj) { + { obj.unwrap() } -> std::same_as; + { std::as_const(obj).unwrap() } -> std::same_as; + { std::move(obj).unwrap() } -> std::same_as; + { std::move(std::as_const(obj)).unwrap() } -> std::same_as; +}; + +template +struct Wrapper { + using type = ValueType; + + template + constexpr explicit Wrapper(Args&&... args) : tag{Idx} + , storage{std::forward(args)...} {} + + [[nodiscard]] constexpr std::size_t index() const { return tag; } + + [[nodiscard]] constexpr ValueType& unwrap() & { return storage; } + [[nodiscard]] constexpr ValueType const& unwrap() const& { return storage; } + [[nodiscard]] constexpr ValueType&& unwrap() && { return std::move(storage); } + [[nodiscard]] constexpr ValueType const&& unwrap() const&& { return std::move(storage); } + + decltype(Idx) tag; + ValueType storage; +}; + +static_assert(has_unwrap>); + +} // namespace slo::impl \ No newline at end of file diff --git a/include/slo/union.h b/include/slo/union.h new file mode 100644 index 0000000..fa3410b --- /dev/null +++ b/include/slo/union.h @@ -0,0 +1,115 @@ +#pragma once +#include +#include +#include + +#include "impl/visit.h" +#include "impl/common.h" +#include "impl/wrapper.h" + +#include "impl/union/tree.h" +#include "impl/union/recursive.h" + +namespace slo { +template +union Union { + constexpr static unsigned char npos = static_cast(~0U); + + template + static constexpr auto generate_union(std::index_sequence) + -> impl::RecursiveUnion(Is), Ts>...>; + + template + requires(sizeof...(Ts) >= 42) + static constexpr auto generate_union(std::index_sequence) + -> util::to_cbt(Is), Ts>...>; + + using union_type = decltype(generate_union(std::index_sequence_for())); + using dummy_type = impl::Wrapper; + + dummy_type dummy; + struct Storage { + union_type value; + template + constexpr explicit Storage(Args&&... args) : value(std::forward(args)...) {} + constexpr ~Storage() = default; + } storage; + + template + constexpr explicit Union(std::in_place_index_t idx, Args&&... args) + : storage{idx, std::forward(args)...} {} + + template + constexpr explicit Union(T&& item) + requires(!std::same_as> && (std::same_as, Ts> || ...)) + : storage(std::in_place_index>, std::forward(item)) {} + + constexpr Union() + requires (std::is_default_constructible_v>) + : Union(std::in_place_index<0>) {} + + constexpr Union(Union const& other) { + visit([this](auto const& obj) { this->emplace(obj); }, other); + } + + constexpr Union(Union&& other) { + visit([this](T&& obj) { this->emplace(std::move(obj)); }, std::move(other)); + } + + constexpr Union& operator=(Union const& other) { + if (this != std::addressof(other)) { + visit([this](auto const& obj) { this->emplace(obj); }, other); + } + return *this; + } + + constexpr Union& operator=(Union&& other) { + if (this != std::addressof(other)) { + visit([this](T&& obj) { this->emplace(std::move(obj)); }, std::move(other)); + } + return *this; + } + + constexpr ~Union() { reset(); } + + [[nodiscard]] constexpr std::size_t index() const { + if consteval { + if (!compat::is_within_lifetime(&dummy.tag)) { + return storage.value.index(); + } + } + return dummy.index(); + } + + constexpr void reset() { + slo::visit([](auto&& member) { std::destroy_at(std::addressof(member)); }, *this); + std::construct_at(&dummy); + } + + template + constexpr void emplace(Args&&... args) { + reset(); + std::construct_at(&storage, std::in_place_index, std::forward(args)...); + } + + template + constexpr void emplace(T&& obj) { + reset(); + std::construct_at(&storage, std::in_place_index>, std::forward(obj)); + } + + template + constexpr decltype(auto) get(this Self&& self) { + return std::forward(self).storage.value.template get(); + } + + template + constexpr decltype(auto) get(this Self&& self) { + return std::forward(self).template get>(); + } + + constexpr bool valueless_by_exception() const noexcept{ + return index() == npos; + } +}; +} // namespace slo \ No newline at end of file diff --git a/include/slo/util/compat.h b/include/slo/util/compat.h new file mode 100644 index 0000000..dd4cab5 --- /dev/null +++ b/include/slo/util/compat.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include + +namespace slo::compat { +#if __cpp_lib_forward_like < 202207L +template +[[nodiscard]] constexpr auto&& forward_like(U&& obj) noexcept { + if constexpr (std::is_const_v>) { + if constexpr (std::is_lvalue_reference_v) { + return static_cast const&>(obj); + } else { + return static_cast const&&>(obj); + } + } else { + if constexpr (std::is_lvalue_reference_v) { + return static_cast(obj); + } else { + return static_cast&&>(obj); + } + } +} +#else +using std::forward_like; +#endif + +// standard libraries don't expose this one but it is rather handy +template +using like_t = decltype(forward_like(std::declval())); + +#if __cpp_lib_within_lifetime < 202306L +template +constexpr bool is_within_lifetime(const T* ptr) noexcept { + // this is only constexpr because clang does not like it being consteval +# if __has_builtin(__builtin_constant_p) + return __builtin_constant_p(*ptr); +# else + return 0; +# endif +} +#else +using std::is_within_lifetime; +#endif +} // namespace slo::compat \ No newline at end of file diff --git a/include/slo/util/concepts.h b/include/slo/util/concepts.h new file mode 100644 index 0000000..eaa4865 --- /dev/null +++ b/include/slo/util/concepts.h @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +namespace slo::util { +template +concept all_same = (std::same_as && ...); + +template +concept is_enum = std::is_enum_v; + +template +concept is_class = std::is_class_v; + +template +concept is_union = std::is_union_v; +} \ No newline at end of file diff --git a/include/slo/util/list.h b/include/slo/util/list.h new file mode 100644 index 0000000..5eab07e --- /dev/null +++ b/include/slo/util/list.h @@ -0,0 +1,67 @@ +#pragma once +#include + +namespace slo::util { + +template +struct TypeList; + +namespace detail { +template