├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── Credits.md ├── LICENSE ├── README.md ├── clean_core.natvis ├── cmake ├── AddLibrary.cmake ├── AddTest.cmake ├── CompileOptions.cmake ├── SourceGroup.cmake └── UnityBuild.cmake ├── src └── clean-core │ ├── algorithms.hh │ ├── alloc_array.hh │ ├── alloc_vector.hh │ ├── allocate.hh │ ├── allocator.cc │ ├── allocator.hh │ ├── allocators │ ├── atomic_linear_allocator.cc │ ├── atomic_linear_allocator.hh │ ├── atomic_pool_allocator.cc │ ├── atomic_pool_allocator.hh │ ├── linear_allocator.cc │ ├── linear_allocator.hh │ ├── stack_allocator.cc │ ├── stack_allocator.hh │ ├── synced_tlsf_allocator.cc │ ├── synced_tlsf_allocator.hh │ ├── synced_virtual_linear_allocator.cc │ ├── synced_virtual_linear_allocator.hh │ ├── system_allocator.cc │ ├── system_allocator.hh │ ├── tlsf_allocator.cc │ ├── tlsf_allocator.hh │ ├── virtual_linear_allocator.cc │ ├── virtual_linear_allocator.hh │ ├── virtual_stack_allocator.cc │ └── virtual_stack_allocator.hh │ ├── always_false.hh │ ├── any_of.hh │ ├── apply.hh │ ├── array.hh │ ├── assert.cc │ ├── assert.hh │ ├── assertf.hh │ ├── atomic_linked_pool.cc │ ├── atomic_linked_pool.hh │ ├── base64.cc │ ├── base64.hh │ ├── bit_cast.hh │ ├── bits.hh │ ├── bitset.hh │ ├── breakpoint.cc │ ├── breakpoint.hh │ ├── capped_array.hh │ ├── capped_vector.hh │ ├── char_predicates.hh │ ├── collection_traits.hh │ ├── cursor.hh │ ├── defer.hh │ ├── demangle.cc │ ├── demangle.hh │ ├── detail │ ├── compact_size_t.hh │ ├── container_impl_util.hh │ ├── is_reference_wrapper.hh │ ├── lib │ │ ├── StackWalker.cc │ │ ├── StackWalker.hh │ │ ├── tlsf.cc │ │ └── tlsf.hh │ ├── remove_reference.hh │ ├── srange.hh │ ├── vector_base.hh │ └── xxHash │ │ ├── xxh3.hh │ │ ├── xxhash.cc │ │ └── xxhash.hh │ ├── dont_deduce.hh │ ├── enable_if.hh │ ├── equal_to.hh │ ├── error_messages.cc │ ├── error_messages.hh │ ├── experimental │ ├── box.hh │ ├── capped_box.hh │ ├── chunked_buffer.hh │ ├── clamped_span.hh │ ├── disjoint_set.hh │ ├── filewatch.cc │ ├── filewatch.hh │ ├── flat_vector_of_vector.hh │ ├── fwd_box.hh │ ├── mpmc_queue.hh │ ├── pimpl.hh │ ├── poly_box.hh │ ├── ringbuffer.hh │ └── wrapped_span.hh │ ├── explicit_bool.hh │ ├── flags.hh │ ├── format.cc │ ├── format.hh │ ├── forward.hh │ ├── forward_list.hh │ ├── from_string.cc │ ├── from_string.hh │ ├── function_ptr.hh │ ├── function_ref.hh │ ├── functors.hh │ ├── fwd.hh │ ├── fwd_array.hh │ ├── get.hh │ ├── has_operator.hh │ ├── hash.cc │ ├── hash.hh │ ├── hash.sha1.cc │ ├── hash.sha1.hh │ ├── hash.xxh3.cc │ ├── hash.xxh3.hh │ ├── hash_combine.hh │ ├── indices_of.hh │ ├── intrinsics.hh │ ├── invoke.hh │ ├── is_contiguous_range.hh │ ├── is_range.hh │ ├── iterator.hh │ ├── less.hh │ ├── lock_guard.hh │ ├── macros.hh │ ├── map.hh │ ├── move.hh │ ├── native │ ├── detail │ │ ├── win32_sanitize_after.inl │ │ └── win32_sanitize_before.inl │ ├── memory.cc │ ├── memory.hh │ ├── timing.cc │ ├── timing.hh │ ├── wchar_conversion.cc │ ├── wchar_conversion.hh │ ├── win32_fwd.hh │ ├── win32_sanitized.hh │ ├── win32_util.cc │ └── win32_util.hh │ ├── new.hh │ ├── optional.hh │ ├── overloaded.hh │ ├── pair.hh │ ├── poly_unique_ptr.hh │ ├── polymorphic.hh │ ├── print.cc │ ├── print.hh │ ├── priority_tag.hh │ ├── range_ref.hh │ ├── result.hh │ ├── sbo_string.hh │ ├── sentinel.hh │ ├── set.hh │ ├── shared_ptr.hh │ ├── sort.hh │ ├── span.hh │ ├── spin_lock.hh │ ├── storage.hh │ ├── stream_ref.hh │ ├── strided_span.hh │ ├── string.hh │ ├── string_stream.hh │ ├── string_view.hh │ ├── stringhash.hh │ ├── temp_cstr.cc │ ├── temp_cstr.hh │ ├── to_string.cc │ ├── to_string.hh │ ├── tuple.hh │ ├── type_id.hh │ ├── unique_function.hh │ ├── unique_ptr.hh │ ├── utility.hh │ ├── variant.hh │ ├── vector.hh │ ├── vector_ex.hh │ └── xxHash.hh └── tests ├── CMakeLists.txt ├── alloc-benchmark.cc ├── allocator.cc ├── any_of.cc ├── array.cc ├── base64.cc ├── bits.cc ├── bitset.cc ├── chunked_buffer.cc ├── clamped_span.cc ├── collection_traits.cc ├── flags.cc ├── format.cc ├── forward_list.cc ├── from_string.cc ├── function_ref.cc ├── functors.cc ├── hash-comparison.cc ├── hash.cc ├── hash.sha1.cc ├── indices_of.cc ├── invoke.cc ├── main.cc ├── map.cc ├── optional.cc ├── pretty-printer.cc ├── range_ref.cc ├── result.cc ├── set.cc ├── sort.cc ├── span.cc ├── special_types.hh ├── stream_ref.cc ├── strided_span.cc ├── string.cc ├── string_stream.cc ├── string_view.cc ├── structured_bindings.cc ├── swap.cc ├── to_string.cc ├── tuple.cc ├── types.cc ├── unique_function.cc ├── unique_ptr.cc ├── utility.cc ├── values.cc ├── variant.cc ├── vector.cc └── wrapped_span.cc /.clang-format: -------------------------------------------------------------------------------- 1 | # Style file for clang-format ( http://clang.llvm.org/docs/ClangFormatStyleOptions.html ) 2 | 3 | # Style is based on google's c++ coding style. 4 | # see http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml 5 | BasedOnStyle: Google 6 | #Language: Cpp 7 | Standard: Cpp11 8 | 9 | 10 | # 80 columns guideline 11 | ColumnLimit: 150 12 | PenaltyExcessCharacter: 1 13 | PenaltyBreakString: 50 14 | 15 | 16 | # Indentation and Braces 17 | IndentWidth: 4 18 | AllowShortIfStatementsOnASingleLine: false 19 | AllowShortLoopsOnASingleLine: false 20 | AllowShortFunctionsOnASingleLine: All 21 | AlwaysBreakBeforeMultilineStrings: false 22 | AlwaysBreakTemplateDeclarations: true 23 | #BraceBreakingStyle: ??? 24 | BreakBeforeBinaryOperators: true 25 | BreakBeforeTernaryOperators: true 26 | BreakConstructorInitializersBeforeComma: false 27 | BreakBeforeBraces: Allman 28 | BinPackParameters: false 29 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 30 | Cpp11BracedListStyle: true 31 | IndentCaseLabels: false 32 | 33 | 34 | # Spaces 35 | DerivePointerAlignment: false 36 | PointerAlignment: Left 37 | DerivePointerBinding: true 38 | MaxEmptyLinesToKeep: 2 39 | SpaceAfterControlStatementKeyword: true 40 | SpaceBeforeAssignmentOperators: true 41 | SpaceInEmptyParentheses: false 42 | SpacesBeforeTrailingComments: 1 43 | SpacesInAngles: false 44 | SpacesInCStyleCastParentheses: false 45 | SpacesInParentheses: false 46 | #SpacesInSquareBrackets: false 47 | UseTab: Never 48 | ConstructorInitializerIndentWidth: 2 49 | AccessModifierOffset: -4 50 | 51 | 52 | # Comments 53 | FixNamespaceComments: true 54 | AlignTrailingComments: true 55 | 56 | # Includes 57 | IncludeBlocks: Preserve 58 | 59 | # east const 60 | QualifierAlignment: Right 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | *.txt.user 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | project(CleanCore) 3 | 4 | # ========================================= 5 | # global options 6 | 7 | option(CC_STRICT "if true, enables all warnings and -Werror" OFF) 8 | option(CC_ENABLE_ASSERTIONS "if true, enables assertions (also in RelWithDebInfo)" ON) 9 | option(CC_ENABLE_BOUND_CHECKING "if true, enables bound checking (e.g. for containers and iterators, only if assertions are active)" ON) 10 | option(CC_ENABLE_NULL_CHECKING "if true, enables null checking (e.g. for smart pointers, only if assertions are active)" ON) 11 | option(CC_ENABLE_CONTRACT_CHECKING "if true, enables contract checking (e.g. pre- and postconditions, only if assertions are active)" ON) 12 | option(CC_VERBOSE_CMAKE "if true, adds more verbose cmake output (for debugging)" OFF) 13 | option(CC_ENABLE_ADDRESS_SANITIZER "if true, enables address sanitizer on all arcana libraries" OFF) 14 | 15 | # ========================================= 16 | # import scripts 17 | 18 | include(cmake/UnityBuild.cmake) 19 | include(cmake/SourceGroup.cmake) 20 | include(cmake/CompileOptions.cmake) 21 | include(cmake/AddLibrary.cmake) 22 | 23 | # TODO: make configurable 24 | include(cmake/AddTest.cmake) 25 | 26 | # ========================================= 27 | # define library 28 | 29 | file(GLOB_RECURSE SOURCES "src/*.cc") 30 | file(GLOB_RECURSE HEADERS "src/*.hh" "src/*.inl") 31 | file(GLOB_RECURSE MISC_FILES "*.natvis") 32 | 33 | # set up source_group, optionally enable unity builds, add the library 34 | arcana_add_library(CC clean-core SOURCES HEADERS) 35 | 36 | # add natvis files (visual studio debug views for span, vector, etc.) 37 | if (${CMAKE_GENERATOR} MATCHES "Visual Studio" OR WIN32) 38 | target_sources(clean-core PUBLIC ${MISC_FILES}) 39 | endif() 40 | 41 | target_include_directories(clean-core PUBLIC src/) 42 | 43 | # ========================================= 44 | # set up compile flags 45 | 46 | # default to RelWithDebInfo 47 | if(NOT CMAKE_BUILD_TYPE) 48 | set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING 49 | "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) 50 | endif() 51 | 52 | target_link_libraries(clean-core PUBLIC 53 | $<$:-pthread> 54 | ) 55 | 56 | target_compile_definitions(clean-core PUBLIC 57 | $<$:CC_DEBUG> 58 | $<$:CC_RELEASE> 59 | $<$:CC_RELWITHDEBINFO> 60 | ) 61 | 62 | if (CC_ENABLE_ASSERTIONS) 63 | target_compile_definitions(clean-core PUBLIC $<$:CC_ENABLE_ASSERTIONS>) 64 | target_compile_definitions(clean-core PUBLIC $<$:CC_ENABLE_ASSERTIONS>) 65 | endif() 66 | 67 | if (CC_ENABLE_BOUND_CHECKING) 68 | target_compile_definitions(clean-core PUBLIC CC_ENABLE_BOUND_CHECKING) 69 | endif() 70 | 71 | if (CC_ENABLE_NULL_CHECKING) 72 | target_compile_definitions(clean-core PUBLIC CC_ENABLE_NULL_CHECKING) 73 | endif() 74 | 75 | if (CC_ENABLE_CONTRACT_CHECKING) 76 | target_compile_definitions(clean-core PUBLIC CC_ENABLE_CONTRACT_CHECKING) 77 | endif() 78 | -------------------------------------------------------------------------------- /Credits.md: -------------------------------------------------------------------------------- 1 | # Credits 2 | 3 | ## xxHash 4 | 5 | ``` 6 | /* 7 | * xxHash - Extremely Fast Hash algorithm 8 | * Development source file for `xxh3` 9 | * Copyright (C) 2019-2020 Yann Collet 10 | * 11 | * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php) 12 | * 13 | * Redistribution and use in source and binary forms, with or without 14 | * modification, are permitted provided that the following conditions are 15 | * met: 16 | * 17 | * * Redistributions of source code must retain the above copyright 18 | * notice, this list of conditions and the following disclaimer. 19 | * * Redistributions in binary form must reproduce the above 20 | * copyright notice, this list of conditions and the following disclaimer 21 | * in the documentation and/or other materials provided with the 22 | * distribution. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 27 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 28 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 29 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 30 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 32 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | * 36 | * You can contact the author at: 37 | * - xxHash homepage: https://www.xxhash.com 38 | * - xxHash source repository: https://github.com/Cyan4973/xxHash 39 | */ 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Philip Trettner 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 | # clean-core 2 | clean-core (`cc`) is a clean and lean reimagining of the C++ standard library. 3 | 4 | ## Goals 5 | 6 | * significantly faster to compile than `std` 7 | * forward declaration for all public types 8 | * no slower than `std` 9 | * safer than `std` 10 | * more modular header design (each type can be separately included) 11 | * convenient interfacing with code using `std` types 12 | * removal of unintuitive behavior (e.g. `vector` or `optional::operator bool`) 13 | * mostly keeping naming scheme and intent of `std` 14 | * better debugging support and performance 15 | * no dependency on `exception`s 16 | 17 | ## Requirements / Dependencies 18 | 19 | * a C++17 compiler 20 | * a few `std` features (that are hard to otherwise implement) 21 | 22 | ## Allowed `std` includes 23 | 24 | * `` (1ms, for memcpy) 25 | * `` (1ms, for some types) 26 | * `` (5-8ms, many not implementable ourselves) 27 | * `` (10-15ms, swap, declval, NOTE: `cc` has own move and forward) 28 | * `` (20ms, hard to implement ourselves) 29 | 30 | ## Notable Changes 31 | 32 | Changes that were rarely used features that increased implementation cost immensely: 33 | 34 | * no `allocator`s 35 | * no custom deleters for `unique_ptr` 36 | 37 | Error-prone and unintuitive or suprising features: 38 | 39 | * no specialized `vector` 40 | * no `operator bool` for `optional` 41 | * no `operator<` for `optional` 42 | 43 | Others: 44 | 45 | * no strong `exception` support 46 | * no iterator-pair library, only ranges 47 | * no unreadable `_UglyCase` (leaking non-caps macros is a sin) 48 | * traits types and values are not suffixed with `_t` or `_v` 49 | * no `volatile` support 50 | * no `unique_ptr` 51 | * some interfaces are stricter to prevent easy mistakes 52 | 53 | ## New Features 54 | 55 | * `span` (strided array view) 56 | * `flat_` containers 57 | * `inline_` types (no heap allocs) 58 | * customizable low-impact `assert` 59 | * internal assertions (optional) 60 | * bound-checked containers 61 | * null checks for smart pointer 62 | * contract checks 63 | 64 | ## TODO 65 | 66 | * big list of comparison between `std` and `cc` 67 | * name of feature/class (e.g. `pair`) 68 | * header name of `std`/`cc` 69 | * parse time `std`/`cc` 70 | * preprocessed, significant LOC of `std`/`cc` 71 | -------------------------------------------------------------------------------- /clean_core.natvis: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | {{ vector size={_size} }} 17 | 18 | _size 19 | _capacity 20 | 21 | _size 22 | _data 23 | 24 | 25 | 26 | 27 | 28 | {{ alloc_vector size={_size} }} 29 | 30 | _size 31 | _capacity 32 | _allocator 33 | 34 | _size 35 | _data 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {{ array size={_size} }} 44 | 45 | _size 46 | 47 | _size 48 | _data 49 | 50 | 51 | 52 | 53 | 54 | 55 | {{ alloc_array size={_size} }} 56 | 57 | _size 58 | _allocator 59 | 60 | size() 61 | _data 62 | 63 | 64 | 65 | 66 | 67 | 68 | {{ span size={_size} }} 69 | 70 | 71 | _size 72 | _data 73 | 74 | 75 | 76 | 77 | 78 | 79 | {{ string {_data,[_size]s} }} 80 | 81 | _data 82 | _size 83 | _data == _sbo 84 | 85 | 86 | 87 | 88 | {{ string_view {_data,[_size]s} }} 89 | 90 | _data,_size 91 | _size 92 | 93 | 94 | 95 | 96 | 97 | {{ span2 size={_size_x}x{_size_y} }} 98 | 99 | 100 | Forward 101 | 2 102 | $i == 0 ? _size_x : _size_y 103 | ($T1*) _data 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /cmake/AddLibrary.cmake: -------------------------------------------------------------------------------- 1 | 2 | # usage: 3 | # arcana_add_library(ML my-lib SOURCES HEADERS) 4 | # 5 | # (where ML is the prefix used in cmake options for my-lib) 6 | # (NOTE: sources and headers are variable names, do NOT use ${SOURCES} and ${HEADERS}) 7 | # 8 | # - calls add_library 9 | # - sets up source groups for sources and headers 10 | # - enables unity builds for all sources (including an option to opt-out) 11 | # - sets up compile flags (errors, warning level, etc.) 12 | function(arcana_add_library LIB_PREFIX LIB_TARGET SOURCES_VARIABLE_NAME HEADERS_VARIABLE_NAME) 13 | 14 | if (CC_VERBOSE_CMAKE) 15 | message(STATUS "[${LIB_TARGET}] configuring library") 16 | endif() 17 | 18 | source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/src" FILES ${${SOURCES_VARIABLE_NAME}} ${${HEADERS_VARIABLE_NAME}}) 19 | 20 | option(${LIB_PREFIX}_ENABLE_UNITY_BUILD "If enabled, compiles this library as a single compilation unit" ON) 21 | option(${LIB_PREFIX}_BUILD_DLL "If enabled, build a shared DLL instead of a static library" OFF) 22 | 23 | if (${${LIB_PREFIX}_ENABLE_UNITY_BUILD}) 24 | if (CC_VERBOSE_CMAKE) 25 | message(STATUS "[${LIB_TARGET}] enabling unity builds") 26 | endif() 27 | arcana_enable_unity_build(${LIB_TARGET} ${SOURCES_VARIABLE_NAME} 100 cc) 28 | endif() 29 | 30 | if (${${LIB_PREFIX}_BUILD_DLL}) 31 | if (CC_VERBOSE_CMAKE) 32 | message(STATUS "[${LIB_TARGET}] building shared library (DLL)") 33 | endif() 34 | 35 | add_library(${LIB_TARGET} SHARED ${${SOURCES_VARIABLE_NAME}} ${${HEADERS_VARIABLE_NAME}}) 36 | # define _BUILD_DLL always, and _DLL only privately - when the DLL itself is being built 37 | # macro is used to differentiate between dllexport/dllimport 38 | target_compile_definitions(${LIB_TARGET} PUBLIC ${LIB_PREFIX}_BUILD_DLL PRIVATE ${LIB_PREFIX}_DLL) 39 | else() 40 | if (CC_VERBOSE_CMAKE) 41 | message(STATUS "[${LIB_TARGET}] building static library") 42 | endif() 43 | add_library(${LIB_TARGET} STATIC ${${SOURCES_VARIABLE_NAME}} ${${HEADERS_VARIABLE_NAME}}) 44 | endif() 45 | 46 | arcana_configure_lib_options(${LIB_TARGET}) 47 | 48 | endfunction() 49 | 50 | # same as arcana_add_library but without unity build 51 | function(arcana_add_library_no_unity LIB_PREFIX LIB_TARGET SOURCES_VARIABLE_NAME HEADERS_VARIABLE_NAME) 52 | 53 | if (CC_VERBOSE_CMAKE) 54 | message(STATUS "[${LIB_TARGET}] configuring library") 55 | endif() 56 | 57 | source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/src" FILES ${${SOURCES_VARIABLE_NAME}} ${${HEADERS_VARIABLE_NAME}}) 58 | 59 | add_library(${LIB_TARGET} STATIC ${${SOURCES_VARIABLE_NAME}} ${${HEADERS_VARIABLE_NAME}}) 60 | 61 | arcana_configure_lib_options(${LIB_TARGET}) 62 | 63 | endfunction() 64 | -------------------------------------------------------------------------------- /cmake/CompileOptions.cmake: -------------------------------------------------------------------------------- 1 | 2 | # set up compile options we want for a library 3 | function(arcana_configure_lib_options LIB_TARGET) 4 | if (MSVC) 5 | target_compile_options(${LIB_TARGET} PUBLIC 6 | /MP # multi-threaded compilation 7 | /we4715 # error on missing return 8 | /we4477 # printf warnings as errors 9 | /we4474 # printf warnings as errors 10 | ) 11 | else() 12 | target_compile_options(${LIB_TARGET} PRIVATE 13 | -Wall 14 | -fPIC 15 | -Werror=return-type # error on missing return 16 | -Werror=format # printf warnings as errors 17 | ) 18 | if(LINUX) 19 | option(CC_LINKER_MOLD "If true, uses the mold linker (default, must be installed)" ON) 20 | option(CC_LINKER_GOLD "If true, uses the gold linker (must be installed)" OFF) 21 | if (CC_LINKER_MOLD) 22 | target_link_libraries(${LIB_TARGET} PUBLIC -fuse-ld=mold) 23 | elseif (CC_LINKER_GOLD) 24 | target_link_libraries(${LIB_TARGET} PUBLIC -fuse-ld=gold) 25 | endif() 26 | endif() 27 | 28 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 29 | else() # GCC 30 | target_compile_options(${LIB_TARGET} PRIVATE 31 | -Wno-sign-compare # disable signed/unsigned warnings 32 | $<$:-Wno-class-memaccess> # disable memset warnings 33 | ) 34 | endif() 35 | endif() 36 | 37 | # disable floating point contractions (will ruin a lot of code otherwise) 38 | if (MSVC) 39 | target_compile_options(${LIB_TARGET} PUBLIC /fp:precise) 40 | else() 41 | target_compile_options(${LIB_TARGET} PUBLIC -ffp-contract=off) 42 | endif() 43 | 44 | # strict mode enables some Werror-xyz errors (mainly used in deploy and CI) 45 | if (CC_STRICT) 46 | if (CC_VERBOSE_CMAKE) 47 | message(STATUS "[${LIB_TARGET}] enable strict mode (selective Werrors)") 48 | endif() 49 | if (MSVC) 50 | target_compile_definitions(${LIB_TARGET} PRIVATE 51 | /WX # treat linker warnings as errors 52 | /we4101 # unreferenced local variable 53 | ) 54 | else() 55 | target_compile_options(${LIB_TARGET} PRIVATE 56 | # unused entities 57 | -Werror=unused-variable 58 | -Werror=unused-function 59 | 60 | -Werror=deprecated-declarations # no deprecate warnings 61 | -Werror=switch # unhandled switch statements 62 | ) 63 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 64 | target_compile_options(${LIB_TARGET} PRIVATE 65 | # more unused entities 66 | -Werror=unused-private-field 67 | -Werror=unneeded-internal-declaration 68 | ) 69 | else() # GCC 70 | target_compile_options(${LIB_TARGET} PRIVATE 71 | # more unused entities 72 | -Werror=unused-but-set-variable 73 | ) 74 | endif() 75 | endif() 76 | endif() 77 | 78 | if (CC_ENABLE_ADDRESS_SANITIZER) 79 | if (MSVC) 80 | target_compile_options(${LIB_TARGET} PUBLIC /fsanitize=address) 81 | else() 82 | target_compile_options(${LIB_TARGET} PUBLIC -fsanitize=address) 83 | endif() 84 | endif() 85 | endfunction() 86 | -------------------------------------------------------------------------------- /cmake/SourceGroup.cmake: -------------------------------------------------------------------------------- 1 | 2 | # properly groups sources for Qt Creator / Visual Studio 3 | # 4 | # usage: 5 | # 6 | # file(GLOB_RECURSE SOURCES "src/*.cc") 7 | # file(GLOB_RECURSE HEADERS "src/*.hh" "src/*.inl") 8 | # arcana_source_group(SOURCES HEADERS) 9 | # 10 | # 11 | macro(arcana_source_group) 12 | message(STATUS "arcana_source_group: in ${CMAKE_CURRENT_SOURCE_DIR}") 13 | foreach(loop_var ${ARGN}) 14 | 15 | if (${CMAKE_GENERATOR} MATCHES "Visual Studio" OR WIN32) 16 | source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/src" FILES ${${loop_var}}) 17 | else() 18 | source_group("${CMAKE_CURRENT_SOURCE_DIR}/src" FILES ${${loop_var}}) 19 | endif() 20 | 21 | endforeach() 22 | endmacro() 23 | -------------------------------------------------------------------------------- /src/clean-core/algorithms.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cc 7 | { 8 | template 9 | constexpr void copy(RangeFrom const& from, RangeTo& to) 10 | { 11 | auto c_from = cc::to_cursor(from); 12 | auto c_to = cc::to_cursor(to); 13 | while (c_from) 14 | { 15 | CC_CONTRACT(c_to); 16 | *c_to = *c_from; 17 | ++c_to, ++c_from; 18 | } 19 | } 20 | 21 | template 22 | constexpr void fill(Range& range, T const& value) 23 | { 24 | auto c = cc::to_cursor(range); 25 | while (c) 26 | { 27 | *c = value; 28 | ++c; 29 | } 30 | } 31 | 32 | template 33 | [[nodiscard]] constexpr bool are_ranges_equal(RangeA const& a, RangeB const& b) 34 | { 35 | if (a.size() != b.size()) 36 | return false; 37 | 38 | auto ca = cc::to_cursor(a); 39 | auto cb = cc::to_cursor(b); 40 | 41 | while (ca) 42 | { 43 | if (*ca != *cb) 44 | return false; 45 | ++ca, ++cb; 46 | } 47 | 48 | return true; 49 | } 50 | 51 | template 52 | [[nodiscard]] constexpr bool are_ranges_unequal(RangeA const& a, RangeB const& b) 53 | { 54 | if (a.size() != b.size()) 55 | return true; 56 | 57 | auto ca = cc::to_cursor(a); 58 | auto cb = cc::to_cursor(b); 59 | 60 | while (ca) 61 | { 62 | if (*ca != *cb) 63 | return true; 64 | ++ca, ++cb; 65 | } 66 | 67 | return false; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/clean-core/allocator.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | char* cc::allocator::alloc_string_copy(cc::string_view source) 13 | { 14 | size_t const source_length = source.length(); 15 | char* const res = reinterpret_cast(this->alloc(source_length + 1, alignof(char))); 16 | 17 | // check because empty source is a legitimate case (allocates a single 0 char) 18 | if (source_length > 0) 19 | { 20 | std::memcpy(res, source.data(), source_length); 21 | } 22 | res[source_length] = '\0'; 23 | return res; 24 | } 25 | 26 | std::byte* cc::allocator::realloc(void* ptr, size_t new_size, size_t align) 27 | { 28 | std::byte* res = nullptr; 29 | 30 | if (new_size > 0) 31 | { 32 | res = this->alloc(new_size, align); 33 | 34 | if (ptr != nullptr) 35 | { 36 | size_t old_size = 0; 37 | bool got_old_size = this->get_allocation_size(ptr, old_size); 38 | CC_RUNTIME_ASSERT(got_old_size && "Allocator with default realloc failed to provide old allocation size"); 39 | 40 | std::memcpy(res, ptr, cc::min(old_size, new_size)); 41 | } 42 | } 43 | 44 | this->free(ptr); 45 | return res; 46 | } 47 | 48 | std::byte* cc::allocator::try_alloc(size_t size, size_t alignment) { return this->alloc(size, alignment); } 49 | 50 | std::byte* cc::allocator::try_realloc(void* ptr, size_t new_size, size_t alignment) { return this->realloc(ptr, new_size, alignment); } 51 | -------------------------------------------------------------------------------- /src/clean-core/allocators/atomic_linear_allocator.cc: -------------------------------------------------------------------------------- 1 | #include "atomic_linear_allocator.hh" -------------------------------------------------------------------------------- /src/clean-core/allocators/atomic_linear_allocator.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace cc 8 | { 9 | /// thread safe version of cc::linear_allocator 10 | /// Can also realloc any allocation 11 | /// Will take up more space than strictly necessary in the given buffer 12 | struct atomic_linear_allocator final : allocator 13 | { 14 | std::byte* alloc(size_t size, size_t align = alignof(std::max_align_t)) override 15 | { 16 | CC_ASSERT(_buffer_begin != nullptr && "atomic_linear_allocator unintialized"); 17 | 18 | align = cc::max(align, 1); 19 | 20 | auto const buffer_size = size + (align - 1) + sizeof(size_t); // align worst case buffer to satisfy up-aligning, add header field for alloc size 21 | auto const buffer_start = _offset.fetch_add(buffer_size, std::memory_order_acquire); 22 | 23 | auto* const alloc_start = _buffer_begin + buffer_start; 24 | auto* const padded_res = cc::align_up(alloc_start + sizeof(size_t), align); 25 | 26 | // store alloc size 27 | *((size_t*)(padded_res - sizeof(size_t))) = size; 28 | 29 | CC_ASSERT(padded_res - (alloc_start + sizeof(size_t)) < std::ptrdiff_t(align) && "up-align OOB"); 30 | CC_ASSERT(padded_res + size <= _buffer_end && "atomic_linear_allocator overcommitted"); 31 | 32 | return padded_res; 33 | } 34 | 35 | void free(void* ptr) override 36 | { 37 | // no-op 38 | (void)ptr; 39 | } 40 | 41 | bool get_allocation_size(void const* ptr, size_t& out_size) override 42 | { 43 | if (!ptr) 44 | return false; 45 | 46 | out_size = *((size_t const*)((std::byte const*)ptr - sizeof(size_t))); 47 | return true; 48 | } 49 | 50 | char const* get_name() const override { return "Atomic Linear Allocator"; } 51 | 52 | void reset() { _offset.store(0, std::memory_order_release); } 53 | 54 | size_t allocated_size() const { return _offset.load(); } 55 | size_t max_size() const { return _buffer_end - _buffer_begin; } 56 | float allocated_ratio() const { return allocated_size() / float(max_size()); } 57 | 58 | atomic_linear_allocator() = default; 59 | explicit atomic_linear_allocator(span buffer) : _buffer_begin(buffer.data()), _offset(0), _buffer_end(buffer.data() + buffer.size()) {} 60 | 61 | void initialize(span buffer) 62 | { 63 | // atomics cant be moved, making this necessary 64 | _buffer_begin = buffer.data(); 65 | _offset = 0; 66 | _buffer_end = buffer.data() + buffer.size(); 67 | } 68 | 69 | private: 70 | std::byte* _buffer_begin = nullptr; 71 | std::atomic _offset = {0}; 72 | std::byte* _buffer_end = nullptr; 73 | }; 74 | } -------------------------------------------------------------------------------- /src/clean-core/allocators/atomic_pool_allocator.cc: -------------------------------------------------------------------------------- 1 | #include "atomic_pool_allocator.hh" 2 | 3 | cc::atomic_pool_allocator::atomic_pool_allocator(span buffer, size_t block_size) { initialize(buffer, block_size); } 4 | 5 | void cc::atomic_pool_allocator::initialize(span buffer, size_t block_size) 6 | { 7 | CC_ASSERT(_buffer_begin == nullptr && "double initialize"); 8 | _buffer_begin = buffer.data(); 9 | _buffer_size = buffer.size(); 10 | _block_size = block_size; 11 | 12 | CC_ASSERT(_block_size >= sizeof(std::byte*) && "blocks must be large enough to accomodate a pointer"); 13 | CC_ASSERT(_block_size <= _buffer_size && "not enough memory to allocate a single block"); 14 | 15 | size_t const num_blocks = _buffer_size / _block_size; 16 | 17 | // initialize linked list 18 | for (auto i = 0u; i < num_blocks - 1; ++i) 19 | { 20 | std::byte* node_ptr = &_buffer_begin[i * block_size]; 21 | new (cc::placement_new, node_ptr) std::byte*(&_buffer_begin[(i + 1) * block_size]); 22 | } 23 | 24 | // initialize linked list tail 25 | { 26 | std::byte* tail_ptr = &_buffer_begin[(num_blocks - 1) * block_size]; 27 | new (cc::placement_new, tail_ptr) std::byte*(nullptr); 28 | } 29 | 30 | _first_free_node = &_buffer_begin[0]; 31 | } 32 | -------------------------------------------------------------------------------- /src/clean-core/allocators/linear_allocator.cc: -------------------------------------------------------------------------------- 1 | #include "linear_allocator.hh" -------------------------------------------------------------------------------- /src/clean-core/allocators/linear_allocator.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | /// trivial linear allocator operating in a given buffer 8 | /// cannot free individual allocations, only reset entirely 9 | /// Will only take as much space as strictly necessary (no allocation headers) 10 | /// 11 | /// RESTRICTION: Must only realloc the most recent allocation 12 | struct linear_allocator final : allocator 13 | { 14 | std::byte* alloc(size_t size, size_t align = alignof(std::max_align_t)) override 15 | { 16 | CC_ASSERT(_buffer_begin != nullptr && "linear_allocator uninitialized"); 17 | 18 | align = cc::max(align, 1); 19 | 20 | auto* const padded_res = cc::align_up(_head, align); 21 | CC_ASSERT(padded_res + size <= _buffer_end && "linear_allocator overcommitted"); 22 | 23 | _head = padded_res + size; 24 | _latest_allocation = padded_res; 25 | 26 | return padded_res; 27 | } 28 | 29 | void free(void* ptr) override 30 | { 31 | // no-op 32 | (void)ptr; 33 | } 34 | 35 | bool get_allocation_size(void const* ptr, size_t& out_size) override 36 | { 37 | if (!ptr || ptr != _latest_allocation) 38 | return false; 39 | 40 | out_size = _head - _latest_allocation; 41 | return true; 42 | } 43 | 44 | std::byte* realloc(void* ptr, size_t new_size, size_t align = alignof(std::max_align_t)) override 45 | { 46 | if (ptr) 47 | { 48 | // real realloc 49 | 50 | // for a "generally usable" linear allocator without any restriction, use atomic_linear_allocator 51 | // linear_allocator guarantees to only use as much space as strictly necessary and thus can't store headers 52 | CC_ASSERT(ptr == _latest_allocation && "linear_allocator can only realloc the most recent allocation"); 53 | 54 | std::byte* const ptr_byte = static_cast(ptr); 55 | CC_ASSERT(ptr_byte + new_size <= _buffer_end && "linear_allocator overcommitted"); 56 | 57 | _head = ptr_byte + new_size; 58 | return ptr_byte; 59 | } 60 | 61 | // fall back to default behavior 62 | return cc::allocator::realloc(ptr, new_size, align); 63 | } 64 | 65 | char const* get_name() const override { return "Linear Allocator"; } 66 | 67 | void reset() 68 | { 69 | _head = _buffer_begin; 70 | _latest_allocation = nullptr; 71 | } 72 | 73 | size_t allocated_size() const { return _head - _buffer_begin; } 74 | size_t remaining_size() const { return _buffer_end - _head; } 75 | size_t max_size() const { return _buffer_end - _buffer_begin; } 76 | float allocated_ratio() const { return allocated_size() / float(max_size()); } 77 | 78 | std::byte* buffer() const { return _buffer_begin; } 79 | 80 | linear_allocator() = default; 81 | explicit linear_allocator(span buffer) : _buffer_begin(buffer.data()), _head(buffer.data()), _buffer_end(buffer.data() + buffer.size()) 82 | { 83 | } 84 | 85 | private: 86 | std::byte* _buffer_begin = nullptr; 87 | std::byte* _head = nullptr; 88 | std::byte* _buffer_end = nullptr; 89 | std::byte* _latest_allocation = nullptr; 90 | }; 91 | } -------------------------------------------------------------------------------- /src/clean-core/allocators/stack_allocator.cc: -------------------------------------------------------------------------------- 1 | #include "stack_allocator.hh" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | std::byte* cc::stack_allocator::alloc(size_t size, size_t align) 11 | { 12 | CC_ASSERT(_buffer_begin != nullptr && "stack_allocator uninitialized"); 13 | 14 | auto* const padded_res = align_up_with_header(_head, align, sizeof(stack_alloc_header)); 15 | 16 | CC_ASSERT(padded_res + size <= _buffer_end && "stack_allocator overcommitted"); 17 | 18 | ++_last_alloc_id; 19 | stack_alloc_header header = {}; 20 | header.padding = uint32_t(padded_res - _head); 21 | header.alloc_id = _last_alloc_id; 22 | 23 | ::memcpy(padded_res - sizeof(header), &header, sizeof(header)); 24 | 25 | _head = padded_res + size; 26 | return padded_res; 27 | } 28 | 29 | void cc::stack_allocator::free(void* ptr) 30 | { 31 | if (ptr == nullptr) 32 | return; 33 | 34 | std::byte* const byte_ptr = static_cast(ptr); 35 | stack_alloc_header const* const alloc_header = (stack_alloc_header*)(byte_ptr - sizeof(stack_alloc_header)); 36 | 37 | CC_ASSERT(alloc_header->alloc_id == _last_alloc_id && "freed ptr was not the most recent allocation"); 38 | --_last_alloc_id; 39 | 40 | _head = byte_ptr - alloc_header->padding; 41 | } 42 | 43 | std::byte* cc::stack_allocator::realloc(void* ptr, size_t new_size, size_t align) 44 | { 45 | if (new_size == 0) 46 | { 47 | // free case 48 | this->free(ptr); 49 | return nullptr; 50 | } 51 | else if (ptr == nullptr) 52 | { 53 | // malloc case 54 | return this->alloc(new_size, align); 55 | } 56 | 57 | (void)align; 58 | // no need to memcpy, the memory remains the same 59 | std::byte* const byte_ptr = static_cast(ptr); 60 | stack_alloc_header const* const alloc_header = (stack_alloc_header*)(byte_ptr - sizeof(stack_alloc_header)); 61 | 62 | CC_ASSERT(alloc_header->alloc_id == _last_alloc_id && "realloc ptr was not the most recent allocation"); 63 | CC_ASSERT(byte_ptr + new_size <= _buffer_end && "stack_allocator overcommitted"); 64 | // CC_ASSERT(old_size == _head - byte_ptr && "incorrect old size"); 65 | 66 | _head = byte_ptr + new_size; 67 | return byte_ptr; 68 | } 69 | -------------------------------------------------------------------------------- /src/clean-core/allocators/stack_allocator.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | /// stack allocator operating in a given buffer 8 | /// like a linear allocator, but can also free the most recent allocation 9 | /// 10 | /// RESTRICTION: Must only free or realloc the most recent allocation 11 | struct stack_allocator final : allocator 12 | { 13 | std::byte* alloc(size_t size, size_t align = alignof(std::max_align_t)) override; 14 | 15 | /// NOTE: ptr must be the most recent allocation received 16 | void free(void* ptr) override; 17 | 18 | /// NOTE: ptr must be the most recent allocation received 19 | std::byte* realloc(void* ptr, size_t new_size, size_t align = alignof(std::max_align_t)) override; 20 | 21 | char const* get_name() const override { return "Stack Allocator"; } 22 | 23 | void reset() 24 | { 25 | _head = _buffer_begin; 26 | _last_alloc_id = 0; 27 | } 28 | 29 | stack_allocator() = default; 30 | explicit stack_allocator(span buffer) : _buffer_begin(buffer.data()), _head(buffer.data()), _buffer_end(buffer.data() + buffer.size()) 31 | { 32 | } 33 | 34 | private: 35 | std::byte* _buffer_begin = nullptr; 36 | std::byte* _head = nullptr; 37 | std::byte* _buffer_end = nullptr; 38 | int32_t _last_alloc_id = 0; 39 | }; 40 | } -------------------------------------------------------------------------------- /src/clean-core/allocators/synced_tlsf_allocator.cc: -------------------------------------------------------------------------------- 1 | #include "synced_tlsf_allocator.hh" -------------------------------------------------------------------------------- /src/clean-core/allocators/synced_tlsf_allocator.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace cc 10 | { 11 | /// Synchronized (mutexed) version of tlsf_allocator 12 | template 13 | struct synced_tlsf_allocator final : allocator 14 | { 15 | synced_tlsf_allocator() = default; 16 | explicit synced_tlsf_allocator(cc::span buffer) : _backing(buffer) {} 17 | ~synced_tlsf_allocator() { destroy(); } 18 | 19 | 20 | std::byte* alloc(size_t size, size_t align = alignof(std::max_align_t)) override 21 | { 22 | auto lg = cc::lock_guard(_lock); 23 | return _backing.alloc(size, align); 24 | } 25 | 26 | void free(void* ptr) override 27 | { 28 | auto lg = cc::lock_guard(_lock); 29 | _backing.free(ptr); 30 | } 31 | 32 | std::byte* realloc(void* ptr, size_t new_size, size_t align = alignof(std::max_align_t)) override 33 | { 34 | auto lg = cc::lock_guard(_lock); 35 | return _backing.realloc(ptr, new_size, align); 36 | } 37 | 38 | bool get_allocation_size(void const* ptr, size_t& out_size) override 39 | { 40 | auto lg = cc::lock_guard(_lock); 41 | return _backing.get_allocation_size(ptr, out_size); 42 | } 43 | 44 | bool validate_heap() override 45 | { 46 | auto lg = cc::lock_guard(_lock); 47 | return _backing.validate_heap(); 48 | } 49 | 50 | char const* get_name() const override { return "Synced TLSF Allocator"; } 51 | 52 | void initialize(cc::span buffer) { _backing.initialize(buffer); } 53 | void destroy() 54 | { 55 | auto lg = cc::lock_guard(_lock); 56 | _backing.destroy(); 57 | } 58 | 59 | private: 60 | LockT _lock; 61 | cc::tlsf_allocator _backing; 62 | }; 63 | } -------------------------------------------------------------------------------- /src/clean-core/allocators/synced_virtual_linear_allocator.cc: -------------------------------------------------------------------------------- 1 | #include "synced_virtual_linear_allocator.hh" -------------------------------------------------------------------------------- /src/clean-core/allocators/synced_virtual_linear_allocator.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace cc 8 | { 9 | /// Synchronized (mutexed) version of virtual_linear_allocator 10 | template 11 | struct synced_virtual_linear_allocator final : allocator 12 | { 13 | synced_virtual_linear_allocator() = default; 14 | explicit synced_virtual_linear_allocator(size_t max_size_bytes, size_t chunk_size_bytes = 65536) 15 | { 16 | _backing.initialize(max_size_bytes, chunk_size_bytes); 17 | } 18 | 19 | void initialize(size_t max_size_bytes, size_t chunk_size_bytes = 65536) { _backing.initialize(max_size_bytes, chunk_size_bytes); } 20 | 21 | void destroy() 22 | { 23 | auto lg = cc::lock_guard(_mutex); 24 | _backing.destroy(); 25 | } 26 | 27 | std::byte* alloc(size_t size, size_t align = alignof(std::max_align_t)) override 28 | { 29 | auto lg = cc::lock_guard(_mutex); 30 | return _backing.alloc(size, align); 31 | } 32 | 33 | void free(void*) override {} // nothing 34 | 35 | std::byte* realloc(void* ptr, size_t new_size, size_t align = alignof(std::max_align_t)) override 36 | { 37 | auto lg = cc::lock_guard(_mutex); 38 | return _backing.realloc(ptr, new_size, align); 39 | } 40 | 41 | char const* get_name() const override { return "Synced Virtual Linear Allocator"; } 42 | 43 | size_t reset() 44 | { 45 | auto lg = cc::lock_guard(_mutex); 46 | return _backing.reset(); 47 | } 48 | 49 | // decommit the physical memory of all pages not currently required 50 | // returns amount of bytes decommitted 51 | size_t decommit_idle_memory() 52 | { 53 | auto lg = cc::lock_guard(_mutex); 54 | return _backing.decommit_idle_memory(); 55 | } 56 | 57 | cc::virtual_linear_allocator const& get_backing() const { return _backing; } 58 | 59 | private: 60 | MutexT _mutex; 61 | cc::virtual_linear_allocator _backing; 62 | }; 63 | } -------------------------------------------------------------------------------- /src/clean-core/allocators/system_allocator.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | // 8 | // underlying logic of cc::system_allocator_t as free functions 9 | // required for some pre-main scenarios (e.g. non-alloc vector) 10 | 11 | std::byte* system_malloc(size_t size, size_t alignment); 12 | 13 | std::byte* system_realloc(void* ptr, size_t new_size, size_t align); 14 | 15 | size_t system_msize(void const* ptr); 16 | 17 | void system_free(void* ptr); 18 | 19 | // system provided allocator (malloc / free) 20 | struct system_allocator_t final : cc::allocator 21 | { 22 | std::byte* try_alloc(size_t size, size_t alignment) override { return cc::system_malloc(size, alignment); } 23 | 24 | std::byte* alloc(size_t size, size_t alignment) override 25 | { 26 | std::byte* const result = this->try_alloc(size, alignment); 27 | CC_RUNTIME_ASSERT((result != nullptr || size == 0) && "Out of system memory - allocation failed"); 28 | return result; 29 | } 30 | 31 | std::byte* try_realloc(void* ptr, size_t new_size, size_t align) override { return cc::system_realloc(ptr, new_size, align); } 32 | 33 | std::byte* realloc(void* ptr, size_t new_size, size_t align) override 34 | { 35 | std::byte* result = this->try_realloc(ptr, new_size, align); 36 | CC_RUNTIME_ASSERT((result != nullptr || new_size == 0) && "Out of system memory - allocation failed"); 37 | return result; 38 | } 39 | 40 | void free(void* ptr) override { cc::system_free(ptr); } 41 | 42 | bool get_allocation_size(void const* ptr, size_t& out_size) override 43 | { 44 | out_size = cc::system_msize(ptr); 45 | return true; 46 | } 47 | 48 | bool validate_heap() override; 49 | 50 | char const* get_name() const override; 51 | 52 | constexpr system_allocator_t() = default; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /src/clean-core/allocators/tlsf_allocator.cc: -------------------------------------------------------------------------------- 1 | #include "tlsf_allocator.hh" 2 | 3 | #include 4 | 5 | #include 6 | 7 | void cc::tlsf_allocator::initialize(cc::span buffer) 8 | { 9 | CC_ASSERT(_tlsf == nullptr && "double init"); 10 | CC_ASSERT(buffer.size() > tlsf_size() && "buffer not large enough"); 11 | 12 | _tlsf = tlsf_create_with_pool(buffer.data(), buffer.size()); 13 | CC_ASSERT(_tlsf != nullptr && "failed to create TLSF"); 14 | } 15 | 16 | void cc::tlsf_allocator::destroy() 17 | { 18 | if (_tlsf) 19 | { 20 | tlsf_destroy(_tlsf); 21 | _tlsf = nullptr; 22 | } 23 | } 24 | 25 | void cc::tlsf_allocator::add_pool(cc::span buffer) 26 | { 27 | CC_ASSERT(_tlsf && "unitialized"); 28 | void* pool = tlsf_add_pool(_tlsf, buffer.data(), buffer.size()); 29 | CC_ASSERT(pool != nullptr && "failed to add TLSF pool"); 30 | } 31 | 32 | std::byte* cc::tlsf_allocator::alloc(size_t size, size_t align) 33 | { 34 | CC_ASSERT(size > 0 && "Attempted empty TLSF allocation"); 35 | auto const res = static_cast(tlsf_memalign(_tlsf, align, size)); 36 | CC_ASSERT(res != nullptr && "TLSF full"); 37 | return res; 38 | } 39 | 40 | void cc::tlsf_allocator::free(void* ptr) { tlsf_free(_tlsf, ptr); } 41 | 42 | std::byte* cc::tlsf_allocator::realloc(void* ptr, size_t new_size, size_t align) 43 | { 44 | (void)align; 45 | return static_cast(tlsf_realloc(_tlsf, ptr, new_size)); 46 | } 47 | 48 | bool cc::tlsf_allocator::get_allocation_size(void const* ptr, size_t& out_size) 49 | { 50 | if (!ptr) 51 | return false; 52 | 53 | out_size = tlsf_block_size(const_cast(ptr)); 54 | return true; 55 | } 56 | 57 | bool cc::tlsf_allocator::validate_heap() 58 | { 59 | CC_ASSERT(_tlsf && "unitialized"); 60 | 61 | CC_RUNTIME_ASSERT(tlsf_check(_tlsf) == 0 && "TLSF heap state corrupt"); 62 | 63 | return true; 64 | } 65 | -------------------------------------------------------------------------------- /src/clean-core/allocators/tlsf_allocator.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | /// Two Level Segregated Fit allocator 8 | /// O(1) cost for alloc, free, realloc 9 | /// extremely low memory overhead, 4 byte per allocation 10 | struct tlsf_allocator final : allocator 11 | { 12 | tlsf_allocator() = default; 13 | explicit tlsf_allocator(cc::span buffer) { initialize(buffer); } 14 | ~tlsf_allocator() { destroy(); } 15 | 16 | void initialize(cc::span buffer); 17 | void destroy(); 18 | 19 | // provide an additional memory pool to the TLSF (can be done multiple times) 20 | void add_pool(cc::span buffer); 21 | 22 | std::byte* alloc(size_t size, size_t align = alignof(std::max_align_t)) override; 23 | 24 | void free(void* ptr) override; 25 | 26 | std::byte* realloc(void* ptr, size_t new_size, size_t align = alignof(std::max_align_t)) override; 27 | 28 | bool get_allocation_size(void const* ptr, size_t& out_size) override; 29 | 30 | bool validate_heap() override; 31 | 32 | char const* get_name() const override { return "TLSF Allocator"; } 33 | 34 | tlsf_allocator(tlsf_allocator&& rhs) noexcept : _tlsf(rhs._tlsf) { rhs._tlsf = nullptr; } 35 | tlsf_allocator& operator==(tlsf_allocator&& rhs) noexcept 36 | { 37 | destroy(); 38 | _tlsf = rhs._tlsf; 39 | rhs._tlsf = nullptr; 40 | return *this; 41 | } 42 | 43 | private: 44 | void* _tlsf = nullptr; 45 | }; 46 | } -------------------------------------------------------------------------------- /src/clean-core/allocators/virtual_linear_allocator.cc: -------------------------------------------------------------------------------- 1 | #include "virtual_linear_allocator.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | void cc::virtual_linear_allocator::initialize(size_t max_size_bytes, size_t chunk_size_bytes) 10 | { 11 | _virtual_begin = reserve_virtual_memory(max_size_bytes); 12 | _virtual_end = _virtual_begin + max_size_bytes; 13 | _physical_current = _virtual_begin; 14 | _physical_end = _virtual_begin; 15 | _chunk_size_bytes = chunk_size_bytes; 16 | 17 | CC_ASSERT(max_size_bytes > 0 && chunk_size_bytes > 0 && "invalid sizes"); 18 | // the cast is necessary on apple M1 19 | CC_ASSERT(is_pow2(uint64_t(chunk_size_bytes)) && "Chunk size must be a power of 2"); 20 | CC_ASSERT(_virtual_begin != nullptr && "virtual reserve failed"); 21 | } 22 | 23 | void cc::virtual_linear_allocator::destroy() 24 | { 25 | if (_virtual_begin) 26 | { 27 | free_virtual_memory(_virtual_begin, _virtual_end - _virtual_begin); 28 | _virtual_begin = nullptr; 29 | } 30 | } 31 | 32 | std::byte* cc::virtual_linear_allocator::alloc(size_t size, size_t align) 33 | { 34 | CC_ASSERT(_virtual_begin != nullptr && "virtual_linear_allocator uninitialized"); 35 | 36 | std::byte* const padded_res = cc::align_up(_physical_current + sizeof(size_t), align); 37 | size_t const num_padding_bytes = size_t(padded_res - _physical_current); 38 | size_t const required_size = num_padding_bytes + size; 39 | 40 | _physical_end = grow_physical_memory(_physical_current, _physical_end, _virtual_end, _chunk_size_bytes, required_size); 41 | 42 | _physical_current = padded_res + size; 43 | _last_allocation = padded_res; 44 | 45 | // store alloc size 46 | *((size_t*)(padded_res - sizeof(size_t))) = size; 47 | 48 | return padded_res; 49 | } 50 | 51 | bool cc::virtual_linear_allocator::get_allocation_size(void const* ptr, size_t& out_size) 52 | { 53 | if (!ptr) 54 | return false; 55 | 56 | out_size = *((size_t const*)((std::byte const*)ptr - sizeof(size_t))); 57 | return true; 58 | } 59 | 60 | std::byte* cc::virtual_linear_allocator::realloc(void* ptr, size_t new_size, size_t align) 61 | { 62 | if (!ptr || ptr != _last_allocation) 63 | { 64 | // cannot realloc, fall back (this is not invalid usage unlike for stack_linear_allocator) 65 | return cc::allocator::realloc(ptr, new_size, align); 66 | } 67 | 68 | // true realloc 69 | std::byte* const byte_ptr = static_cast(ptr); 70 | 71 | size_t old_size; 72 | bool const success = get_allocation_size(ptr, old_size); 73 | CC_ASSERT(success && old_size == _physical_current - byte_ptr && "incorrect old size"); 74 | 75 | if (new_size > old_size) 76 | { 77 | // grow physically to meet demand 78 | size_t const num_new_bytes = new_size - old_size; 79 | _physical_end = grow_physical_memory(_physical_current, _physical_end, _virtual_end, _chunk_size_bytes, num_new_bytes); 80 | } 81 | 82 | // store new alloc size 83 | *((size_t*)(byte_ptr - sizeof(size_t))) = new_size; 84 | 85 | _physical_current = byte_ptr + new_size; 86 | return byte_ptr; 87 | } 88 | 89 | size_t cc::virtual_linear_allocator::decommit_idle_memory() 90 | { 91 | // align up to the start of the first empty page 92 | std::byte* const ptr = cc::align_up(_physical_current, _chunk_size_bytes); 93 | // then free all memory between that and _physical_end 94 | ptrdiff_t const size_to_free = _physical_end - ptr; 95 | 96 | if (size_to_free > 0) 97 | { 98 | decommit_physical_memory(ptr, size_t(size_to_free)); 99 | _physical_end = ptr; 100 | } 101 | 102 | return size_to_free; 103 | } 104 | -------------------------------------------------------------------------------- /src/clean-core/allocators/virtual_linear_allocator.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | /// linear allocator operating in virtual memory 8 | /// reserves pages on init, commits pages on demand 9 | /// only frees pages if explicitly called 10 | struct virtual_linear_allocator final : allocator 11 | { 12 | virtual_linear_allocator() = default; 13 | explicit virtual_linear_allocator(size_t max_size_bytes, size_t chunk_size_bytes = 65536) { initialize(max_size_bytes, chunk_size_bytes); } 14 | ~virtual_linear_allocator() { destroy(); } 15 | 16 | // max_size_bytes: amount of contiguous virtual memory being reserved 17 | // chunk_size_bytes: increment of physical memory being committed whenever more is required 18 | // note there is a lower limit on virtual allocation granularity (Win32: 64K = 16 pages) 19 | void initialize(size_t max_size_bytes, size_t chunk_size_bytes = 65536); 20 | 21 | void destroy(); 22 | 23 | std::byte* alloc(size_t size, size_t align = alignof(std::max_align_t)) override; 24 | 25 | void free(void* ptr) override { (void)ptr; } 26 | 27 | bool get_allocation_size(void const* ptr, size_t& out_size) override; 28 | 29 | char const* get_name() const override { return "Virtual Linear Allocator"; } 30 | 31 | std::byte* realloc(void* ptr, size_t new_size, size_t align = alignof(std::max_align_t)) override; 32 | 33 | // free all current allocations 34 | // does not decommit any memory! 35 | size_t reset() 36 | { 37 | size_t const num_bytes_allocated = _physical_current - _virtual_begin; 38 | _physical_current = _virtual_begin; 39 | _last_allocation = nullptr; 40 | return num_bytes_allocated; 41 | } 42 | 43 | // decommit the physical memory of all pages not currently required 44 | // returns amount of bytes decommitted 45 | size_t decommit_idle_memory(); 46 | 47 | // amount of bytes in the virtual address range 48 | size_t get_virtual_size_bytes() const { return _virtual_end - _virtual_begin; } 49 | 50 | // amount of bytes in the physically committed memory 51 | size_t get_physical_size_bytes() const { return _physical_end - _virtual_begin; } 52 | 53 | // amount of bytes in the physically committed and allocated memory 54 | size_t get_allocated_size_bytes() const { return _physical_current - _virtual_begin; } 55 | 56 | private: 57 | std::byte* _virtual_begin = nullptr; 58 | std::byte* _virtual_end = nullptr; 59 | std::byte* _physical_current = nullptr; 60 | std::byte* _physical_end = nullptr; 61 | std::byte* _last_allocation = nullptr; 62 | size_t _chunk_size_bytes = 0; 63 | }; 64 | } -------------------------------------------------------------------------------- /src/clean-core/allocators/virtual_stack_allocator.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // int32_t 4 | 5 | #include 6 | 7 | namespace cc 8 | { 9 | /// stack allocator operating in virtual memory 10 | /// reserves pages on init, commits pages on demand 11 | /// only frees pages if explicitly called 12 | /// 13 | /// RESTRICTION: Must only free or realloc the most recent allocation 14 | struct virtual_stack_allocator final : allocator 15 | { 16 | virtual_stack_allocator() = default; 17 | explicit virtual_stack_allocator(size_t max_size_bytes, size_t chunk_size_bytes = 65536) { initialize(max_size_bytes, chunk_size_bytes); } 18 | ~virtual_stack_allocator() override { destroy(); } 19 | 20 | // max_size_bytes: amount of contiguous virtual memory being reserved 21 | // chunk_size_bytes: increment of physical memory being committed whenever more is required 22 | // note there is a lower limit on virtual allocation granularity (Win32: 64K = 16 pages) 23 | void initialize(size_t max_size_bytes, size_t chunk_size_bytes = 65536); 24 | 25 | void destroy(); 26 | 27 | std::byte* alloc(size_t size, size_t align = alignof(std::max_align_t)) override; 28 | 29 | /// NOTE: ptr must be the most recent allocation received 30 | void free(void* ptr) override; 31 | 32 | /// NOTE: ptr must be the most recent allocation received 33 | std::byte* realloc(void* ptr, size_t new_size, size_t align = alignof(std::max_align_t)) override; 34 | 35 | char const* get_name() const override { return "Virtual Stack Allocator"; } 36 | 37 | // free all current allocations 38 | // does not decommit any memory! 39 | size_t reset(); 40 | 41 | // decommit the physical memory of all pages not currently required 42 | // returns amount of bytes decommitted 43 | size_t decommit_idle_memory(); 44 | 45 | // amount of bytes in the virtual address range 46 | size_t get_virtual_size_bytes() const { return _virtual_end - _virtual_begin; } 47 | 48 | // amount of bytes in the physically committed memory 49 | size_t get_physical_size_bytes() const { return _physical_end - _virtual_begin; } 50 | 51 | // amount of bytes in the physically committed and allocated memory 52 | size_t get_allocated_size_bytes() const { return _physical_current - _virtual_begin; } 53 | 54 | // returns whether the given ptr is the latest allocation, meaning it can be freed or reallocated 55 | bool is_latest_allocation(void* ptr) const; 56 | 57 | private: 58 | std::byte* _virtual_begin = nullptr; 59 | std::byte* _virtual_end = nullptr; 60 | std::byte* _physical_current = nullptr; 61 | std::byte* _physical_end = nullptr; 62 | size_t _chunk_size_bytes = 0; 63 | int32_t _last_alloc_id = 0; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/clean-core/always_false.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cc 4 | { 5 | /// Helper class for indicating errors in static_asserts 6 | /// Usage: 7 | /// static_assert(cc::always_false, "some error"); 8 | template 9 | constexpr bool always_false = false; 10 | 11 | /// same as always_false, but for non-type template parameters, e.g. always_false_v 12 | template 13 | constexpr bool always_false_v = false; 14 | } 15 | -------------------------------------------------------------------------------- /src/clean-core/any_of.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace cc 8 | { 9 | /// NOTE: this class should not be stored! 10 | template 11 | struct any_of_t 12 | { 13 | any_of_t(F f) : test(cc::move(f)) {} 14 | 15 | F test; 16 | }; 17 | 18 | template 19 | bool operator==(T const& lhs, any_of_t const& rhs) 20 | { 21 | return rhs.test(lhs); 22 | } 23 | template 24 | bool operator!=(T const& lhs, any_of_t const& rhs) 25 | { 26 | return !rhs.test(lhs); 27 | } 28 | 29 | template > = true> 30 | auto any_of(Range const& r) 31 | { 32 | return any_of_t([&r](auto const& lhs) { 33 | for (auto const& rhs : r) 34 | if (lhs == rhs) 35 | return true; 36 | return false; 37 | }); 38 | } 39 | 40 | template 41 | auto any_of(A const& a, B const& b, Rest const&... rest) 42 | { 43 | return any_of_t([&](auto const& lhs) { 44 | if (lhs == a) 45 | return true; 46 | if (lhs == b) 47 | return true; 48 | return (... || (lhs == rest)); 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/clean-core/apply.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include // index seq and tuple protocol 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace cc 11 | { 12 | namespace detail 13 | { 14 | template 15 | constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence) 16 | { 17 | return cc::invoke(cc::forward(f), cc::get(cc::forward(t))...); 18 | } 19 | } 20 | 21 | template 22 | constexpr decltype(auto) apply(F&& f, Tuple&& t) 23 | { 24 | return detail::apply_impl(cc::forward(f), cc::forward(t), std::make_index_sequence>::value>{}); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/clean-core/assertf.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // usage: CC_ASSERTF(a == b, "{} is not {}", a, b); 8 | #define CC_ASSERTF(cond, ...) CC_ASSERT_MSG(cond, ::cc::format(__VA_ARGS__).c_str()) 9 | 10 | #define CC_RUNTIME_ASSERTF(cond, ...) CC_RUNTIME_ASSERT_MSG(cond, ::cc::format(__VA_ARGS__).c_str()) 11 | -------------------------------------------------------------------------------- /src/clean-core/atomic_linked_pool.cc: -------------------------------------------------------------------------------- 1 | #include "atomic_linked_pool.hh" 2 | 3 | // Radix Sort implementation from https://github.com/983/RadixSort 4 | // License: "Unlicense" (public domain) 5 | 6 | namespace 7 | { 8 | CC_FORCE_INLINE void radix_sort_pass(uint32_t const* __restrict src, uint32_t* __restrict dst, size_t n, size_t shift) 9 | { 10 | size_t next_index = 0, index[256] = {0}; 11 | for (size_t i = 0; i < n; i++) 12 | index[(src[i] >> shift) & 0xff]++; 13 | for (size_t i = 0; i < 256; i++) 14 | { 15 | size_t const count = index[i]; 16 | index[i] = next_index; 17 | next_index += count; 18 | } 19 | for (size_t i = 0; i < n; i++) 20 | dst[index[(src[i] >> shift) & 0xff]++] = src[i]; 21 | } 22 | 23 | // instantiate atomic linked pool once 24 | static_assert(sizeof(cc::atomic_linked_pool) > 0, ""); 25 | } 26 | 27 | 28 | void cc::detail::radix_sort(uint32_t* __restrict a, uint32_t* __restrict temp, size_t n) 29 | { 30 | radix_sort_pass(a, temp, n, 0 * 8); 31 | radix_sort_pass(temp, a, n, 1 * 8); 32 | radix_sort_pass(a, temp, n, 2 * 8); 33 | radix_sort_pass(temp, a, n, 3 * 8); 34 | } 35 | -------------------------------------------------------------------------------- /src/clean-core/base64.cc: -------------------------------------------------------------------------------- 1 | #include "base64.hh" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | static cc::string_view constexpr base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 10 | "abcdefghijklmnopqrstuvwxyz" 11 | "0123456789+/"; 12 | 13 | static int find_base64_char(unsigned char c) 14 | { 15 | for (auto i = 0; i < int(base64_chars.size()); ++i) 16 | if (base64_chars[i] == c) 17 | return i; 18 | CC_ASSERT(false && "not found"); 19 | return -1; 20 | } 21 | 22 | static bool is_base64(unsigned char c) { return (isalnum(c) || (c == '+') || (c == '/')); } 23 | 24 | cc::string cc::base64_encode(cc::span data) 25 | { 26 | cc::string ret; 27 | int i = 0; 28 | int j = 0; 29 | unsigned char char_array_3[3]; 30 | unsigned char char_array_4[4]; 31 | 32 | for (auto b : data) 33 | { 34 | char_array_3[i++] = (unsigned char)b; 35 | if (i == 3) 36 | { 37 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 38 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 39 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 40 | char_array_4[3] = char_array_3[2] & 0x3f; 41 | 42 | for (i = 0; (i < 4); i++) 43 | ret += base64_chars[char_array_4[i]]; 44 | i = 0; 45 | } 46 | } 47 | 48 | if (i) 49 | { 50 | for (j = i; j < 3; j++) 51 | char_array_3[j] = '\0'; 52 | 53 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 54 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 55 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 56 | char_array_4[3] = char_array_3[2] & 0x3f; 57 | 58 | for (j = 0; (j < i + 1); j++) 59 | ret += base64_chars[char_array_4[j]]; 60 | 61 | while ((i++ < 3)) 62 | ret += '='; 63 | } 64 | 65 | return ret; 66 | } 67 | cc::vector cc::base64_decode(cc::string_view encoded_string) 68 | { 69 | int in_len = static_cast(encoded_string.size()); 70 | int i = 0; 71 | int j = 0; 72 | int in_ = 0; 73 | unsigned char char_array_4[4], char_array_3[3]; 74 | cc::vector ret; 75 | 76 | while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) 77 | { 78 | char_array_4[i++] = encoded_string[in_]; 79 | in_++; 80 | if (i == 4) 81 | { 82 | for (i = 0; i < 4; i++) 83 | char_array_4[i] = find_base64_char(char_array_4[i]) & 0xff; 84 | 85 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 86 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 87 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 88 | 89 | for (i = 0; (i < 3); i++) 90 | ret.push_back(std::byte(char_array_3[i])); 91 | i = 0; 92 | } 93 | } 94 | 95 | if (i) 96 | { 97 | for (j = 0; j < i; j++) 98 | char_array_4[j] = find_base64_char(char_array_4[j]) & 0xff; 99 | 100 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 101 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 102 | 103 | for (j = 0; (j < i - 1); j++) 104 | ret.push_back(std::byte(char_array_3[j])); 105 | } 106 | 107 | return ret; 108 | } 109 | -------------------------------------------------------------------------------- /src/clean-core/base64.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | base64.cc and base64.hh 5 | 6 | Copyright (C) 2004-2008 René Nyffenegger 7 | 8 | This source code is provided 'as-is', without any express or implied 9 | warranty. In no event will the author be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this source code must not be misrepresented; you must not 17 | claim that you wrote the original source code. If you use this source code 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 21 | 2. Altered source versions must be plainly marked as such, and must not be 22 | misrepresented as being the original source code. 23 | 24 | 3. This notice may not be removed or altered from any source distribution. 25 | 26 | René Nyffenegger rene.nyffenegger@adp-gmbh.ch 27 | 28 | with modifications to fit better with clean-core 29 | */ 30 | 31 | #include 32 | 33 | #include 34 | #include 35 | 36 | namespace cc 37 | { 38 | cc::string base64_encode(cc::span data); 39 | cc::vector base64_decode(cc::string_view encoded_string); 40 | } 41 | -------------------------------------------------------------------------------- /src/clean-core/bit_cast.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace cc 9 | { 10 | template 11 | [[nodiscard]] CC_FORCE_INLINE To bit_cast(From const& src) 12 | { 13 | static_assert(std::is_trivially_copyable_v, "only supported for trivially copyable types"); 14 | static_assert(std::is_trivially_copyable_v, "only supported for trivially copyable types"); 15 | static_assert(sizeof(To) == sizeof(From), "only supported if types have the same size"); 16 | To dst; 17 | std::memcpy(&dst, &src, sizeof(To)); 18 | return dst; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/clean-core/breakpoint.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef CC_COMPILER_MSVC 4 | #include 5 | #endif 6 | 7 | // TODO: Maybe use IsDebuggerPresent (http://msdn.microsoft.com/en-us/library/ms680345%28VS.85%29.aspx) 8 | 9 | void cc::breakpoint() 10 | { 11 | #if defined(CC_COMPILER_MSVC) 12 | __debugbreak(); 13 | #elif defined(CC_COMPILER_CLANG) || defined(CC_COMPILER_GCC) 14 | __builtin_trap(); 15 | #else 16 | #error "Unknown compiler" 17 | #endif 18 | } 19 | -------------------------------------------------------------------------------- /src/clean-core/breakpoint.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | [[deprecated("use CC_DEBUG_BREAK()")]] CC_DONT_INLINE void breakpoint(); 8 | } 9 | -------------------------------------------------------------------------------- /src/clean-core/char_predicates.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // locale independent character predicates on 'char's 4 | // see https://en.cppreference.com/w/cpp/string/byte 5 | 6 | namespace cc 7 | { 8 | [[nodiscard]] constexpr bool is_space(char c) { return c == ' ' || c == '\f' || c == '\t' || c == '\n' || c == '\r' || c == '\v'; } 9 | [[nodiscard]] constexpr bool is_blank(char c) { return c == ' ' || c == '\t'; } 10 | [[nodiscard]] constexpr bool is_digit(char c) { return '0' <= c && c <= '9'; } 11 | [[nodiscard]] constexpr bool is_hex_digit(char c) { return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'); } 12 | [[nodiscard]] constexpr bool is_alphanumeric(char c) { return ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); } 13 | [[nodiscard]] constexpr bool is_lower(char c) { return 'a' <= c && c <= 'z'; } 14 | [[nodiscard]] constexpr bool is_upper(char c) { return 'A' <= c && c <= 'Z'; } 15 | [[nodiscard]] constexpr bool is_punctuation(char c) 16 | { 17 | return ('\x21' <= c && c <= '\x2F') || ('\x3A' <= c && c <= '\x40') || ('\x5B' <= c && c <= '\x60') || ('\x7B' <= c && c <= '\x7E'); 18 | } 19 | [[nodiscard]] constexpr bool is_graphical(char c) { return is_alphanumeric(c) || is_punctuation(c); } 20 | [[nodiscard]] constexpr bool is_printable(char c) { return c == ' ' || is_alphanumeric(c) || is_punctuation(c); } 21 | [[nodiscard]] constexpr bool is_control(char c) { return ('\x00' <= c && c <= '\x1F') || c == '\x7F'; } 22 | 23 | [[nodiscard]] constexpr char to_lower(char c) { return is_upper(c) ? char('a' + (c - 'A')) : c; } 24 | [[nodiscard]] constexpr char to_upper(char c) { return is_lower(c) ? char('A' + (c - 'a')) : c; } 25 | 26 | [[nodiscard]] constexpr auto is_equal_fun(char c) 27 | { 28 | return [c](char cc) { return c == cc; }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/clean-core/cursor.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace cc 8 | { 9 | /** 10 | * cursor-based container facade: 11 | * struct my_cursor : cc::cursor 12 | * { 13 | * T get() const { return ...; } 14 | * void advance() { ... } 15 | * bool is_valid() const { return ...; } 16 | * }; 17 | * 18 | * guarantees: 19 | * - advance() is never called if is_valid is false 20 | * - get() is never called if is_valid is false 21 | */ 22 | template 23 | struct cursor 24 | { 25 | [[nodiscard]] decltype(auto) operator*() const { return static_cast(this)->get(); } 26 | void operator++() { static_cast(this)->advance(); } 27 | void operator++(int) { static_cast(this)->advance(); } 28 | explicit operator bool() const { return static_cast(this)->is_valid(); } 29 | 30 | [[nodiscard]] bool operator==(sentinel) const { return !static_cast(this)->is_valid(); } 31 | [[nodiscard]] bool operator!=(sentinel) const { return static_cast(this)->is_valid(); } 32 | 33 | // TODO: can we prevent the copy? 34 | [[nodiscard]] this_t begin() const { return *static_cast(this); } 35 | [[nodiscard]] sentinel end() const { return {}; } 36 | }; 37 | 38 | // adaptor for legacy begin/end iterators 39 | template 40 | struct iterator_cursor : cursor> 41 | { 42 | iterator_cursor(iterator_t begin, sentinel_t end) : _curr(cc::move(begin)), _end(cc::move(end)) {} 43 | 44 | [[nodiscard]] constexpr decltype(auto) get() const 45 | { 46 | CC_CONTRACT(bool(_curr)); 47 | CC_CONTRACT(is_valid()); 48 | return *_curr; 49 | } 50 | constexpr void advance() 51 | { 52 | CC_CONTRACT(is_valid()); 53 | _curr++; 54 | } 55 | [[nodiscard]] constexpr bool is_valid() const { return _curr != _end; } 56 | 57 | private: 58 | iterator_t _curr; 59 | sentinel_t _end; 60 | }; 61 | 62 | // NOTE: this should always be fully qualified (cc::to_cursor) 63 | // otherwise it might get ADL-jacked 64 | template 65 | [[nodiscard]] constexpr auto to_cursor(Range& range) 66 | { 67 | // TODO: some partial specialization point for custom to_cursor 68 | return cc::iterator_cursor(range.begin(), range.end()); 69 | } 70 | template 71 | void to_cursor(Range&& range) = delete; // this is a lifetime hazard 72 | } 73 | -------------------------------------------------------------------------------- /src/clean-core/defer.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * execute code at scope-exit: 8 | * NOTE: captures by reference! 9 | * 10 | * begin(); 11 | * CC_DEFER { end(); }; 12 | * 13 | */ 14 | #define CC_DEFER auto const CC_MACRO_JOIN(_cc_deferred_, __COUNTER__) = ::cc::detail::deferred_tag{} + [&] 15 | 16 | /** 17 | * execute code at scope-exit in calling function: 18 | * CAUTION: this one captures by value! 19 | * 20 | * auto scoped_foo() { 21 | * begin(); 22 | * CC_RETURN_DEFER { this->end(); }; 23 | * } 24 | * 25 | */ 26 | #define CC_RETURN_DEFER return ::cc::detail::deferred_tag{} + [=] 27 | 28 | namespace cc::detail 29 | { 30 | template 31 | struct deferred 32 | { 33 | F f; 34 | 35 | ~deferred() { f(); } 36 | }; 37 | 38 | struct deferred_tag 39 | { 40 | }; 41 | 42 | template 43 | deferred operator+(deferred_tag, F&& f) 44 | { 45 | return {cc::forward(f)}; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/clean-core/demangle.cc: -------------------------------------------------------------------------------- 1 | #include "demangle.hh" 2 | 3 | #include 4 | 5 | #ifdef CC_OS_LINUX 6 | #include 7 | #include 8 | 9 | cc::string cc::demangle(string_view mangled_name) 10 | { 11 | int status = -4; 12 | // TODO: use non-allocating version if performance is ever a concern 13 | auto cname = abi::__cxa_demangle(cc::temp_cstr(mangled_name), nullptr, nullptr, &status); 14 | 15 | if (!cname) 16 | return mangled_name; // some error occurred 17 | 18 | auto name = cc::string(cname); 19 | ::free(cname); 20 | 21 | return name; 22 | } 23 | 24 | #else 25 | // TODO: implement me 26 | cc::string cc::demangle(string_view mangled_name) { return mangled_name; } 27 | #endif 28 | -------------------------------------------------------------------------------- /src/clean-core/demangle.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cc 7 | { 8 | cc::string demangle(cc::string_view mangled_name); 9 | } 10 | -------------------------------------------------------------------------------- /src/clean-core/detail/compact_size_t.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace cc::detail 9 | { 10 | template 11 | auto helper_size_t() 12 | { 13 | if constexpr (N < (1 << 8) && Alignment <= 1) 14 | return uint8_t{}; 15 | else if constexpr (N < (1 << 16) && Alignment <= 2) 16 | return uint16_t{}; 17 | else if constexpr (N < (1uLL << 32) && Alignment <= 4) 18 | return uint32_t{}; 19 | else 20 | return uint64_t{}; 21 | } 22 | 23 | template 24 | using compact_size_t_for = decltype(helper_size_t()); 25 | 26 | /// Indirection workaround for a current MSVC compiler bug (19.22) 27 | /// without indirection: https://godbolt.org/z/iQ19yj 28 | /// with indirection: https://godbolt.org/z/6MoWE4 29 | /// Bug report: https://developercommunity.visualstudio.com/content/problem/800899/false-positive-for-c2975-on-alias-template-fixed-w.html 30 | template 31 | using compact_size_t_typed = compact_size_t_for; 32 | 33 | template 34 | struct compact_size_t_by_bits_impl 35 | { 36 | static_assert(always_false_v, "bit size not supported"); 37 | }; 38 | template <> 39 | struct compact_size_t_by_bits_impl<8> 40 | { 41 | using type = std::uint8_t; 42 | }; 43 | template <> 44 | struct compact_size_t_by_bits_impl<16> 45 | { 46 | using type = std::uint16_t; 47 | }; 48 | template <> 49 | struct compact_size_t_by_bits_impl<32> 50 | { 51 | using type = std::uint32_t; 52 | }; 53 | template <> 54 | struct compact_size_t_by_bits_impl<64> 55 | { 56 | using type = std::uint64_t; 57 | }; 58 | 59 | template 60 | using compact_size_t_by_bits = typename compact_size_t_by_bits_impl<(bits <= 8 ? 8 : bits <= 16 ? 16 : bits <= 32 ? 32 : 64)>::type; 61 | } 62 | -------------------------------------------------------------------------------- /src/clean-core/detail/container_impl_util.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace cc::detail 13 | { 14 | template 15 | CC_FORCE_INLINE void container_move_construct_range(T* __restrict src, SizeT num, T* __restrict dest) 16 | { 17 | static_assert(sizeof(T) > 0, "cannot move incomplete types"); 18 | CC_ASSERT(num == 0 || (src != nullptr && dest != nullptr)); 19 | if constexpr (std::is_trivially_move_constructible_v && std::is_trivially_copyable_v) 20 | { 21 | if (num > 0) 22 | std::memcpy(dest, src, sizeof(T) * num); 23 | } 24 | else 25 | { 26 | for (SizeT i = 0; i < num; ++i) 27 | new (placement_new, &dest[i]) T(cc::move(src[i])); 28 | } 29 | } 30 | 31 | template 32 | CC_FORCE_INLINE void container_copy_construct_range(T const* __restrict src, SizeT num, T* __restrict dest) 33 | { 34 | static_assert(sizeof(T) > 0, "cannot copy incomplete types"); 35 | CC_ASSERT(num == 0 || (src != nullptr && dest != nullptr)); 36 | if constexpr (std::is_trivially_copyable_v) 37 | { 38 | if (num > 0) 39 | std::memcpy(dest, src, sizeof(T) * num); 40 | } 41 | else 42 | { 43 | for (SizeT i = 0; i < num; ++i) 44 | new (placement_new, &dest[i]) T(src[i]); 45 | } 46 | } 47 | 48 | template 49 | CC_FORCE_INLINE void container_default_construct_or_zeroed(SizeT num, T* __restrict dest) 50 | { 51 | static_assert(sizeof(T) > 0, "cannot copy incomplete types"); 52 | CC_ASSERT(num == 0 || dest != nullptr); 53 | if constexpr (!std::is_trivially_constructible_v) 54 | { 55 | for (SizeT i = 0; i < num; ++i) 56 | new (placement_new, &dest[i]) T(); 57 | } 58 | else 59 | { 60 | std::memset(dest, 0, num * sizeof(T)); 61 | } 62 | } 63 | 64 | template 65 | CC_FORCE_INLINE void container_copy_construct_fill(T const& value, SizeT num, T* __restrict dest) 66 | { 67 | static_assert(sizeof(T) > 0, "cannot copy incomplete types"); 68 | CC_ASSERT(num == 0 || dest != nullptr); 69 | for (SizeT i = 0; i < num; ++i) 70 | new (placement_new, &dest[i]) T(value); 71 | } 72 | 73 | template 74 | CC_FORCE_INLINE void container_relocate_construct_range(T* dest, T* src, SizeT num_elements) 75 | { 76 | CC_ASSERT(num_elements == 0 || (src != nullptr && dest != nullptr)); 77 | 78 | if constexpr (std::is_trivially_copyable_v) 79 | { 80 | if (num_elements > 0) 81 | { 82 | std::memmove(dest, src, num_elements * sizeof(T)); 83 | } 84 | } 85 | else 86 | { 87 | for (int64_t i = num_elements - 1; i >= 0; --i) 88 | { 89 | // move-construct new element in place 90 | new (placement_new, &dest[i]) T(cc::move(src[i])); 91 | // call dtor on old element 92 | src[i].~T(); 93 | } 94 | } 95 | } 96 | 97 | template 98 | CC_FORCE_INLINE void container_destroy_reverse([[maybe_unused]] T* data, [[maybe_unused]] SizeT size, [[maybe_unused]] SizeT to_index = 0) 99 | { 100 | static_assert(sizeof(T) > 0, "cannot destroy incomplete types"); 101 | CC_ASSERT(size == 0 || data != nullptr); 102 | if constexpr (!std::is_trivially_destructible_v) 103 | { 104 | for (SizeT i = size; i > to_index; --i) 105 | data[i - 1].~T(); 106 | } 107 | } 108 | } // namespace cc::detail 109 | -------------------------------------------------------------------------------- /src/clean-core/detail/is_reference_wrapper.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | namespace detail 8 | { 9 | template 10 | struct test_reference_wrapper : std::false_type 11 | { 12 | }; 13 | template 14 | struct test_reference_wrapper> : std::true_type 15 | { 16 | }; 17 | 18 | template 19 | struct unwrap_reference_wrapper 20 | { 21 | using type = T; 22 | }; 23 | 24 | template 25 | struct unwrap_reference_wrapper> 26 | { 27 | using type = T&; 28 | }; 29 | 30 | } // namespace detail 31 | 32 | // currently only used in invoke.hh 33 | template 34 | constexpr bool is_reference_wrapper = detail::test_reference_wrapper::value; 35 | 36 | template 37 | using reference_decay_t = typename detail::unwrap_reference_wrapper::type>::type; 38 | } 39 | -------------------------------------------------------------------------------- /src/clean-core/detail/lib/tlsf.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef INCLUDED_tlsf 3 | #define INCLUDED_tlsf 4 | 5 | /* 6 | ** Two Level Segregated Fit memory allocator, version 3.1. 7 | ** Written by Matthew Conte 8 | ** http://tlsf.baisoku.org 9 | ** 10 | ** Based on the original documentation by Miguel Masmano: 11 | ** http://www.gii.upv.es/tlsf/main/docs 12 | ** 13 | ** This implementation was written to the specification 14 | ** of the document, therefore no GPL restrictions apply. 15 | ** 16 | ** Copyright (c) 2006-2016, Matthew Conte 17 | ** All rights reserved. 18 | ** 19 | ** Redistribution and use in source and binary forms, with or without 20 | ** modification, are permitted provided that the following conditions are met: 21 | ** * Redistributions of source code must retain the above copyright 22 | ** notice, this list of conditions and the following disclaimer. 23 | ** * Redistributions in binary form must reproduce the above copyright 24 | ** notice, this list of conditions and the following disclaimer in the 25 | ** documentation and/or other materials provided with the distribution. 26 | ** * Neither the name of the copyright holder nor the 27 | ** names of its contributors may be used to endorse or promote products 28 | ** derived from this software without specific prior written permission. 29 | ** 30 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 31 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 32 | ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 33 | ** DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY 34 | ** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 35 | ** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 36 | ** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 37 | ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 38 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 39 | ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 40 | */ 41 | 42 | #include 43 | 44 | #if defined(__cplusplus) 45 | extern "C" 46 | { 47 | #endif 48 | 49 | /* tlsf_t: a TLSF structure. Can contain 1 to N pools. */ 50 | /* pool_t: a block of memory that TLSF can manage. */ 51 | typedef void* tlsf_t; 52 | typedef void* pool_t; 53 | 54 | /* Create/destroy a memory pool. */ 55 | tlsf_t tlsf_create(void* mem); 56 | tlsf_t tlsf_create_with_pool(void* mem, size_t bytes); 57 | void tlsf_destroy(tlsf_t tlsf); 58 | pool_t tlsf_get_pool(tlsf_t tlsf); 59 | 60 | /* Add/remove memory pools. */ 61 | pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes); 62 | void tlsf_remove_pool(tlsf_t tlsf, pool_t pool); 63 | 64 | /* malloc/memalign/realloc/free replacements. */ 65 | void* tlsf_malloc(tlsf_t tlsf, size_t bytes); 66 | void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t bytes); 67 | void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size); 68 | void tlsf_free(tlsf_t tlsf, void* ptr); 69 | 70 | /* Returns internal block size, not original request size */ 71 | size_t tlsf_block_size(void* ptr); 72 | 73 | /* Overheads/limits of internal structures. */ 74 | size_t tlsf_size(void); 75 | size_t tlsf_align_size(void); 76 | size_t tlsf_block_size_min(void); 77 | size_t tlsf_block_size_max(void); 78 | size_t tlsf_pool_overhead(void); 79 | size_t tlsf_alloc_overhead(void); 80 | 81 | /* Debugging. */ 82 | typedef void (*tlsf_walker)(void* ptr, size_t size, int used, void* user); 83 | void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user); 84 | /* Returns nonzero if any internal consistency check fails. */ 85 | int tlsf_check(tlsf_t tlsf); 86 | int tlsf_check_pool(pool_t pool); 87 | 88 | #if defined(__cplusplus) 89 | }; 90 | #endif 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /src/clean-core/detail/remove_reference.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cc 4 | { 5 | template 6 | struct remove_reference_t 7 | { 8 | using type = T; 9 | }; 10 | template 11 | struct remove_reference_t 12 | { 13 | using type = T; 14 | }; 15 | template 16 | struct remove_reference_t 17 | { 18 | using type = T; 19 | }; 20 | template 21 | using remove_reference = typename remove_reference_t::type; 22 | } 23 | -------------------------------------------------------------------------------- /src/clean-core/detail/xxHash/xxhash.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * xxHash - Extremely Fast Hash algorithm 3 | * Copyright (C) 2012-2020 Yann Collet 4 | * 5 | * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php) 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are 9 | * met: 10 | * 11 | * * Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above 14 | * copyright notice, this list of conditions and the following disclaimer 15 | * in the documentation and/or other materials provided with the 16 | * distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | * 30 | * You can contact the author at: 31 | * - xxHash homepage: https://www.xxhash.com 32 | * - xxHash source repository: https://github.com/Cyan4973/xxHash 33 | */ 34 | 35 | 36 | /* 37 | * xxhash.c instantiates functions defined in xxhash.h 38 | */ 39 | 40 | #define XXH_STATIC_LINKING_ONLY /* access advanced declarations */ 41 | #define XXH_IMPLEMENTATION /* access definitions */ 42 | 43 | #include "xxhash.hh" 44 | -------------------------------------------------------------------------------- /src/clean-core/dont_deduce.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cc 4 | { 5 | namespace detail 6 | { 7 | template 8 | struct dont_deduce 9 | { 10 | using type = T; 11 | }; 12 | } 13 | 14 | /** 15 | * helper typedef for disabling template argument deduction 16 | * see https://artificial-mind.net/blog/2020/09/26/dont-deduce 17 | * 18 | * example: 19 | * 20 | * template 21 | * vec3 operator*(vec3 const& a, dont_deduce b); 22 | * 23 | * // otherwise this won't work: 24 | * vec3 v = ...; 25 | * v = v * 3; 26 | */ 27 | template 28 | using dont_deduce = typename detail::dont_deduce::type; 29 | } 30 | -------------------------------------------------------------------------------- /src/clean-core/enable_if.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cc 4 | { 5 | namespace detail 6 | { 7 | template 8 | struct enable_if_t 9 | { 10 | }; 11 | template <> 12 | struct enable_if_t 13 | { 14 | using type = bool; 15 | }; 16 | } 17 | 18 | /// Example: 19 | /// template > = true> 20 | /// void foo(T t); 21 | template 22 | using enable_if = typename detail::enable_if_t::type; 23 | } 24 | -------------------------------------------------------------------------------- /src/clean-core/equal_to.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace cc 8 | { 9 | template 10 | struct equal_to 11 | { 12 | static_assert(has_operator_equal, "if no bool-valued operator< is found, provide a specialization equal_to<>"); 13 | 14 | [[nodiscard]] constexpr bool operator()(T const& a, T const& b) const noexcept { return a == b; } 15 | }; 16 | 17 | template <> 18 | struct equal_to // transparent comparison 19 | { 20 | template 21 | [[nodiscard]] constexpr bool operator()(A const& a, B const& b) const noexcept 22 | { 23 | return a == b; 24 | } 25 | }; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/clean-core/error_messages.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | // 9 | // this header contains helper for constructing pleasing error messages 10 | // 11 | 12 | namespace cc 13 | { 14 | 15 | struct substr_error 16 | { 17 | cc::string_view target; // can be zero-sized to indicate the space between two chars 18 | cc::string_view message; 19 | 20 | substr_error() = default; 21 | substr_error(cc::string_view target, cc::string_view message) : target(target), message(message) {} 22 | substr_error(char const* target, cc::string_view message) : target(target, size_t(0)), message(message) {} 23 | }; 24 | 25 | /// creates an ASCII-art error message for 'str' 26 | /// does NOT have a trailing \n 27 | /// 28 | /// Example from cc::format: 29 | /// 30 | /// make_error_message_for_substrings(fmt_str, {{curr_c, "expected '}' (or missing earlier '{')"}}) 31 | /// 32 | /// called in the erroneous call 'cc::format("{2} - 0} = {1}", 1, 2, 3)' 33 | /// 34 | /// produces: 35 | /// 36 | /// > {2} - 0} = {1} 37 | /// ^ 38 | /// * expected '}' (or missing earlier '{') 39 | /// 40 | cc::string make_error_message_for_substrings(cc::string_view str, std::initializer_list errors, cc::string_view message = ""); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/clean-core/experimental/box.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace cc 10 | { 11 | /// a non-polymorphic move-only value type (allocated on the heap) 12 | /// (basically a non-nullable unique_ptr) 13 | template 14 | struct box 15 | { 16 | box(T const& v) { _data = cc::alloc(v); } 17 | box(T&& v) { _data = cc::alloc(cc::move(v)); } 18 | 19 | box& operator=(T&& v) 20 | { 21 | if (!_data) // can be in a moved-from state 22 | _data = cc::alloc(cc::move(v)); 23 | else 24 | *_data = cc::move(v); 25 | 26 | return *this; 27 | } 28 | 29 | box(box&& b) noexcept 30 | { 31 | _data = b._data; 32 | b._data = nullptr; 33 | } 34 | box& operator=(box&& b) noexcept 35 | { 36 | if (_data) 37 | cc::free(_data); 38 | _data = b._data; 39 | b._data = nullptr; 40 | 41 | return *this; 42 | } 43 | 44 | box(box const&) = delete; 45 | box& operator=(box const&) = delete; 46 | 47 | ~box() 48 | { 49 | if (_data) 50 | cc::free(_data); 51 | } 52 | 53 | template 54 | friend box make_box(Args&&... args); 55 | 56 | [[nodiscard]] T* get() { return _data; } 57 | [[nodiscard]] T const* get() const { return _data; } 58 | 59 | T* operator->() 60 | { 61 | CC_ASSERT_NOT_NULL(_data); // moved-from state? 62 | return _data; 63 | } 64 | T const* operator->() const 65 | { 66 | CC_ASSERT_NOT_NULL(_data); // moved-from state? 67 | return _data; 68 | } 69 | T& operator*() 70 | { 71 | CC_ASSERT_NOT_NULL(_data); // moved-from state? 72 | return *_data; 73 | } 74 | T const& operator*() const 75 | { 76 | CC_ASSERT_NOT_NULL(_data); // moved-from state? 77 | return *_data; 78 | } 79 | 80 | operator T const&() const { return *_data; } 81 | operator T&() { return *_data; } 82 | 83 | private: 84 | explicit box(T* data) : _data(data) {} 85 | 86 | T* _data; 87 | }; 88 | 89 | template 90 | box make_box(Args&&... args) 91 | { 92 | return box(cc::alloc(cc::forward(args)...)); 93 | } 94 | 95 | // TODO: is making these SFINAE-friendly worth it? 96 | 97 | template 98 | bool operator==(box const& a, box const& b) 99 | { 100 | return *a == *b; 101 | } 102 | template 103 | bool operator!=(box const& a, box const& b) 104 | { 105 | return *a != *b; 106 | } 107 | template 108 | bool operator<(box const& a, box const& b) 109 | { 110 | return *a < *b; 111 | } 112 | template 113 | bool operator>(box const& a, box const& b) 114 | { 115 | return *a > *b; 116 | } 117 | template 118 | bool operator<=(box const& a, box const& b) 119 | { 120 | return *a <= *b; 121 | } 122 | template 123 | bool operator>=(box const& a, box const& b) 124 | { 125 | return *a >= *b; 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/clean-core/experimental/clamped_span.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // int64_t 4 | 5 | #include 6 | 7 | namespace cc 8 | { 9 | /// a non-owning view of a contiguous array of Ts 10 | /// works exactly like span, except there are no bounds checks and indices are instead clamped to the valid range 11 | /// For example, my_clamped_span[-1] == my_clamped_span[0] and my_clamped_span[my_clamped_span.size()] == my_clamped_span[my_clamped_span.size() -1] 12 | template 13 | struct clamped_span : public span 14 | { 15 | public: 16 | // ctors 17 | using span::span; 18 | 19 | // container 20 | public: 21 | constexpr T& operator[](int64_t i) const 22 | { 23 | CC_ASSERT(this->size() > 0); 24 | return this->data()[clamp(i, int64_t(0), int64_t(this->size() - 1))]; 25 | } 26 | }; 27 | 28 | // deduction guide for containers 29 | template > = true> 30 | clamped_span(Container& c) -> clamped_span>; 31 | template > = true> 32 | clamped_span(Container&& c) -> clamped_span>; 33 | } // namespace cc 34 | -------------------------------------------------------------------------------- /src/clean-core/experimental/filewatch.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace cc 10 | { 11 | /** 12 | * Watch files on disk for changes 13 | * 14 | * Usage: 15 | * 16 | * auto watch = cc::filewatch::create(path); 17 | * 18 | * // time passes... 19 | * 20 | * if (watch.has_changed()) 21 | * { 22 | * // reload resource.. 23 | * watch.set_unchanged(); 24 | * } 25 | * 26 | * NOTE: 27 | * this class is move-only 28 | */ 29 | struct filewatch 30 | { 31 | public: 32 | /// returns true iff this watches a file 33 | bool is_valid() const { return _state != nullptr; } 34 | 35 | /// returns true iff the watch is valid and the watched file has changed 36 | bool has_changed() const; 37 | 38 | /// clears the "has_changed" status 39 | void set_unchanged(); 40 | 41 | /// creates a filewatch for a specific path 42 | static filewatch create(cc::string_view filename); 43 | 44 | filewatch(); 45 | filewatch(filewatch&&); 46 | filewatch& operator=(filewatch&&) noexcept; 47 | filewatch(filewatch const&) = delete; 48 | filewatch& operator=(filewatch const&) = delete; 49 | ~filewatch(); 50 | 51 | private: 52 | struct state; 53 | cc::unique_ptr _state; 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /src/clean-core/experimental/fwd_box.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace cc 10 | { 11 | /// a non-polymorphic move-only forward-declaration friendly value type (allocated on the heap) 12 | /// (basically a non-nullable unique_ptr with support for incomplete types) 13 | template 14 | struct fwd_box 15 | { 16 | fwd_box(fwd_box&& b) noexcept 17 | { 18 | _data = b._data; 19 | _deleter = b._deleter; 20 | b._data = nullptr; 21 | b._deleter = nullptr; 22 | } 23 | fwd_box& operator=(fwd_box&& b) noexcept 24 | { 25 | if (_deleter) 26 | _deleter(_data); 27 | _data = b._data; 28 | _deleter = b._deleter; 29 | b._data = nullptr; 30 | b._deleter = nullptr; 31 | 32 | return *this; 33 | } 34 | 35 | fwd_box(fwd_box const&) = delete; 36 | fwd_box& operator=(fwd_box const&) = delete; 37 | 38 | ~fwd_box() 39 | { 40 | if (_deleter) 41 | _deleter(_data); 42 | } 43 | 44 | template 45 | friend fwd_box make_fwd_box(Args&&... args); 46 | 47 | [[nodiscard]] T* get() { return _data; } 48 | [[nodiscard]] T const* get() const { return _data; } 49 | 50 | T* operator->() 51 | { 52 | CC_ASSERT_NOT_NULL(_data); // moved-from state? 53 | return _data; 54 | } 55 | T const* operator->() const 56 | { 57 | CC_ASSERT_NOT_NULL(_data); // moved-from state? 58 | return _data; 59 | } 60 | T& operator*() 61 | { 62 | CC_ASSERT_NOT_NULL(_data); // moved-from state? 63 | return *_data; 64 | } 65 | T const& operator*() const 66 | { 67 | CC_ASSERT_NOT_NULL(_data); // moved-from state? 68 | return *_data; 69 | } 70 | 71 | operator T const&() const { return *_data; } 72 | operator T&() { return *_data; } 73 | 74 | private: 75 | explicit fwd_box(T* data) : _data(data) 76 | { 77 | _deleter = [](T* data) { cc::free(data); }; 78 | } 79 | 80 | T* _data; 81 | function_ptr _deleter; 82 | }; 83 | 84 | template 85 | fwd_box make_fwd_box(Args&&... args) 86 | { 87 | return fwd_box(cc::alloc(cc::forward(args)...)); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/clean-core/experimental/pimpl.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | template 8 | using pimpl = fwd_box; 9 | 10 | template 11 | pimpl make_pimpl(Args&&... args) 12 | { 13 | return cc::make_fwd_box(cc::forward(args)...); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/clean-core/experimental/poly_box.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace cc 10 | { 11 | /// a polymorphic move-only value type (allocated on the heap) 12 | /// (basically a non-nullable poly_unique_ptr) 13 | template 14 | struct poly_box 15 | { 16 | template > = true> 17 | poly_box(U&& v) 18 | { 19 | _data = new U(cc::forward(v)); 20 | } 21 | 22 | template > = true> 23 | poly_box& operator=(U&& v) 24 | { 25 | delete _data; 26 | _data = new U(cc::forward(v)); 27 | return *this; 28 | } 29 | 30 | poly_box(poly_box&& b) noexcept 31 | { 32 | _data = b._data; 33 | b._data = nullptr; 34 | } 35 | poly_box& operator=(poly_box&& b) noexcept 36 | { 37 | if (_data) 38 | delete _data; 39 | _data = b._data; 40 | b._data = nullptr; 41 | 42 | return *this; 43 | } 44 | 45 | template > = true> 46 | poly_box(poly_box&& b) noexcept 47 | { 48 | _data = b._data; 49 | b._data = nullptr; 50 | } 51 | template > = true> 52 | poly_box& operator=(poly_box&& b) noexcept 53 | { 54 | if (_data) 55 | delete _data; 56 | _data = b._data; 57 | b._data = nullptr; 58 | 59 | return *this; 60 | } 61 | 62 | template 63 | U& emplace(Args&&... args) 64 | { 65 | static_assert(std::is_base_of_v, "classes not compatible"); 66 | delete _data; 67 | auto p = new U(cc::forward(args)...); 68 | _data = p; 69 | return *p; 70 | } 71 | 72 | poly_box(poly_box const&) = delete; 73 | poly_box& operator=(poly_box const&) = delete; 74 | 75 | ~poly_box() { delete _data; } 76 | 77 | template 78 | friend poly_box make_poly_box(Args&&... args); 79 | 80 | template 81 | friend struct poly_box; 82 | 83 | [[nodiscard]] T* get() { return _data; } 84 | [[nodiscard]] T const* get() const { return _data; } 85 | 86 | T* operator->() 87 | { 88 | CC_ASSERT_NOT_NULL(_data); // moved-from state? 89 | return _data; 90 | } 91 | T const* operator->() const 92 | { 93 | CC_ASSERT_NOT_NULL(_data); // moved-from state? 94 | return _data; 95 | } 96 | T& operator*() 97 | { 98 | CC_ASSERT_NOT_NULL(_data); // moved-from state? 99 | return *_data; 100 | } 101 | T const& operator*() const 102 | { 103 | CC_ASSERT_NOT_NULL(_data); // moved-from state? 104 | return *_data; 105 | } 106 | 107 | operator T const&() const { return *_data; } 108 | operator T&() { return *_data; } 109 | 110 | private: 111 | explicit poly_box(T* data) : _data(data) {} 112 | 113 | T* _data; 114 | }; 115 | 116 | template 117 | poly_box make_poly_box(Args&&... args) 118 | { 119 | return poly_box(new T(cc::forward(args)...)); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/clean-core/experimental/wrapped_span.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // int64_t 4 | 5 | #include 6 | 7 | namespace cc 8 | { 9 | /// a non-owning view of a contiguous array of Ts 10 | /// works exactly like span, except for access via the subscript operator 11 | /// Indices wrap around, there are no bounds checks 12 | /// The last element can be accessed with my_span[-1], the second last element with my_span[-2] and so on, 13 | /// while the first element will be returned when using my_span[my_span.size()] 14 | template 15 | struct wrapped_span : public span 16 | { 17 | public: 18 | // ctors 19 | using span::span; 20 | 21 | // container 22 | public: 23 | constexpr T& operator[](int64_t i) const 24 | { 25 | CC_ASSERT(this->size() > 0); 26 | i = i % int64_t(this->size()); 27 | if (i < 0) 28 | i += int64_t(this->size()); 29 | return this->data()[i]; 30 | } 31 | }; 32 | 33 | // deduction guide for containers 34 | template > = true> 35 | wrapped_span(Container& c) -> wrapped_span>; 36 | template > = true> 37 | wrapped_span(Container&& c) -> wrapped_span>; 38 | } // namespace cc 39 | -------------------------------------------------------------------------------- /src/clean-core/explicit_bool.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cc 4 | { 5 | /// A strongly-typed boolean that does not implicitly decay to int 6 | struct explicit_bool 7 | { 8 | constexpr explicit_bool() = default; 9 | constexpr explicit_bool(bool v) : _value(v) {} 10 | 11 | constexpr explicit operator bool() const { return _value; } 12 | 13 | constexpr explicit_bool operator!() const { return !_value; } 14 | constexpr bool operator==(explicit_bool b) const { return _value == b._value; } 15 | constexpr bool operator!=(explicit_bool b) const { return _value != b._value; } 16 | 17 | private: 18 | bool _value = false; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/clean-core/forward.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cc 7 | { 8 | template 9 | CC_FORCE_INLINE constexpr T&& forward(remove_reference& t) noexcept 10 | { 11 | return static_cast(t); 12 | } 13 | template 14 | CC_FORCE_INLINE constexpr T&& forward(remove_reference&& t) noexcept 15 | { 16 | return static_cast(t); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/clean-core/from_string.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | // converts strings to primitive types 8 | // returns true only if the string was successfully parsed and entirely consumed 9 | 10 | namespace cc 11 | { 12 | [[nodiscard]] bool from_string(cc::string_view str, bool& out_value); // str: "true" or "false" 13 | [[nodiscard]] bool from_string(cc::string_view str, char& out_value); 14 | 15 | [[nodiscard]] bool from_string(char const* c_str, int8_t& out_value); 16 | [[nodiscard]] bool from_string(char const* c_str, int16_t& out_value); 17 | [[nodiscard]] bool from_string(char const* c_str, int32_t& out_value); 18 | [[nodiscard]] bool from_string(char const* c_str, int64_t& out_value); 19 | 20 | [[nodiscard]] bool from_string(char const* c_str, uint8_t& out_value); 21 | [[nodiscard]] bool from_string(char const* c_str, uint16_t& out_value); 22 | [[nodiscard]] bool from_string(char const* c_str, uint32_t& out_value); 23 | [[nodiscard]] bool from_string(char const* c_str, uint64_t& out_value); 24 | 25 | [[nodiscard]] bool from_string(char const* c_str, float& out_value); 26 | [[nodiscard]] bool from_string(char const* c_str, double& out_value); 27 | 28 | [[nodiscard]] bool from_string(cc::string_view str, int8_t& out_value); 29 | [[nodiscard]] bool from_string(cc::string_view str, int16_t& out_value); 30 | [[nodiscard]] bool from_string(cc::string_view str, int32_t& out_value); 31 | [[nodiscard]] bool from_string(cc::string_view str, int64_t& out_value); 32 | 33 | [[nodiscard]] bool from_string(cc::string_view str, uint8_t& out_value); 34 | [[nodiscard]] bool from_string(cc::string_view str, uint16_t& out_value); 35 | [[nodiscard]] bool from_string(cc::string_view str, uint32_t& out_value); 36 | [[nodiscard]] bool from_string(cc::string_view str, uint64_t& out_value); 37 | 38 | [[nodiscard]] bool from_string(cc::string_view str, float& out_value); 39 | [[nodiscard]] bool from_string(cc::string_view str, double& out_value); 40 | 41 | // 2 chars hex, e.g. "FF" or "1A" 42 | [[nodiscard]] bool from_string(cc::string_view str, std::byte& out_value); 43 | } 44 | -------------------------------------------------------------------------------- /src/clean-core/function_ptr.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | namespace detail 8 | { 9 | template 10 | struct function_ptr_t 11 | { 12 | static_assert(always_false, "this should only be used with function signatures"); 13 | }; 14 | template 15 | struct function_ptr_t 16 | { 17 | using type = R (*)(Args...); 18 | }; 19 | template 20 | struct function_ptr_t 21 | { 22 | using type = R (*)(Args...) noexcept; 23 | }; 24 | } 25 | template 26 | using function_ptr = typename detail::function_ptr_t::type; 27 | } 28 | -------------------------------------------------------------------------------- /src/clean-core/functors.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | /// a function object that ignores all arguments 8 | struct void_function 9 | { 10 | template 11 | constexpr void operator()(Args&&...) const noexcept 12 | { 13 | } 14 | }; 15 | 16 | /// a function object that always returns a given constant 17 | template 18 | struct constant_function 19 | { 20 | template 21 | constexpr auto operator()(Args&&...) const noexcept 22 | { 23 | return C; 24 | } 25 | }; 26 | 27 | /// general-purpose identity function object 28 | /// (preserves value category) 29 | struct identity_function 30 | { 31 | template 32 | constexpr T&& operator()(T&& v) const noexcept 33 | { 34 | return cc::forward(v); 35 | } 36 | }; 37 | 38 | /// always returns the I-th argument 39 | /// (preserves value category) 40 | template 41 | struct projection_function 42 | { 43 | template 44 | constexpr decltype(auto) operator()(T&&, Rest&&... args) const noexcept 45 | { 46 | static_assert(sizeof...(Rest) + 1 > I, "not enough arguments"); 47 | return projection_function{}(cc::forward(args)...); 48 | } 49 | }; 50 | template <> 51 | struct projection_function<0> 52 | { 53 | template 54 | constexpr T&& operator()(T&& v, Rest&&...) const noexcept 55 | { 56 | return cc::forward(v); 57 | } 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /src/clean-core/fwd.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | // constants 8 | enum : size_t 9 | { 10 | dynamic_size = size_t(-1) 11 | }; 12 | 13 | // utility 14 | template // SFINAE-friendly for cc::enable_if 15 | struct hash; 16 | 17 | template // SFINAE-friendly for cc::enable_if 18 | struct less; 19 | template // SFINAE-friendly for cc::enable_if 20 | struct greater; 21 | template // SFINAE-friendly for cc::enable_if 22 | struct equal_to; 23 | 24 | struct nullopt_t; 25 | template 26 | struct optional; 27 | template 28 | struct variant; 29 | template 30 | struct result; 31 | 32 | template 33 | struct pair; 34 | template 35 | struct tuple; 36 | 37 | // containers and ranges 38 | template 39 | struct span; 40 | template 41 | struct strided_span; 42 | 43 | template 44 | struct vector; 45 | template 46 | struct vector_ex; 47 | template 48 | struct capped_vector; 49 | template 50 | struct alloc_vector; 51 | 52 | template 53 | struct array; 54 | template 55 | struct fwd_array; 56 | template 57 | struct capped_array; 58 | template 59 | struct alloc_array; 60 | 61 | template 62 | struct bitset; 63 | 64 | template , class EqualT = cc::equal_to> 65 | struct map; 66 | template , class EqualT = cc::equal_to> 67 | struct set; 68 | template 69 | struct atomic_linked_pool; 70 | 71 | // values 72 | template 73 | struct box; 74 | template 75 | struct fwd_box; 76 | template 77 | struct poly_box; 78 | template 79 | struct capped_box; 80 | template 81 | using pimpl = fwd_box; 82 | 83 | // strings 84 | struct string_view; 85 | template 86 | struct sbo_string; 87 | using string = sbo_string<15>; 88 | struct string_stream; 89 | struct string_stream_ref; 90 | 91 | // streams 92 | template 93 | struct stream_ref; 94 | 95 | // functional 96 | template 97 | struct unique_function; 98 | template 99 | struct function_ref; 100 | 101 | // smart pointer 102 | template 103 | struct unique_ptr; 104 | template 105 | struct poly_unique_ptr; 106 | template 107 | struct shared_ptr; 108 | 109 | // locks 110 | struct spin_lock; 111 | template 112 | struct lock_guard; 113 | 114 | // allocators 115 | struct allocator; 116 | struct linear_allocator; 117 | struct stack_allocator; 118 | struct tlsf_allocator; 119 | struct atomic_pool_allocator; 120 | struct atomic_linear_allocator; 121 | 122 | extern allocator* const system_allocator; 123 | 124 | // experimental 125 | template 126 | struct ringbuffer; 127 | } // namespace cc 128 | -------------------------------------------------------------------------------- /src/clean-core/fwd_array.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace cc 17 | { 18 | /// a forward-declaration friendly array 19 | template 20 | struct fwd_array 21 | { 22 | fwd_array() = default; 23 | 24 | explicit fwd_array(size_t size) 25 | { 26 | _op.init(); 27 | _size = size; 28 | _data = new T[_size](); // default ctor! 29 | } 30 | 31 | [[nodiscard]] static fwd_array defaulted(size_t size) { return fwd_array(size); } 32 | 33 | [[nodiscard]] static fwd_array uninitialized(size_t size) 34 | { 35 | fwd_array a; 36 | a._op.init(); 37 | a._size = size; 38 | a._data = new T[size]; 39 | return a; 40 | } 41 | 42 | [[nodiscard]] static fwd_array filled(size_t size, T const& value) 43 | { 44 | fwd_array a; 45 | a._op.init(); 46 | a._size = size; 47 | a._data = new T[size]; 48 | cc::fill(a, value); 49 | return a; 50 | } 51 | 52 | fwd_array(std::initializer_list data) 53 | { 54 | _op.init(); 55 | _size = data.size(); 56 | _data = new T[_size]; 57 | detail::container_copy_construct_range(data.begin(), _size, _data); 58 | } 59 | 60 | fwd_array(span data) 61 | { 62 | _op.init(); 63 | _size = data.size(); 64 | _data = new T[_size]; 65 | detail::container_copy_construct_range(data.data(), _size, _data); 66 | } 67 | 68 | fwd_array(fwd_array&& a) noexcept 69 | { 70 | _op = a._op; 71 | _data = a._data; 72 | _size = a._size; 73 | a._data = nullptr; 74 | a._size = 0; 75 | } 76 | fwd_array& operator=(fwd_array&& a) noexcept 77 | { 78 | if (_data) 79 | _op.delete_data(*this); 80 | _op = a._op; 81 | _data = a._data; 82 | _size = a._size; 83 | a._data = nullptr; 84 | a._size = 0; 85 | return *this; 86 | } 87 | ~fwd_array() 88 | { 89 | if (_data) 90 | _op.delete_data(*this); 91 | } 92 | 93 | fwd_array(fwd_array const& a) = delete; 94 | fwd_array& operator=(fwd_array const& a) = delete; 95 | 96 | constexpr T* begin() { return _data; } 97 | constexpr T* end() { return _data + _size; } 98 | constexpr T const* begin() const { return _data; } 99 | constexpr T const* end() const { return _data + _size; } 100 | constexpr T* data() { return _data; } 101 | constexpr T const* data() const { return _data; } 102 | constexpr size_t size() const { return _size; } 103 | constexpr bool empty() const { return _size == 0; } 104 | 105 | constexpr T& operator[](size_t i) 106 | { 107 | CC_CONTRACT(i < _size); 108 | return _data[i]; 109 | } 110 | constexpr T const& operator[](size_t i) const 111 | { 112 | CC_CONTRACT(i < _size); 113 | return _data[i]; 114 | } 115 | 116 | private: 117 | T* _data = nullptr; 118 | size_t _size = 0; 119 | 120 | struct ops 121 | { 122 | cc::function_ptr delete_data = nullptr; 123 | 124 | void init() 125 | { 126 | static_assert(sizeof(T) > 0, "cannot construct array of incomplete object"); 127 | 128 | delete_data = [](fwd_array& a) { delete[] a._data; }; 129 | } 130 | } _op; 131 | }; 132 | } 133 | -------------------------------------------------------------------------------- /src/clean-core/get.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace cc 9 | { 10 | namespace detail 11 | { 12 | template 13 | constexpr auto impl_get(T&& v, cc::priority_tag<1>) -> decltype(v.template get()) 14 | { 15 | return v.template get(); 16 | } 17 | template 18 | constexpr auto impl_get(T&& v, cc::priority_tag<0>) -> decltype(std::get(v)) 19 | { 20 | return std::get(v); 21 | } 22 | } 23 | 24 | template 25 | [[nodiscard]] constexpr decltype(auto) get(T&& v) 26 | { 27 | return cc::detail::impl_get(v, cc::priority_tag<1>{}); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/clean-core/has_operator.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include // declval 5 | 6 | namespace cc 7 | { 8 | namespace detail 9 | { 10 | template 11 | [[maybe_unused]] static auto test_op_less(int) -> decltype(bool(std::declval() < std::declval()), std::true_type{}); 12 | template 13 | [[maybe_unused]] static auto test_op_less(char) -> std::false_type; 14 | 15 | template 16 | [[maybe_unused]] static auto test_op_greater(int) -> decltype(bool(std::declval() > std::declval()), std::true_type{}); 17 | template 18 | [[maybe_unused]] static auto test_op_greater(char) -> std::false_type; 19 | 20 | template 21 | [[maybe_unused]] static auto test_op_less_or_equal(int) -> decltype(bool(std::declval() <= std::declval()), std::true_type{}); 22 | template 23 | [[maybe_unused]] static auto test_op_less_or_equal(char) -> std::false_type; 24 | 25 | template 26 | [[maybe_unused]] static auto test_op_greater_or_equal(int) -> decltype(bool(std::declval() >= std::declval()), std::true_type{}); 27 | template 28 | [[maybe_unused]] static auto test_op_greater_or_equal(char) -> std::false_type; 29 | 30 | template 31 | [[maybe_unused]] static auto test_op_equal(int) -> decltype(bool(std::declval() == std::declval()), std::true_type{}); 32 | template 33 | [[maybe_unused]] static auto test_op_equal(char) -> std::false_type; 34 | 35 | template 36 | [[maybe_unused]] static auto test_op_not_equal(int) -> decltype(bool(std::declval() != std::declval()), std::true_type{}); 37 | template 38 | [[maybe_unused]] static auto test_op_not_equal(char) -> std::false_type; 39 | 40 | template 41 | [[maybe_unused]] static auto test_op_left_shift(int) -> decltype(std::declval() << std::declval(), std::true_type{}); 42 | template 43 | [[maybe_unused]] static auto test_op_left_shift(char) -> std::false_type; 44 | 45 | template 46 | [[maybe_unused]] static auto test_op_subscript(int) -> decltype(std::declval()[std::declval()], std::true_type{}); 47 | template 48 | [[maybe_unused]] static auto test_op_subscript(char) -> std::false_type; 49 | } // namespace detail 50 | 51 | template 52 | constexpr bool has_operator_less = decltype(detail::test_op_less(0))::value; 53 | template 54 | constexpr bool has_operator_less_or_equal = decltype(detail::test_op_less_or_equal(0))::value; 55 | template 56 | constexpr bool has_operator_greater = decltype(detail::test_op_greater(0))::value; 57 | template 58 | constexpr bool has_operator_greater_or_equal = decltype(detail::test_op_greater_or_equal(0))::value; 59 | template 60 | constexpr bool has_operator_equal = decltype(detail::test_op_equal(0))::value; 61 | template 62 | constexpr bool has_operator_not_equal = decltype(detail::test_op_not_equal(0))::value; 63 | 64 | template 65 | constexpr bool has_operator_left_shift = decltype(detail::test_op_left_shift(0))::value; 66 | 67 | template 68 | constexpr bool has_operator_subscript = decltype(detail::test_op_subscript(0))::value; 69 | } 70 | -------------------------------------------------------------------------------- /src/clean-core/hash.cc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-arcana/clean-core/3097be6e3d3617982b18585e294c007f806b895f/src/clean-core/hash.cc -------------------------------------------------------------------------------- /src/clean-core/hash.sha1.cc: -------------------------------------------------------------------------------- 1 | #include "hash.sha1.hh" 2 | 3 | cc::array cc::make_hash_sha1(cc::span data) 4 | { 5 | sha1_builder sha1; 6 | 7 | sha1.reset(); 8 | sha1.add(data); 9 | return sha1.finalize(); 10 | } 11 | 12 | cc::array cc::make_hash_sha1(string_view data) { return cc::make_hash_sha1(cc::as_byte_span(data)); } 13 | 14 | cc::string cc::make_hash_sha1_string(cc::span data) 15 | { 16 | auto hex = "0123456789abcdef"; 17 | auto hash = cc::make_hash_sha1(data); 18 | auto s = cc::string::uninitialized(40); 19 | for (auto i = 0; i < 20; ++i) 20 | { 21 | s[i * 2 + 0] = hex[uint8_t(hash[i]) >> 4]; 22 | s[i * 2 + 1] = hex[uint8_t(hash[i]) & 0b1111]; 23 | } 24 | return s; 25 | } 26 | 27 | cc::string cc::make_hash_sha1_string(string_view data) { return cc::make_hash_sha1_string(cc::as_byte_span(data)); } 28 | -------------------------------------------------------------------------------- /src/clean-core/hash.xxh3.cc: -------------------------------------------------------------------------------- 1 | #include "hash.xxh3.hh" 2 | 3 | #include 4 | 5 | uint64_t cc::make_hash_xxh3(cc::span data, uint64_t seed) { return XXH3_64bits_withSeed(data.data(), data.size(), seed); } 6 | -------------------------------------------------------------------------------- /src/clean-core/hash.xxh3.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace cc 9 | { 10 | // returns a hash of the data by executing https://github.com/Cyan4973/xxHash 11 | uint64_t make_hash_xxh3(cc::span data, uint64_t seed); 12 | } 13 | -------------------------------------------------------------------------------- /src/clean-core/hash_combine.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | [[nodiscard]] constexpr uint64_t hash_combine() noexcept { return 0x2a5114b5c6133408uLL; } 8 | [[nodiscard]] constexpr uint64_t hash_combine(uint64_t a) noexcept { return a; } 9 | [[nodiscard]] constexpr uint64_t hash_combine(uint64_t a, uint64_t b) noexcept { return a * 6364136223846793005ULL + b + 0xda3e39cb94b95bdbULL; } 10 | 11 | template 12 | [[nodiscard]] constexpr uint64_t hash_combine(uint64_t a, uint64_t b, uint64_t c, Args... rest) noexcept 13 | { 14 | auto h = hash_combine(a, b); 15 | h = hash_combine(h, c); 16 | ((h = hash_combine(h, rest)), ...); 17 | return h; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/clean-core/indices_of.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cc 7 | { 8 | /// returns a stupid-simple-range that iterates over the indices of a sized range 9 | /// usage: 10 | /// 11 | /// cc::vector v = ...; 12 | /// 13 | /// for (auto i : cc::indices_of(v)) 14 | /// use(i); 15 | /// 16 | /// // is the same as: 17 | /// 18 | /// for (size_t i = 0; i != v.size(); ++i) 19 | /// use(i); 20 | /// 21 | /// // and the same as 22 | /// for (auto i : cc::indices_of(v.size())( 23 | /// use(i); 24 | /// 25 | /// the generated assembly is equivalent for simple loops: https://godbolt.org/z/vvEKno4jT 26 | /// and can be better when aliasing is involved: https://godbolt.org/z/Tv5ddjvaz 27 | template 28 | constexpr auto indices_of(SizedRangeOrIntegral const& range) 29 | { 30 | if constexpr (std::is_integral_v) 31 | { 32 | using T = SizedRangeOrIntegral; 33 | return cc::detail::irange{T(0), range}; 34 | } 35 | else 36 | { 37 | using T = std::decay_t; 38 | return cc::detail::irange{T(0), cc::collection_size(range)}; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/clean-core/invoke.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace cc 9 | { 10 | // std::invoke implementation without , from 11 | // https://en.cppreference.com/w/cpp/utility/functional/invoke 12 | namespace detail 13 | { 14 | template 15 | constexpr decltype(auto) perform_invoke(Type T::*f, T1&& t1, Args&&... args) 16 | { 17 | if constexpr (std::is_member_function_pointer_v) 18 | { 19 | if constexpr (std::is_base_of_v>) 20 | return (cc::forward(t1).*f)(cc::forward(args)...); 21 | else if constexpr (cc::is_reference_wrapper>) 22 | return (t1.get().*f)(cc::forward(args)...); 23 | else 24 | return ((*cc::forward(t1)).*f)(cc::forward(args)...); 25 | } 26 | else 27 | { 28 | static_assert(std::is_member_object_pointer_v); 29 | static_assert(sizeof...(args) == 0); 30 | if constexpr (std::is_base_of_v>) 31 | return cc::forward(t1).*f; 32 | else if constexpr (cc::is_reference_wrapper>) 33 | return t1.get().*f; 34 | else 35 | return (*cc::forward(t1)).*f; 36 | } 37 | } 38 | 39 | template 40 | constexpr decltype(auto) perform_invoke(F&& f, Args&&... args) 41 | { 42 | return cc::forward(f)(cc::forward(args)...); 43 | } 44 | } 45 | 46 | template 47 | constexpr decltype(auto) invoke(F&& f, Args&&... args) noexcept(std::is_nothrow_invocable_v) 48 | { 49 | return detail::perform_invoke(cc::forward(f), cc::forward(args)...); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/clean-core/is_contiguous_range.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cc 7 | { 8 | namespace detail 9 | { 10 | template 11 | auto contiguous_range_test(Container* c) -> decltype(static_cast(c->data()), static_cast(c->size()), 0); 12 | template 13 | char contiguous_range_test(...); 14 | 15 | template 16 | auto contiguous_range_test_of_pods(Container* c) 17 | -> std::enable_if_tdata())>>, // c->data exists and is pointer-to-pod 18 | decltype(static_cast(c->size()), 0)>; // c->size exists and is integral 19 | template 20 | char contiguous_range_test_of_pods(...); 21 | } 22 | 23 | template 24 | static constexpr bool is_contiguous_range = sizeof(detail::contiguous_range_test, ElementT>(nullptr)) == sizeof(int); 25 | 26 | template 27 | static constexpr bool is_contiguous_range = true; 28 | 29 | template 30 | static constexpr bool is_contiguous_range = true; 31 | 32 | template 33 | static constexpr bool is_any_contiguous_range = is_contiguous_range; 34 | 35 | 36 | template 37 | static constexpr bool is_any_contiguous_range_of_pods 38 | = sizeof(detail::contiguous_range_test_of_pods>(nullptr)) == sizeof(int); 39 | template 40 | static constexpr bool is_any_contiguous_range_of_pods = std::is_trivially_copyable_v; 41 | } 42 | -------------------------------------------------------------------------------- /src/clean-core/is_range.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cc 7 | { 8 | namespace detail 9 | { 10 | template 11 | struct is_range_t : std::false_type 12 | { 13 | }; 14 | template 15 | struct is_range_t : std::true_type 16 | { 17 | }; 18 | template 19 | struct is_range_t : std::true_type 20 | { 21 | }; 22 | template 23 | struct is_range_t : std::true_type 24 | { 25 | }; 26 | template 27 | struct is_range_t : std::true_type 28 | { 29 | }; 30 | template 31 | struct is_range_t(*std::declval().begin())), // 35 | decltype(std::declval().end()) // 36 | >> : std::true_type 37 | { 38 | }; 39 | 40 | template 41 | struct is_any_range_t : std::false_type 42 | { 43 | }; 44 | template 45 | struct is_any_range_t : std::true_type 46 | { 47 | }; 48 | template 49 | struct is_any_range_t : std::true_type 50 | { 51 | }; 52 | template 53 | struct is_any_range_t().begin()), // 56 | decltype(std::declval().end()) // 57 | >> : std::true_type 58 | { 59 | }; 60 | } 61 | 62 | template 63 | static constexpr bool is_range = detail::is_range_t::value; 64 | template 65 | static constexpr bool is_any_range = detail::is_any_range_t::value; 66 | } 67 | -------------------------------------------------------------------------------- /src/clean-core/iterator.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // std::is_array_v, std::extent_v 4 | 5 | #include 6 | 7 | namespace cc::detail 8 | { 9 | template 10 | constexpr auto begin_impl(ContainerT& c, cc::priority_tag<0>) -> decltype(begin(c)) 11 | { 12 | return begin(c); 13 | } 14 | 15 | template 16 | constexpr auto begin_impl(ContainerT& c, cc::priority_tag<1>) -> decltype(c.begin()) 17 | { 18 | return c.begin(); 19 | } 20 | 21 | template 22 | constexpr auto end_impl(ContainerT& c, cc::priority_tag<0>) -> decltype(end(c)) 23 | { 24 | return end(c); 25 | } 26 | 27 | template 28 | constexpr auto end_impl(ContainerT& c, cc::priority_tag<1>) -> decltype(c.end()) 29 | { 30 | return c.end(); 31 | } 32 | } 33 | 34 | namespace cc 35 | { 36 | template 37 | constexpr auto begin(ContainerT& c) 38 | { 39 | if constexpr (std::is_array_v) 40 | return c; 41 | else 42 | return cc::detail::begin_impl(c, cc::priority_tag<1>{}); 43 | } 44 | 45 | template 46 | constexpr auto end(ContainerT& c) 47 | { 48 | if constexpr (std::is_array_v) 49 | return c + std::extent_v; 50 | else 51 | return cc::detail::end_impl(c, cc::priority_tag<1>{}); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/clean-core/less.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace cc 8 | { 9 | template 10 | struct less 11 | { 12 | static_assert(has_operator_less, "if no bool-valued operator< is found, provide a specialization less<>"); 13 | 14 | [[nodiscard]] constexpr bool operator()(T const& a, T const& b) const noexcept { return a < b; } 15 | }; 16 | 17 | template <> 18 | struct less // transparent comparison 19 | { 20 | template 21 | [[nodiscard]] constexpr bool operator()(A const& a, B const& b) const noexcept 22 | { 23 | return a < b; 24 | } 25 | }; 26 | 27 | template 28 | struct greater 29 | { 30 | static_assert(has_operator_greater, "if no bool-valued operator< is found, provide a specialization greater<>"); 31 | 32 | [[nodiscard]] constexpr bool operator()(T const& a, T const& b) const noexcept { return a > b; } 33 | }; 34 | 35 | template <> 36 | struct greater // transparent comparison 37 | { 38 | template 39 | [[nodiscard]] constexpr bool operator()(A const& a, B const& b) const noexcept 40 | { 41 | return a > b; 42 | } 43 | }; 44 | 45 | /// helper for implementing total orders for tuple-like types 46 | /// 47 | /// example: 48 | /// 49 | /// struct foo 50 | /// { 51 | /// int x; 52 | /// int y; 53 | /// bool operator<(foo const& r) const { 54 | /// // implements lexicographic order on (x, y) 55 | /// return cc::cascaded_less( 56 | /// x, r.x, 57 | /// y, r.y 58 | /// ); 59 | /// } 60 | /// }; 61 | /// 62 | /// NOTE: always uses op< and op== internally 63 | /// 64 | /// TODO: use spaceship in c++20 65 | template 66 | [[nodiscard]] constexpr bool cascaded_less(Lhs const& lhs, Rhs const& rhs, Rest const&... rest) 67 | { 68 | static_assert(sizeof...(Rest) % 2 == 0, "must provide an even number of arguments"); 69 | if constexpr (sizeof...(Rest) == 0) 70 | return lhs < rhs; 71 | else 72 | return lhs != rhs ? lhs < rhs : cc::cascaded_less(rest...); 73 | } 74 | 75 | } // namespace cc 76 | -------------------------------------------------------------------------------- /src/clean-core/lock_guard.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | template 8 | struct [[nodiscard]] lock_guard 9 | { 10 | CC_FORCE_INLINE explicit lock_guard(T& mutex) : _lock(mutex) { _lock.lock(); } 11 | CC_FORCE_INLINE ~lock_guard() { _lock.unlock(); } 12 | 13 | lock_guard(lock_guard const&) = delete; 14 | lock_guard(lock_guard&&) noexcept = delete; 15 | lock_guard& operator=(lock_guard const&) = delete; 16 | lock_guard& operator=(lock_guard&&) noexcept = delete; 17 | 18 | private: 19 | T& _lock; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/clean-core/move.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cc 7 | { 8 | template 9 | CC_FORCE_INLINE constexpr remove_reference&& move(T&& t) noexcept 10 | { 11 | return static_cast&&>(t); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/clean-core/native/detail/win32_sanitize_after.inl: -------------------------------------------------------------------------------- 1 | #pragma warning(pop) 2 | 3 | #undef near 4 | #undef far 5 | #undef min 6 | #undef max 7 | 8 | #undef INT 9 | #undef UINT 10 | #undef DWORD 11 | #undef FLOAT 12 | #undef uint8 13 | #undef uint16 14 | #undef uint32 15 | #undef int32 16 | #undef float 17 | 18 | #undef PF_MAX 19 | #undef PlaySound 20 | #undef DrawText 21 | #undef CaptureStackBackTrace 22 | #undef MemoryBarrier 23 | #undef DeleteFile 24 | #undef MoveFile 25 | #undef CopyFile 26 | #undef CreateDirectory 27 | #undef GetCurrentTime 28 | #undef SendMessage 29 | #undef LoadString 30 | #undef UpdateResource 31 | #undef FindWindow 32 | #undef GetObject 33 | #undef GetEnvironmentVariable 34 | #undef CreateFont 35 | #undef CreateDesktop 36 | #undef GetMessage 37 | #undef GetCommandLine 38 | #undef GetProp 39 | #undef SetProp 40 | #undef GetFileAttributes 41 | #undef ReportEvent 42 | #undef GetClassName 43 | #undef GetClassInfo 44 | #undef Yield 45 | #undef IMediaEventSink 46 | -------------------------------------------------------------------------------- /src/clean-core/native/memory.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace cc 9 | { 10 | // reserves a range of pages in virtual memory 11 | std::byte* reserve_virtual_memory(size_t size_bytes); 12 | 13 | // frees a virtual memory range, the result of reserve_virtual_memory 14 | // also decommits all physical regions inside 15 | void free_virtual_memory(std::byte* ptr, size_t size_bytes); 16 | 17 | // commits a region of pages inside a reserved, virtual memory range 18 | void commit_physical_memory(std::byte* ptr, size_t size_bytes); 19 | 20 | // touches (reads) the first byte of every page in this range to ensure pages are available 21 | void prefault_memory(std::byte* ptr, size_t size_bytes); 22 | 23 | // decommits a region of pages inside a reserved, virtual memory range 24 | void decommit_physical_memory(std::byte* ptr, size_t size_bytes); 25 | 26 | // commits new region of pages in multiples of a given chunk size 27 | // checks if virtual_end is exceeded, returns the new physical end ptr 28 | std::byte* grow_physical_memory(std::byte* physical_current, std::byte* physical_end, std::byte* virtual_end, size_t chunk_size, size_t grow_num_bytes); 29 | 30 | // header for an allocation in a stack-like allocator 31 | struct stack_alloc_header 32 | { 33 | uint32_t padding; 34 | int32_t alloc_id; 35 | }; 36 | 37 | // [... pad ...] [header] [data] 38 | inline std::byte* align_up_with_header(std::byte* head, size_t align, size_t header_size) 39 | { 40 | std::byte* padded_res = cc::align_up(head, align); 41 | size_t const padding = size_t(padded_res - head); 42 | 43 | if (padding < header_size) 44 | { 45 | // header does not fit - align up 46 | header_size -= padding; 47 | 48 | if (header_size % align > 0) 49 | { 50 | padded_res += align * (1 + (header_size / align)); 51 | } 52 | else 53 | { 54 | padded_res += align * (header_size / align); 55 | } 56 | } 57 | 58 | return padded_res; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/clean-core/native/timing.cc: -------------------------------------------------------------------------------- 1 | #include "timing.hh" -------------------------------------------------------------------------------- /src/clean-core/native/timing.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace cc 8 | { 9 | // returns a high precision platform specific tick counter 10 | // divide by frequency to convert to seconds 11 | CC_FORCE_INLINE int64_t get_high_precision_ticks(); 12 | 13 | // returns the amount of ticks per second 14 | CC_FORCE_INLINE int64_t get_high_precision_frequency(); 15 | } 16 | 17 | #ifdef CC_OS_WINDOWS 18 | 19 | #include 20 | 21 | CC_FORCE_INLINE int64_t cc::get_high_precision_ticks() 22 | { 23 | ::LARGE_INTEGER timestamp; 24 | ::QueryPerformanceCounter(×tamp); 25 | return timestamp.QuadPart; 26 | } 27 | 28 | CC_FORCE_INLINE int64_t cc::get_high_precision_frequency() 29 | { 30 | ::LARGE_INTEGER frequency; 31 | ::QueryPerformanceFrequency(&frequency); 32 | return frequency.QuadPart; 33 | } 34 | 35 | #elif defined(CC_OS_LINUX) || defined(CC_OS_APPLE) 36 | 37 | #include 38 | #include 39 | #include 40 | 41 | CC_FORCE_INLINE int64_t cc::get_high_precision_ticks() 42 | { 43 | ::timespec ts; 44 | ::clock_gettime(CLOCK_MONOTONIC, &ts); 45 | return ts.tv_sec * 1000000000LL + ts.tv_nsec; 46 | } 47 | 48 | CC_FORCE_INLINE int64_t cc::get_high_precision_frequency() { return 1000000000LL; } 49 | 50 | #else 51 | #error "Unsupported platform" 52 | #endif 53 | -------------------------------------------------------------------------------- /src/clean-core/native/wchar_conversion.cc: -------------------------------------------------------------------------------- 1 | #include "wchar_conversion.hh" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | int cc::widechar_to_char(cc::span dest, const wchar_t* src, int opt_num_src_chars) 11 | { 12 | #ifdef CC_OS_WINDOWS 13 | return ::WideCharToMultiByte(CP_UTF8, 0, src, opt_num_src_chars, dest.data(), int(dest.size()), nullptr, nullptr); 14 | #else 15 | std::mbstate_t state = {}; 16 | int num_dest_chars = opt_num_src_chars > 0 ? cc::min(int(dest.size()), opt_num_src_chars / int(sizeof(wchar_t))) : int(dest.size()); 17 | return int(std::wcsrtombs(dest.data(), &src, size_t(num_dest_chars), &state)); 18 | #endif 19 | } 20 | 21 | int cc::char_to_widechar(cc::span dest, const char* src, int opt_num_src_chars) 22 | { 23 | #ifdef CC_OS_WINDOWS 24 | return ::MultiByteToWideChar(CP_UTF8, 0, src, opt_num_src_chars, dest.data(), int(dest.size())); 25 | #else 26 | std::mbstate_t state = {}; 27 | int num_dest_chars = opt_num_src_chars > 0 ? cc::min(int(dest.size()), opt_num_src_chars) : int(dest.size()); 28 | return int(std::mbsrtowcs(dest.data(), &src, size_t(num_dest_chars), &state)); 29 | #endif 30 | } 31 | -------------------------------------------------------------------------------- /src/clean-core/native/wchar_conversion.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | /// converts a wchar_t string to a (UTF-8) char string 8 | /// opt_num_src_chars can be specified to stop conversion before '\0' termination 9 | /// returns amount of characters written to dest 10 | int widechar_to_char(cc::span dest, wchar_t const* src, int opt_num_src_chars = -1); 11 | 12 | /// converts a (UTF-8) char string to a wchar_t string 13 | /// opt_num_src_chars can be specified to stop conversion before '\0' termination 14 | /// returns amount of characters written to dest 15 | int char_to_widechar(cc::span dest, char const* src, int opt_num_src_chars = -1); 16 | } 17 | -------------------------------------------------------------------------------- /src/clean-core/native/win32_fwd.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #ifdef CC_OS_WINDOWS 5 | 6 | // Common Win32 entities forward declared 7 | // NOTE: This header might cause conflicts if using Microsoft SAL code analysis tools 8 | // See https://docs.microsoft.com/en-us/cpp/c-runtime-library/sal-annotations?redirectedfrom=MSDN&view=vs-2019 9 | 10 | typedef int BOOL; 11 | typedef unsigned char BYTE; 12 | typedef unsigned short WORD; 13 | typedef unsigned long DWORD; 14 | typedef const wchar_t* LPCWSTR; 15 | typedef void* HANDLE; 16 | typedef struct HINSTANCE__* HINSTANCE; 17 | typedef struct HWND__* HWND; 18 | typedef struct HMONITOR__* HMONITOR; 19 | typedef struct _SECURITY_ATTRIBUTES SECURITY_ATTRIBUTES; 20 | typedef struct _OVERLAPPED OVERLAPPED, *LPOVERLAPPED; 21 | typedef struct _OVERLAPPED_ENTRY OVERLAPPED_ENTRY, *LPOVERLAPPED_ENTRY; 22 | typedef long HRESULT; 23 | typedef unsigned int UINT; 24 | 25 | #ifdef _WIN64 26 | typedef __int64 INT_PTR, *PINT_PTR; 27 | typedef unsigned __int64 UINT_PTR, *PUINT_PTR; 28 | 29 | typedef __int64 LONG_PTR, *PLONG_PTR; 30 | typedef unsigned __int64 ULONG_PTR, *PULONG_PTR; 31 | #else 32 | #error "Unsupported platform" 33 | #endif 34 | 35 | typedef UINT_PTR WPARAM; 36 | typedef LONG_PTR LPARAM; 37 | typedef LONG_PTR LRESULT; 38 | 39 | #if (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED) 40 | #define CALLBACK __stdcall 41 | #define WINAPI __stdcall 42 | #define WINAPIV __cdecl 43 | #define APIENTRY WINAPI 44 | #define APIPRIVATE __stdcall 45 | #define PASCAL __stdcall 46 | #else 47 | #define CALLBACK 48 | #define WINAPI 49 | #define WINAPIV 50 | #define APIENTRY WINAPI 51 | #define APIPRIVATE 52 | #define PASCAL pascal 53 | #endif 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/clean-core/native/win32_sanitized.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) 4 | 5 | // Check if was included somewhere before this header 6 | #if defined(_WINDOWS_) && !defined(CC_SANITIZED_WINDOWS_H) 7 | #pragma message("[clean-core][native/win32_sanitized.hh] Detected unsanitized Windows.h") 8 | #endif 9 | #define CC_SANITIZED_WINDOWS_H 10 | 11 | // clang-format off 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | // clang-format on 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/clean-core/native/win32_util.cc: -------------------------------------------------------------------------------- 1 | #include "win32_util.hh" 2 | 3 | #include 4 | 5 | #ifdef CC_OS_WINDOWS 6 | 7 | #include 8 | 9 | #include 10 | 11 | bool cc::win32_get_version(unsigned& out_major, unsigned& out_minor, unsigned& out_build_number) 12 | { 13 | // getting the current windows version is not as straightforward as it once was, 14 | // since GetVersion and GetVersionEx are deprecated and the Win32 Version Helper functions 15 | // will lie about the OS version depending on the manifest file. 16 | // They also give no direct information about the build number (which corresponds to "Windows 10 Versions" 17 | // like 1904, 1909, 2004, ..., or their marketing names like "Fall Creators Update", "May 2020 Update", ...) 18 | // which nowadays is often the only part of real interest 19 | 20 | // there are several approaches, this one is using the ancient, 21 | // undocumented NTDLL function RtlGetNtVersionNumbers 22 | // out parameters are "major number", "minor number" and "build number" 23 | // the build number must be masked to the lower 16 bit, 24 | // as the high bits contain additional information about it being a checked/free build 25 | 26 | // see 27 | // http://www.geoffchappell.com/studies/windows/win32/ntdll/api/ldrinit/getntversionnumbers.htm 28 | // https://stackoverflow.com/questions/47581146/getting-os-build-version-from-win32-api-c 29 | 30 | HMODULE hModule = GetModuleHandle(TEXT("ntdll.dll")); 31 | 32 | if (hModule == NULL) 33 | return false; 34 | 35 | typedef VOID(WINAPI * RtlGetNtVersionNumbersPtr)(DWORD*, DWORD*, DWORD*); 36 | auto const f_RtlGetNtVersionNumbers = (RtlGetNtVersionNumbersPtr)::GetProcAddress(hModule, "RtlGetNtVersionNumbers"); 37 | 38 | if (f_RtlGetNtVersionNumbers == nullptr) 39 | return false; 40 | 41 | DWORD major_version, minor_version, build_number; 42 | f_RtlGetNtVersionNumbers(&major_version, &minor_version, &build_number); 43 | 44 | out_major = major_version; 45 | out_minor = minor_version; 46 | out_build_number = build_number & 0x0000FFFF; 47 | return true; 48 | } 49 | 50 | #else 51 | 52 | bool cc::win32_get_version(unsigned&, unsigned&, unsigned&) { return false; } 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /src/clean-core/native/win32_util.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cc 4 | { 5 | /// reads the true windows version using the undocumented RtlGetNtVersionNumbers NTDLL kernel function 6 | /// 7 | /// output example on Win10 2004: { major: 10, minor: 0, build_number: 19041 } 8 | /// for a list of build numbers and corresponding "versions" and marketing names, see 9 | /// https://en.wikipedia.org/wiki/Windows_10_version_history#Channels 10 | bool win32_get_version(unsigned& out_major, unsigned& out_minor, unsigned& out_build_number); 11 | } 12 | -------------------------------------------------------------------------------- /src/clean-core/new.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | namespace detail 8 | { 9 | struct placement_new_tag 10 | { 11 | }; 12 | } 13 | [[maybe_unused]] static constexpr cc::detail::placement_new_tag placement_new; 14 | } 15 | 16 | /// Usage: 17 | /// T* ptr = new(cc::placement_new, memory) T(); 18 | inline void* operator new(size_t, cc::detail::placement_new_tag, void* buffer) { return buffer; } 19 | inline void* operator new[](size_t, cc::detail::placement_new_tag, void* buffer) { return buffer; } 20 | inline void operator delete(void*, cc::detail::placement_new_tag, void*) {} 21 | -------------------------------------------------------------------------------- /src/clean-core/overloaded.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cc 4 | { 5 | /** 6 | * Overloading lambdas and other functors: 7 | * 8 | * cc::overloaded( 9 | * [...](...) { ... }, 10 | * [...](...) { ... }, 11 | * ... 12 | * ); 13 | */ 14 | template 15 | struct overloaded : Fs... 16 | { 17 | overloaded(Fs... fs) : Fs(fs)... {} 18 | using Fs::operator()...; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/clean-core/pair.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace cc 10 | { 11 | template 12 | struct pair 13 | { 14 | A first; 15 | B second; 16 | 17 | constexpr pair() = default; 18 | constexpr pair(A f, B s) : first(cc::move(f)), second(cc::move(s)) {} 19 | 20 | template 21 | constexpr bool operator==(pair const& rhs) const 22 | { 23 | return first == rhs.first && second == rhs.second; 24 | } 25 | template 26 | constexpr bool operator!=(pair const& rhs) const 27 | { 28 | return first != rhs.first || second != rhs.second; 29 | } 30 | }; 31 | 32 | template 33 | struct hash> 34 | { 35 | [[nodiscard]] constexpr uint64_t operator()(pair const& v) const noexcept 36 | { 37 | return cc::hash_combine(hash{}(v.first), hash{}(v.second)); 38 | } 39 | }; 40 | 41 | template 42 | constexpr void introspect(I&& i, pair& p) 43 | { 44 | i(p.first, "first"); 45 | i(p.second, "second"); 46 | } 47 | } // namespace cc 48 | -------------------------------------------------------------------------------- /src/clean-core/polymorphic.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cc 4 | { 5 | /// can be used as base class for polymorphic classes 6 | /// (helper that creates virtual dtor and deletes copy/move) 7 | struct polymorphic 8 | { 9 | polymorphic() = default; 10 | 11 | polymorphic(polymorphic const&) = delete; 12 | polymorphic(polymorphic&&) = delete; 13 | polymorphic& operator=(polymorphic const&) = delete; 14 | polymorphic& operator=(polymorphic&&) = delete; 15 | 16 | virtual ~polymorphic() = default; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/clean-core/print.cc: -------------------------------------------------------------------------------- 1 | #include "print.hh" 2 | 3 | #include 4 | 5 | void cc::print(string_view s) { ::fputs(cc::temp_cstr(s), stdout); } 6 | void cc::print(char const* s) { ::fputs(s, stdout); } 7 | void cc::print(cc::string const& s) { ::fputs(s.c_str(), stdout); } 8 | 9 | void cc::println(string_view s) 10 | { 11 | ::puts(cc::temp_cstr(s)); 12 | ::fflush(stdout); 13 | } 14 | void cc::println(char const* s) 15 | { 16 | ::puts(s); 17 | ::fflush(stdout); 18 | } 19 | void cc::println(cc::string const& s) 20 | { 21 | ::puts(s.c_str()); 22 | ::fflush(stdout); 23 | } 24 | -------------------------------------------------------------------------------- /src/clean-core/print.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace cc 8 | { 9 | /// prints the given formatted string to stdout 10 | template 11 | void print(char const* fmt_str, Args const&... args) 12 | { 13 | ::fputs(cc::format(fmt_str, args...).c_str(), stdout); 14 | } 15 | void print(char const* s); 16 | void print(cc::string_view s); 17 | void print(cc::string const& s); 18 | template 19 | void print(T const& v) 20 | { 21 | cc::print("%s", v); 22 | } 23 | /// prints the given formatted string to stdout and appends a \n 24 | /// NOTE: also flushes the stream 25 | /// if this behavior is not desired, use cc::print with \n as part of the format string 26 | template 27 | void println(char const* fmt_str, Args const&... args) 28 | { 29 | ::puts(cc::format(fmt_str, args...).c_str()); 30 | ::fflush(stdout); 31 | } 32 | void println(char const* s); 33 | void println(cc::string_view s); 34 | void println(cc::string const& s); 35 | template 36 | void println(T const& v) 37 | { 38 | cc::println("%s", v); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/clean-core/priority_tag.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | /** 8 | * Priorities for overloads 9 | */ 10 | template 11 | struct priority_tag : priority_tag

12 | { 13 | }; 14 | template <> 15 | struct priority_tag<0> 16 | { 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/clean-core/sentinel.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cc 4 | { 5 | /// a generic end-of-range sentinel 6 | /// use: 7 | /// 8 | /// struct my_struct 9 | /// { 10 | /// ... 11 | /// auto begin() { return ...; } 12 | /// cc::sentinel end() const { return {}; } 13 | /// }; 14 | /// 15 | /// struct my_iterator 16 | /// { 17 | /// bool operator!=(cc::sentinel) const { return is_still_valid(); } 18 | /// }; 19 | /// 20 | struct sentinel 21 | { 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/clean-core/spin_lock.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(__x86_64__) 6 | #include 7 | #endif 8 | 9 | #include 10 | 11 | namespace cc 12 | { 13 | // test-and-test-and-set (TTAS) spinlock 14 | struct spin_lock 15 | { 16 | CC_FORCE_INLINE void lock() noexcept 17 | { 18 | while (true) 19 | { 20 | // immediately try to exchange 21 | // memory order: locking acquires, unlocking releases 22 | if (_is_locked.exchange(true, std::memory_order_acquire) == false) 23 | { 24 | // exhange returned false, meaning the lock was previously unlocked, success 25 | return; 26 | } 27 | 28 | // exchange failed, wait until the value is false without forcing cache misses 29 | while (_is_locked.load(std::memory_order_relaxed)) 30 | { 31 | #if defined(__x86_64__) 32 | // x86 PAUSE to signal spin-wait, improve interleaving 33 | _mm_pause(); 34 | #elif defined(__arm__) || defined(__arm64__) 35 | asm volatile("yield"); 36 | #endif 37 | } 38 | } 39 | } 40 | 41 | CC_FORCE_INLINE bool try_lock() noexcept 42 | { 43 | // early out using a relaxed load to improve performance when spinning on try_lock() 44 | // ref: https://rigtorp.se/spinlock/ 45 | return !_is_locked.load(std::memory_order_relaxed) // 46 | && !_is_locked.exchange(true, std::memory_order_acquire); 47 | } 48 | 49 | CC_FORCE_INLINE void unlock() noexcept 50 | { 51 | // release 52 | _is_locked.store(false, std::memory_order_release); 53 | } 54 | 55 | spin_lock() = default; 56 | spin_lock(spin_lock const& other) = delete; 57 | spin_lock(spin_lock&& other) noexcept = delete; 58 | spin_lock& operator=(spin_lock const& other) = delete; 59 | spin_lock& operator=(spin_lock&& other) noexcept = delete; 60 | 61 | private: 62 | std::atomic_bool _is_locked = {false}; 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /src/clean-core/storage.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cc 4 | { 5 | /** 6 | * Usage: 7 | * 8 | * template 9 | * struct my_optional 10 | * { 11 | * void set_value(T t) { 12 | * if (_has_value) 13 | * _storage.value.~T(); 14 | * new (&_storage.value) T(t); 15 | * _has_value = true; 16 | * } 17 | * 18 | * private: 19 | * storage_for _storage; 20 | * bool _has_value = false; 21 | * }; 22 | */ 23 | template 24 | union storage_for { 25 | // empty ctor/dtor in order to not initialize value 26 | storage_for() {} 27 | ~storage_for() {} 28 | T value; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/clean-core/string.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | /** 8 | * utf8 null-terminated string with small-string-optimizations 9 | * 10 | * TODO: maybe a different SBO strategy 11 | * https://stackoverflow.com/questions/27631065/why-does-libcs-implementation-of-stdstring-take-up-3x-memory-as-libstdc/28003328#28003328 12 | * https://stackoverflow.com/questions/10315041/meaning-of-acronym-sso-in-the-context-of-stdstring/10319672#10319672 13 | * maybe via template arg the sbo capacity? 14 | */ 15 | using string = sbo_string<15>; 16 | 17 | static_assert(sizeof(string) == 4 * 8, "wrong architecture?"); 18 | } 19 | -------------------------------------------------------------------------------- /src/clean-core/string_stream.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // std::memcpy 4 | 5 | #include 6 | #include 7 | 8 | namespace cc 9 | { 10 | struct string_stream 11 | { 12 | public: // methods 13 | string_stream& operator<<(string_view sv) 14 | { 15 | if (sv.empty()) 16 | return *this; // memcpy must not receive nullptr (even for size 0) 17 | 18 | reserve(sv.size()); 19 | std::memcpy(m_curr, sv.data(), sv.size()); 20 | m_curr += sv.size(); 21 | return *this; 22 | } 23 | 24 | string_stream& operator<<(char c) 25 | { 26 | reserve(1); 27 | *m_curr = c; 28 | ++m_curr; 29 | return *this; 30 | } 31 | 32 | [[nodiscard]] string to_string() const 33 | { 34 | if (empty()) 35 | return {}; // memcpy must not be empty 36 | 37 | string s = string::uninitialized(size()); 38 | std::memcpy(s.data(), m_data, size()); 39 | return s; 40 | } 41 | 42 | /// reserve space for at least size more elements 43 | void reserve(size_t size) 44 | { 45 | size_t req_size = this->size() + size; 46 | if (req_size <= m_capacity) 47 | return; 48 | 49 | size_t new_cap = (m_capacity << 1) < req_size ? req_size : (m_capacity << 1); 50 | char* new_data = new char[new_cap]; 51 | if (!empty()) 52 | std::memcpy(new_data, m_data, this->size()); 53 | delete[] m_data; 54 | m_curr = m_curr - m_data + new_data; 55 | m_data = new_data; 56 | m_capacity = new_cap; 57 | } 58 | 59 | void clear() { m_curr = m_data; } 60 | 61 | public: // properties 62 | [[nodiscard]] size_t size() const noexcept { return m_curr - m_data; } 63 | [[nodiscard]] bool empty() const { return m_curr == m_data; } 64 | 65 | public: // ctor 66 | string_stream() = default; 67 | 68 | string_stream(string_stream const& rhs) 69 | { 70 | if (!rhs.empty()) 71 | { 72 | m_data = new char[rhs.size()]; 73 | std::memcpy(m_data, rhs.m_data, rhs.size()); 74 | m_curr = m_data + rhs.size(); 75 | m_capacity = rhs.size(); 76 | } 77 | } 78 | 79 | string_stream(string_stream&& rhs) noexcept 80 | { 81 | m_data = rhs.m_data; 82 | m_curr = rhs.m_curr; 83 | m_capacity = rhs.m_capacity; 84 | rhs.m_data = nullptr; 85 | rhs.m_curr = nullptr; 86 | rhs.m_capacity = 0; 87 | }; 88 | 89 | ~string_stream() { delete[] m_data; } 90 | 91 | public: // assignment 92 | string_stream& operator=(string_stream const& rhs) 93 | { 94 | if (this != &rhs) 95 | { 96 | if (m_capacity < rhs.size()) 97 | { 98 | delete[] m_data; 99 | m_data = new char[rhs.size()]; 100 | m_capacity = rhs.size(); 101 | } 102 | if (!rhs.empty()) 103 | std::memcpy(m_data, rhs.m_data, rhs.size()); 104 | m_curr = m_data + rhs.size(); 105 | } 106 | return *this; 107 | } 108 | 109 | string_stream& operator=(string_stream&& rhs) noexcept 110 | { 111 | delete[] m_data; 112 | m_data = rhs.m_data; 113 | m_curr = rhs.m_curr; 114 | m_capacity = rhs.m_capacity; 115 | rhs.m_data = nullptr; 116 | rhs.m_curr = nullptr; 117 | rhs.m_capacity = 0; 118 | return *this; 119 | } 120 | 121 | private: // member 122 | char* m_data = nullptr; 123 | char* m_curr = nullptr; 124 | size_t m_capacity = 0; 125 | }; 126 | 127 | inline string to_string(string_stream const& ss) { return ss.to_string(); } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/clean-core/stringhash.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cc 6 | { 7 | constexpr uint64_t stringhash(char const* s) 8 | { 9 | if (!s) 10 | return 0; 11 | 12 | uint64_t h = hash_combine(); 13 | while (*s) 14 | { 15 | h = hash_combine(h, uint64_t(*s)); 16 | s++; 17 | } 18 | return h; 19 | } 20 | 21 | constexpr uint64_t stringhash_n(char const* s, int n) 22 | { 23 | if (!s || n <= 0) 24 | return 0; 25 | 26 | uint64_t h = hash_combine(); 27 | while (*s && n > 0) 28 | { 29 | h = hash_combine(h, uint64_t(*s)); 30 | s++; 31 | n--; 32 | } 33 | return h; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/clean-core/temp_cstr.cc: -------------------------------------------------------------------------------- 1 | #include "temp_cstr.hh" 2 | 3 | #include 4 | 5 | cc::temp_cstr::temp_cstr(cc::string_view sv, cc::allocator* allocator) 6 | { 7 | CC_ASSERT(allocator && "allocator is currently required for string views"); 8 | init_from_allocator(sv, allocator); 9 | } 10 | 11 | cc::temp_cstr::temp_cstr(cc::string_view sv, span buffer, cc::allocator* fallback_allocator) 12 | { 13 | if (sv.size() < buffer.size()) // local buffer enough 14 | { 15 | std::memcpy(buffer.data(), sv.data(), sv.size()); 16 | buffer[sv.size()] = std::byte('\0'); 17 | _data = reinterpret_cast(buffer.data()); 18 | } 19 | else 20 | { 21 | CC_ASSERT(fallback_allocator && "no fallback allocator provided"); 22 | init_from_allocator(sv, fallback_allocator); 23 | } 24 | } 25 | 26 | cc::temp_cstr::temp_cstr(cc::string_view sv, span buffer, cc::allocator* fallback_allocator) 27 | : temp_cstr(sv, as_byte_span(buffer), fallback_allocator) 28 | { 29 | } 30 | 31 | void cc::temp_cstr::init_from_allocator(cc::string_view sv, cc::allocator* allocator) 32 | { 33 | CC_ASSERT(allocator); 34 | _alloc = allocator; 35 | auto data = allocator->new_array(sv.size() + 1); 36 | std::memcpy(data, sv.data(), sv.size()); 37 | data[sv.size()] = '\0'; 38 | _data = data; 39 | } 40 | -------------------------------------------------------------------------------- /src/clean-core/temp_cstr.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace cc 11 | { 12 | namespace detail 13 | { 14 | template 15 | struct has_c_str : std::false_type 16 | { 17 | }; 18 | template 19 | struct has_c_str(std::declval().c_str()))>> : std::true_type 20 | { 21 | }; 22 | } 23 | 24 | /** 25 | * A utility class for passing c strings to external APIs 26 | * 27 | * Usage: 28 | * 29 | * void external_fun(char const*); 30 | * 31 | * external_fun(cc::temp_cstr(sv)); 32 | * // where sv is a string_view or string-like object 33 | * 34 | * NOTE: temp_cstr behaves like a conditional view-type 35 | * it must not outlive its parameter 36 | * 37 | * TODO: if string_view becomes cstring-aware, we can save a copy 38 | */ 39 | struct temp_cstr 40 | { 41 | operator char const*() const { return _data; } 42 | 43 | /// currently, always allocates a temporary array for string_views 44 | /// (will change if string_views get c-string-aware) 45 | explicit temp_cstr(string_view sv, cc::allocator* allocator = cc::system_allocator); 46 | /// same as temp_cstr(string_view sv, cc::allocator*) but tries to use the provided buffer first 47 | explicit temp_cstr(string_view sv, span buffer, cc::allocator* fallback_allocator = cc::system_allocator); 48 | /// same as temp_cstr(string_view sv, cc::allocator*) but tries to use the provided buffer first 49 | explicit temp_cstr(string_view sv, span buffer, cc::allocator* fallback_allocator = cc::system_allocator); 50 | /// non-allocating version: just passes through the given c string 51 | explicit temp_cstr(char const* s) : _data(s) {} 52 | /// non-allocating version: objects that follow the "c_str()"-protocol 53 | template ::value> = true> 54 | explicit temp_cstr(String&& str) : _data(str.c_str()) 55 | { 56 | } 57 | 58 | temp_cstr(temp_cstr const&) = delete; 59 | temp_cstr(temp_cstr&&) = delete; 60 | temp_cstr& operator=(temp_cstr const&) = delete; 61 | temp_cstr& operator=(temp_cstr&&) = delete; 62 | 63 | ~temp_cstr() 64 | { 65 | // NOTE: the const_cast is fine as this case is only hit if we actually allocated the data from _alloc 66 | if (_data && _alloc) 67 | _alloc->delete_array(reinterpret_cast(const_cast(_data))); 68 | } 69 | 70 | private: 71 | char const* _data = nullptr; 72 | cc::allocator* _alloc = nullptr; 73 | 74 | void init_from_allocator(string_view sv, cc::allocator* allocator); 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /src/clean-core/type_id.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace cc 8 | { 9 | struct type_id_t 10 | { 11 | bool operator==(type_id_t const& rhs) const { return id == rhs.id; } 12 | bool operator!=(type_id_t const& rhs) const { return id != rhs.id; } 13 | bool operator<(type_id_t const& rhs) const { return id < rhs.id; } 14 | bool operator<=(type_id_t const& rhs) const { return id <= rhs.id; } 15 | bool operator>(type_id_t const& rhs) const { return id > rhs.id; } 16 | bool operator>=(type_id_t const& rhs) const { return id >= rhs.id; } 17 | 18 | type_id_t() = default; // no id is NOT the same as cc::type_id(); 19 | 20 | explicit operator bool() const { return id != nullptr; } 21 | 22 | private: 23 | void const* id = nullptr; 24 | type_id_t(void const* id) : id(id) {} 25 | 26 | template 27 | friend type_id_t type_id(); 28 | }; 29 | 30 | namespace detail 31 | { 32 | template 33 | struct type_id_impl 34 | { 35 | static constexpr int id = 0; 36 | }; 37 | } 38 | 39 | // provides a unique ID for the type 40 | // WARNING: Not consistent across DLLs! 41 | template 42 | type_id_t type_id() 43 | { 44 | return &detail::type_id_impl::id; 45 | } 46 | 47 | template <> 48 | struct hash 49 | { 50 | [[nodiscard]] uint64_t operator()(type_id_t const& v) const noexcept 51 | { 52 | static_assert(sizeof(v) == sizeof(uint64_t)); 53 | return reinterpret_cast(v); 54 | } 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /src/clean-core/unique_function.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace cc 9 | { 10 | /** 11 | * std::function replacement, not copyable, with allocator support 12 | * 13 | * NOTE: empty unique_functions can be created but not called! 14 | * 15 | * same codegen at execute as STL (minus exceptions): 16 | * https://godbolt.org/z/8fY3a5 17 | * https://quick-bench.com/q/sCMOpZNIacJcwOPcCYt0_95Efoc 18 | * 19 | * TODO: sbo_ and capped_ versions 20 | * TODO: deduction guides 21 | * TODO: member functions 22 | * TODO: use cc::alloc as default allocator 23 | */ 24 | template 25 | struct unique_function; 26 | template 27 | struct unique_function 28 | { 29 | public: 30 | unique_function() = default; 31 | unique_function(decltype(nullptr)) {} 32 | 33 | template 34 | unique_function(T&& callable, cc::allocator* alloc = cc::system_allocator) 35 | { 36 | using CallableT = std::decay_t; 37 | static_assert(std::is_invocable_r_v, "argument to cc::unique_function is not callable or has the wrong " 38 | "signature"); 39 | 40 | _func = [](void* ctx, Args&&... args) -> Result { return (*static_cast(ctx))(cc::forward(args)...); }; 41 | _deleter = [](void* ctx, cc::allocator* alloc) { alloc->delete_t(static_cast(ctx)); }; 42 | _alloc = alloc; 43 | _context = alloc->new_t(cc::forward(callable)); 44 | } 45 | 46 | Result operator()(Args... args) const 47 | { 48 | CC_ASSERT(_context && "invoked a null cc::unique_function"); 49 | return (*_func)(_context, cc::forward(args)...); 50 | } 51 | 52 | bool is_valid() const { return _context != nullptr; } 53 | explicit operator bool() const { return _context != nullptr; } 54 | 55 | ~unique_function() { _destroy(); } 56 | 57 | unique_function(unique_function&& rhs) noexcept : _func(rhs._func), _deleter(rhs._deleter), _alloc(rhs._alloc), _context(rhs._context) 58 | { 59 | rhs._context = nullptr; 60 | } 61 | 62 | unique_function& operator=(unique_function&& rhs) noexcept 63 | { 64 | _destroy(); 65 | _func = rhs._func; 66 | _deleter = rhs._deleter; 67 | _alloc = rhs._alloc; 68 | _context = rhs._context; 69 | rhs._context = nullptr; 70 | return *this; 71 | } 72 | 73 | private: 74 | cc::function_ptr _func = nullptr; 75 | cc::function_ptr _deleter = nullptr; 76 | cc::allocator* _alloc = nullptr; 77 | void* _context = nullptr; 78 | 79 | void _destroy() 80 | { 81 | if (_context != nullptr) 82 | { 83 | (*_deleter)(_context, _alloc); 84 | } 85 | } 86 | 87 | unique_function(unique_function const&) = delete; 88 | unique_function& operator=(unique_function const&) = delete; 89 | }; 90 | 91 | template 92 | struct unique_function 93 | { 94 | static_assert(always_false, "cc::unique_function expects a function signature type"); 95 | }; 96 | } 97 | -------------------------------------------------------------------------------- /src/clean-core/unique_ptr.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace cc 12 | { 13 | /** 14 | * - single-owner heap-allocated object 15 | * - move-only type 16 | * - always uses cc::alloc/cc::free 17 | * - can only be constructed via make_unique(...) 18 | * - no polymorphic behavior 19 | * 20 | * changes to std::unique_ptr: 21 | * - no custom deleter 22 | * - no allocators 23 | * - no operator< 24 | * - no operator bool 25 | * - no T[] 26 | * - no reset/release 27 | */ 28 | template 29 | struct unique_ptr 30 | { 31 | unique_ptr() = default; 32 | unique_ptr(std::nullptr_t) {} 33 | 34 | unique_ptr(unique_ptr const&) = delete; 35 | unique_ptr& operator=(unique_ptr const&) = delete; 36 | 37 | unique_ptr(unique_ptr&& rhs) noexcept 38 | { 39 | _ptr = rhs._ptr; 40 | rhs._ptr = nullptr; 41 | } 42 | unique_ptr& operator=(unique_ptr&& rhs) noexcept 43 | { 44 | static_assert(sizeof(T) > 0, "cannot delete incomplete class"); 45 | // self-move is reset 46 | if (_ptr) 47 | cc::free(_ptr); 48 | _ptr = rhs._ptr; 49 | rhs._ptr = nullptr; 50 | return *this; 51 | } 52 | 53 | ~unique_ptr() 54 | { 55 | static_assert(sizeof(T) > 0, "cannot delete incomplete class"); 56 | if (_ptr) 57 | cc::free(_ptr); 58 | } 59 | 60 | [[nodiscard]] T* get() const { return _ptr; } 61 | 62 | T* operator->() const 63 | { 64 | CC_ASSERT_NOT_NULL(_ptr); 65 | return _ptr; 66 | } 67 | T& operator*() const 68 | { 69 | CC_ASSERT_NOT_NULL(_ptr); 70 | return *_ptr; 71 | } 72 | 73 | bool operator==(unique_ptr const& rhs) const { return _ptr == rhs._ptr; } 74 | bool operator!=(unique_ptr const& rhs) const { return _ptr != rhs._ptr; } 75 | bool operator==(T const* rhs) const { return _ptr == rhs; } 76 | bool operator!=(T const* rhs) const { return _ptr != rhs; } 77 | 78 | template 79 | friend unique_ptr make_unique(Args&&... args); 80 | 81 | friend bool operator==(T const* lhs, unique_ptr const& rhs) { return lhs == rhs.get(); } 82 | friend bool operator!=(T const* lhs, unique_ptr const& rhs) { return lhs != rhs.get(); } 83 | friend bool operator==(std::nullptr_t, unique_ptr const& rhs) { return rhs.get() == nullptr; } 84 | friend bool operator!=(std::nullptr_t, unique_ptr const& rhs) { return rhs.get() != nullptr; } 85 | 86 | private: 87 | T* _ptr = nullptr; 88 | }; 89 | 90 | template 91 | struct unique_ptr 92 | { 93 | static_assert(always_false, "unique_ptr does not support arrays, use cc::vector or cc::array instead"); 94 | }; 95 | 96 | template 97 | [[nodiscard]] unique_ptr make_unique(Args&&... args) 98 | { 99 | unique_ptr p; 100 | p._ptr = cc::alloc(cc::forward(args)...); 101 | return p; 102 | } 103 | 104 | template 105 | struct hash> 106 | { 107 | [[nodiscard]] uint64_t operator()(unique_ptr const& v) const noexcept { return uint64_t(v.get()); } 108 | }; 109 | 110 | template 111 | struct less> 112 | { 113 | [[nodiscard]] bool operator()(unique_ptr const& a, unique_ptr const& b) const noexcept { return a.get() < b.get(); } 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /src/clean-core/utility.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // size_t 4 | 5 | #include 6 | 7 | namespace _external_cc_detail 8 | { 9 | template 10 | constexpr void do_swap(T& a, T& b, char) // priority 0 11 | { 12 | T tmp = static_cast(a); 13 | a = static_cast(b); 14 | b = static_cast(tmp); 15 | } 16 | template 17 | constexpr auto do_swap(T& a, T& b, int) -> decltype(swap(a, b)) // priority 1 18 | { 19 | swap(a, b); 20 | } 21 | } 22 | 23 | namespace cc 24 | { 25 | template 26 | [[nodiscard]] constexpr T const& max(T const& a, T const& b) 27 | { 28 | return (a < b) ? b : a; 29 | } 30 | 31 | template 32 | [[nodiscard]] constexpr T const& min(T const& a, T const& b) 33 | { 34 | return (a < b) ? a : b; 35 | } 36 | 37 | template 38 | [[nodiscard]] constexpr T const& clamp(T const& v, T const& lo, T const& hi) 39 | { 40 | CC_CONTRACT(!(hi < lo)); 41 | return (v < lo) ? lo : (hi < v) ? hi : v; 42 | } 43 | 44 | // optimal assembly for increment with custom wrap-around 45 | // https://godbolt.org/z/rTklbk 46 | // (assembly only tested for integral types) 47 | template 48 | [[nodiscard]] constexpr T wrapped_increment(T pos, T max) 49 | { 50 | ++pos; 51 | return pos == max ? 0 : pos; 52 | } 53 | 54 | template 55 | [[nodiscard]] constexpr T wrapped_decrement(T pos, T max) 56 | { 57 | CC_CONTRACT(max > 0); 58 | return pos == 0 ? max - 1 : pos - 1; 59 | } 60 | 61 | /// Divide ints and round up, nom > 0, denom > 0 62 | template 63 | [[nodiscard]] constexpr T int_div_ceil(T nom, T denom) 64 | { 65 | CC_CONTRACT(nom > 0 && denom > 0); 66 | return 1 + ((nom - 1) / denom); 67 | } 68 | 69 | /// Ceil a value to a multiple of a given value 70 | template 71 | [[nodiscard]] constexpr T int_ceil_to_multiple(T val, T multiple) 72 | { 73 | return ((val + multiple - 1) / multiple) * multiple; 74 | } 75 | 76 | [[maybe_unused]] struct 77 | { 78 | template 79 | constexpr void operator()(T& a, T& b) const 80 | { 81 | _external_cc_detail::do_swap(a, b, 0); 82 | } 83 | } constexpr swap; // implemented as functor so it cannot be found by ADL 84 | 85 | // straightforward swap that does not respect custom overloads 86 | template 87 | constexpr void simple_swap(T& a, T& b) 88 | { 89 | T tmp = static_cast(a); 90 | a = static_cast(b); 91 | b = static_cast(tmp); 92 | } 93 | 94 | /// increment the value (pointer or integer) to align to the given mask 95 | template 96 | [[nodiscard]] T align_up_masked(T value, size_t mask) 97 | { 98 | return (T)(((size_t)value + mask) & ~mask); 99 | } 100 | 101 | /// decrement the value (pointer or integer) to align to the given mask 102 | template 103 | [[nodiscard]] T align_down_masked(T value, size_t mask) 104 | { 105 | return (T)((size_t)value & ~mask); 106 | } 107 | 108 | /// increment the value (pointer or integer) to align at the given boundary 109 | /// use with ints: align_up(300, 16) = 304 110 | /// or with ptrs: align_up(0x5ACE, 256) = 0x5B00 111 | /// alignment must be a power of 2 112 | template 113 | [[nodiscard]] T align_up(T value, size_t alignment) 114 | { 115 | return align_up_masked(value, alignment - 1); 116 | } 117 | 118 | /// decrement the value (pointer or integer) to align at the given boundary 119 | /// use with ints: align_down(300, 16) = 288 120 | /// or with ptrs: align_down(0x5ACE, 256) = 0x5A00 121 | /// alignment must be a power of 2 122 | template 123 | [[nodiscard]] T align_down(T value, size_t alignment) 124 | { 125 | return align_down_masked(value, alignment - 1); 126 | } 127 | 128 | /// returns true if the value (pointer or integer) is aligned at the given boundary 129 | template 130 | [[nodiscard]] constexpr bool is_aligned(T value, size_t alignment) 131 | { 132 | return 0 == ((size_t)value & (alignment - 1)); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/clean-core/xxHash.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // NOTE: this header will be removed in a future version 4 | 5 | #include 6 | 7 | namespace cc 8 | { 9 | [[deprecated("use cc::make_hash_xxh3 directly. this header will be removed in the future. the new name is " 10 | "(2023-01-14)")]] inline uint64_t 11 | hash_xxh3(cc::span data, uint64_t seed) 12 | { 13 | return cc::make_hash_xxh3(data, seed); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | 3 | file(GLOB_RECURSE SOURCES 4 | "*.cc" 5 | "*.hh" 6 | ) 7 | 8 | add_arcana_test(tests-clean-core "${SOURCES}") 9 | 10 | target_link_libraries(tests-clean-core PUBLIC 11 | clean-core 12 | typed-geometry 13 | rich-log 14 | ) 15 | 16 | if (TARGET clean-ranges) 17 | target_link_libraries(tests-clean-core PUBLIC clean-ranges) 18 | target_compile_definitions(tests-clean-core PUBLIC HAS_CLEAN_RANGES) 19 | endif() 20 | 21 | if (TARGET ctracer) 22 | target_link_libraries(tests-clean-core PUBLIC ctracer) 23 | target_compile_definitions(tests-clean-core PUBLIC HAS_CTRACER) 24 | endif() 25 | -------------------------------------------------------------------------------- /tests/any_of.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | TEST("cc::any_of") 7 | { 8 | CHECK(1 == cc::any_of(1, 2, 3)); 9 | CHECK(2 == cc::any_of(1, 2, 3)); 10 | CHECK(3 == cc::any_of(1, 2, 3)); 11 | 12 | CHECK(4 != cc::any_of(1, 2, 3)); 13 | 14 | CHECK(5 == cc::any_of(1, 2, 3, 4, 5, 6)); 15 | 16 | cc::vector v = {1, 2, 3}; 17 | 18 | static_assert(cc::is_any_range>); 19 | 20 | CHECK(1 == cc::any_of(v)); 21 | CHECK(2 == cc::any_of(v)); 22 | CHECK(3 == cc::any_of(v)); 23 | 24 | CHECK(4 != cc::any_of(v)); 25 | } 26 | -------------------------------------------------------------------------------- /tests/base64.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | FUZZ_TEST("cc::base64")(tg::rng& rng) 8 | { 9 | auto l = uniform(rng, 0, 50); 10 | auto bytes = cc::vector::defaulted(l); 11 | for (auto& c : bytes) 12 | c = (std::byte)uniform(rng, 0, 255); 13 | 14 | auto s = cc::base64_encode(bytes); 15 | auto bytes2 = cc::base64_decode(s); 16 | 17 | CHECK(bytes == bytes2); 18 | } 19 | -------------------------------------------------------------------------------- /tests/chunked_buffer.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if HAS_CLEAN_RANGES 4 | #include 5 | #endif 6 | 7 | #include 8 | #include 9 | 10 | #if HAS_CLEAN_RANGES 11 | TEST("cc::chunked_buffer basics") 12 | { 13 | cc::chunked_buffer b; 14 | b.set_chunk_size(100); 15 | 16 | CHECK(b.size() == 0); 17 | CHECK(cr::count(b.chunks()) == 0); 18 | 19 | for ([[maybe_unused]] auto c : b.chunks()) 20 | CHECK(false); // should not be capped 21 | 22 | b.push_back(1); 23 | CHECK(b.size() == 1); 24 | CHECK(cr::count(b.chunks()) == 1); 25 | for (auto c : b.chunks()) 26 | CHECK(c.size() == 1); 27 | 28 | for (auto i = 0; i < 10; ++i) 29 | b.push_back(i); 30 | CHECK(b.size() == 11); 31 | CHECK(cr::count(b.chunks()) == 1); 32 | for (auto c : b.chunks()) 33 | CHECK(c.size() == 11); 34 | 35 | for (auto i = 0; i < 100; ++i) 36 | b.push_back(i); 37 | CHECK(b.size() == 111); 38 | CHECK(cr::count(b.chunks()) == 2); 39 | CHECK(cr::to(b.chunks(), [](auto c) { return c.size(); }) == nx::range{100, 11}); 40 | } 41 | #endif 42 | 43 | MONTE_CARLO_TEST("cc::chunked_buffer mct") 44 | { 45 | addOp("gen", [](tg::rng& rng) { return uniform(rng, -10, 10) * 2; }); 46 | 47 | addOp("ctor", [] { return cc::chunked_buffer(); }); 48 | addOp("ctor", [] { return cc::vector(); }); 49 | addOp("size", [](cc::chunked_buffer const& buffer) { return buffer.size(); }); 50 | addOp("size", [](cc::vector const& buffer) { return buffer.size(); }); 51 | addOp("push_back", [](cc::chunked_buffer& buffer, int v) { buffer.push_back(v); }); 52 | addOp("push_back", [](cc::vector& buffer, int v) { buffer.push_back(v); }); 53 | addOp("push_back_many", 54 | [](cc::chunked_buffer& buffer, int v) 55 | { 56 | for (auto i = 0; i < 2345; ++i) 57 | buffer.push_back(v + i); 58 | }); 59 | addOp("push_back_many", 60 | [](cc::vector& buffer, int v) 61 | { 62 | for (auto i = 0; i < 2345; ++i) 63 | buffer.push_back(v + i); 64 | }); 65 | 66 | testEquivalence( 67 | [](cc::vector const& a, cc::chunked_buffer const& b) 68 | { 69 | cc::vector rhs; 70 | cc::vector rhs2; 71 | 72 | b.for_each_chunk([&](auto chunk) { rhs.push_back_range(chunk); }); 73 | 74 | for (auto c : b.chunks()) 75 | rhs2.push_back_range(c); 76 | 77 | REQUIRE(a == rhs); 78 | REQUIRE(a == rhs2); 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /tests/clamped_span.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | TEST("cc::clamped_span") 7 | { 8 | auto vec = cc::vector{1, 2, 3, 4, 5}; 9 | auto ds = cc::clamped_span(vec); 10 | CHECK(ds[0] == 1); 11 | CHECK(ds[-1] == 1); 12 | CHECK(ds[-2] == 1); 13 | CHECK(ds[5] == 5); 14 | CHECK(ds[6] == 5); 15 | } 16 | -------------------------------------------------------------------------------- /tests/flags.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace 9 | { 10 | enum class F 11 | { 12 | a, 13 | b, 14 | c 15 | }; 16 | CC_FLAGS_ENUM(F); 17 | 18 | namespace E 19 | { 20 | enum _t 21 | { 22 | a, 23 | b, 24 | c 25 | }; 26 | CC_FLAGS_ENUM(_t); 27 | 28 | [[maybe_unused]] cc::string to_string(E::_t f) 29 | { 30 | switch (f) 31 | { 32 | case E::a: 33 | return "a"; 34 | case E::b: 35 | return "b"; 36 | case E::c: 37 | return "c"; 38 | default: 39 | return "invalid"; 40 | } 41 | } 42 | } 43 | 44 | [[maybe_unused]] cc::string to_string(F f) 45 | { 46 | switch (f) 47 | { 48 | case F::a: 49 | return "a"; 50 | case F::b: 51 | return "b"; 52 | case F::c: 53 | return "c"; 54 | default: 55 | return "invalid"; 56 | } 57 | } 58 | } 59 | 60 | TEST("cc::flags enum class") 61 | { 62 | cc::flags f; 63 | CHECK(!f.has_any()); 64 | CHECK(!f.has(F::b)); 65 | 66 | f = F::c; 67 | CHECK(f.has_any()); 68 | CHECK(f == F::c); 69 | CHECK(f != F::b); 70 | 71 | f = {F::a, F::c}; 72 | CHECK((f & F::a)); 73 | CHECK(!(f & F::b)); 74 | CHECK((f & F::c)); 75 | 76 | auto f2 = cc::flags(F::a); 77 | f2 = cc::flags(F::b, f); 78 | CHECK(f2 == cc::make_flags(F::a, F::b, F::c)); 79 | 80 | CHECK(to_string(f2) == "{a, b, c}"); 81 | 82 | f = F::b; 83 | f2 = {F::a, F::b}; 84 | CHECK(f.has_any_of(f2)); 85 | CHECK(!f.has_all_of(f2)); 86 | 87 | CHECK(f.is_single()); 88 | CHECK(f.single() == F::b); 89 | CHECK(!f2.is_single()); 90 | 91 | auto f3 = F::c | F::b; 92 | CHECK((f3 & f2) == F::b); 93 | 94 | for (auto e : f) 95 | CHECK(e == F::b); 96 | for (auto e : f2) 97 | CHECK(e == cc::any_of(F::a, F::b)); 98 | for (auto e : f3) 99 | CHECK(e == cc::any_of(F::b, F::c)); 100 | 101 | auto cnt = [](auto&& f) { 102 | auto c = 0; 103 | for (auto e : f) 104 | { 105 | ++c; 106 | (void)e; 107 | } 108 | return c; 109 | }; 110 | 111 | CHECK(cnt(f) == 1); 112 | CHECK(cnt(f2) == 2); 113 | CHECK(cnt(f2) == 2); 114 | 115 | f2 = cc::no_flags; 116 | CHECK(cnt(f2) == 0); 117 | } 118 | 119 | TEST("cc::flags enum") 120 | { 121 | cc::flags f; 122 | CHECK(!f.has_any()); 123 | CHECK(!f.has(E::b)); 124 | 125 | f = E::c; 126 | CHECK(f.has_any()); 127 | CHECK(f == E::c); 128 | CHECK(f != E::b); 129 | 130 | f = {E::a, E::c}; 131 | CHECK((f & E::a)); 132 | CHECK(!(f & E::b)); 133 | CHECK((f & E::c)); 134 | 135 | auto f2 = cc::flags(E::a); 136 | f2 = cc::flags(E::b, f); 137 | CHECK(f2 == cc::make_flags(E::a, E::b, E::c)); 138 | 139 | CHECK(to_string(f2) == "{a, b, c}"); 140 | 141 | f = E::b; 142 | f2 = {E::a, E::b}; 143 | CHECK(f.has_any_of(f2)); 144 | CHECK(!f.has_all_of(f2)); 145 | 146 | CHECK(f.is_single()); 147 | CHECK(f.single() == E::b); 148 | CHECK(!f2.is_single()); 149 | 150 | auto f3 = cc::make_flags(E::c, E::b); 151 | CHECK((f3 & f2) == E::b); 152 | 153 | for (auto e : f) 154 | CHECK(e == E::b); 155 | for (auto e : f2) 156 | CHECK(e == cc::any_of(E::a, E::b)); 157 | for (auto e : f3) 158 | CHECK(e == cc::any_of(E::b, E::c)); 159 | 160 | auto cnt = [](auto&& f) { 161 | auto c = 0; 162 | for (auto e : f) 163 | { 164 | ++c; 165 | (void)e; 166 | } 167 | return c; 168 | }; 169 | 170 | CHECK(cnt(f) == 1); 171 | CHECK(cnt(f2) == 2); 172 | CHECK(cnt(f3) == 2); 173 | 174 | f2 = cc::no_flags; 175 | CHECK(cnt(f2) == 0); 176 | } 177 | -------------------------------------------------------------------------------- /tests/format.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST("cc::format basics") 6 | { 7 | CHECK(cc::format("{}", 17) == "17"); 8 | CHECK(cc::format("{} + {} = {}", 1, 2, 3) == "1 + 2 = 3"); 9 | CHECK(cc::format("%s + %d = %x", 1, 2, 3) == "1 + 2 = 3"); 10 | CHECK(cc::format("{}{}{}", 1, 2, 3) == "123"); 11 | CHECK(cc::format("%s%d%x", 1, 2, 3) == "123"); 12 | 13 | // reordering 14 | CHECK(cc::format("{2} - {0} = {1}", 1, 2, 3) == "3 - 1 = 2"); 15 | CHECK(cc::format("{1} {0}!", "World", "Hello") == "Hello World!"); 16 | 17 | // escaping 18 | CHECK(cc::format("this {{}} is used for args like {}", "this") == "this {} is used for args like this"); 19 | CHECK(cc::format("look ma, I can write %% and {{ and }}") == "look ma, I can write % and { and }"); 20 | 21 | // format strings 22 | CHECK(cc::format("{:4}", 12) == " 12"); 23 | CHECK(cc::format("%4d", 12) == " 12"); 24 | CHECK(cc::format("{:.2f}", 1.2345) == "1.23"); 25 | CHECK(cc::format("%.2f", 1.2345) == "1.23"); 26 | 27 | // decorators? 28 | // cc::format("{}", cc::fmt_join(my_vec, ", "))) 29 | } 30 | 31 | TEST("cc::format opinionated") 32 | { 33 | CHECK(cc::formatf("{} %s", "x") == "{} x"); 34 | CHECK(cc::formatp("{} %s", "x") == "x %s"); 35 | } 36 | 37 | TEST("cc::format string format") 38 | { 39 | CHECK(cc::format("%s", "hi") == "hi"); 40 | CHECK(cc::format("%5s", "hi") == " hi"); 41 | CHECK(cc::format("%<5s", "hi") == "hi "); 42 | CHECK(cc::format("%^4s", "hi") == " hi "); 43 | } 44 | -------------------------------------------------------------------------------- /tests/forward_list.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | TEST("cc::forward_list") 7 | { 8 | cc::forward_list l; 9 | 10 | CHECK(l.empty()); 11 | CHECK(l.size() == 0); 12 | 13 | l.emplace_front(3); 14 | 15 | CHECK(!l.empty()); 16 | CHECK(l.size() == 1); 17 | 18 | l.emplace_front(1); 19 | l.emplace_front(1); 20 | 21 | CHECK(!l.empty()); 22 | CHECK(l.size() == 3); 23 | 24 | for (auto i : l) 25 | CHECK(i == cc::any_of(1, 3)); 26 | 27 | auto l2 = l; 28 | 29 | CHECK(l2.size() == 3); 30 | for (auto i : l2) 31 | CHECK(i == cc::any_of(1, 3)); 32 | } 33 | -------------------------------------------------------------------------------- /tests/from_string.cc: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | TEST("cc::from_string") 10 | { 11 | int32_t v; 12 | CHECK(cc::from_string("123", v)); 13 | CHECK(v == 123); 14 | CHECK(!cc::from_string("123 trailing text", v)); 15 | } 16 | 17 | namespace 18 | { 19 | template 20 | void check_range(tg::rng& rng, T min, T max) 21 | { 22 | T v0 = min; 23 | T v1 = min; 24 | for (auto i = 0; i < 100; ++i) 25 | { 26 | v0 = uniform(rng, min, max); 27 | auto s = cc::to_string(v0); 28 | auto ok = cc::from_string(s, v1); 29 | CHECK(ok); 30 | CHECK(v0 == v1); 31 | } 32 | }; 33 | 34 | template 35 | void check_range_near(tg::rng& rng, T min, T max, T tol) 36 | { 37 | T v0 = min; 38 | T v1 = min; 39 | for (auto i = 0; i < 100; ++i) 40 | { 41 | v0 = uniform(rng, min, max); 42 | auto s = cc::to_string(v0); 43 | auto ok = cc::from_string(s, v1); 44 | CHECK(ok); 45 | CHECK(v0 == nx::approx(v1).abs(tol)); 46 | } 47 | }; 48 | } // anon namespace 49 | 50 | FUZZ_TEST("cc::from_string fuzz")(tg::rng& rng) 51 | { 52 | check_range(rng, -100, 100); 53 | check_range(rng, -100000000000, 100000000000); 54 | check_range(rng, 5, 100); 55 | check_range(rng, 5, 100); 56 | check_range_near(rng, -100., 100., 0.01); 57 | check_range_near(rng, -100.f, 100.f, 0.01f); 58 | } 59 | -------------------------------------------------------------------------------- /tests/function_ref.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace 6 | { 7 | int test_fun(int a, int b) { return a + b; } 8 | 9 | struct test_callable 10 | { 11 | int x = 0; 12 | int operator()(int a, int b) const { return a * b + x; } 13 | }; 14 | } 15 | 16 | TEST("cc::function_ref") 17 | { 18 | cc::function_ref f = test_fun; 19 | 20 | CHECK(f(1, 2) == 3); 21 | 22 | f = &test_fun; 23 | CHECK(f(1, 2) == 3); 24 | 25 | int x = 7; 26 | auto l = [&](int a, int b) { return a + b + x; }; 27 | 28 | f = l; 29 | CHECK(f(1, 2) == 10); 30 | 31 | x = 5; 32 | CHECK(f(1, 2) == 8); 33 | 34 | f = [](int a, int b) { return a * b; }; // careful, lambda lifetime 35 | CHECK(f(3, 4) == 12); 36 | 37 | test_callable tc; 38 | f = tc; 39 | CHECK(f(2, 3) == 6); 40 | tc.x = 10; 41 | CHECK(f(2, 3) == 16); 42 | 43 | test_callable const ctc = {9}; 44 | f = ctc; 45 | CHECK(f(2, 3) == 15); 46 | } 47 | -------------------------------------------------------------------------------- /tests/functors.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | TEST("void functor") 7 | { 8 | auto test = [](auto f) 9 | { 10 | f(); 11 | f(1); 12 | f(1, true); 13 | f(1, true, false, "hi", cc::vector{}); 14 | }; 15 | test(cc::void_function{}); 16 | 17 | CHECK(true); // only checks if compiles 18 | } 19 | 20 | TEST("id functor") 21 | { 22 | auto test = [](auto f) 23 | { 24 | CHECK(f(1) == 1); 25 | CHECK(f(true) == true); 26 | CHECK(f(cc::vector{1, 2, 3}) == cc::vector{1, 2, 3}); 27 | }; 28 | 29 | test(cc::identity_function{}); 30 | } 31 | 32 | TEST("constant functor") 33 | { 34 | auto test = [](auto f) 35 | { 36 | CHECK(f() == 17); 37 | CHECK(f(1) == 17); 38 | CHECK(f(1, true) == 17); 39 | CHECK(f(1, true, false, "hi", cc::vector{}) == 17); 40 | }; 41 | test(cc::constant_function<17>{}); 42 | } 43 | 44 | TEST("projection functor") 45 | { 46 | auto test = [](auto f0, auto f1, auto f2) 47 | { 48 | CHECK(f0(1) == 1); 49 | CHECK(f0(true, 1) == true); 50 | CHECK(f0(cc::vector{1, 2, 3}, 1, true, false, "hi", cc::vector{}) == cc::vector{1, 2, 3}); 51 | 52 | CHECK(f1(true, 1) == 1); 53 | CHECK(f1(cc::vector{1, 2, 3}, false, "hi", cc::vector{}) == false); 54 | 55 | CHECK(f2(true, 1, 'c') == 'c'); 56 | CHECK(f2(1, 2, cc::vector{1, 2, 3}, false, cc::vector{}) == cc::vector{1, 2, 3}); 57 | }; 58 | test(cc::projection_function<0>{}, cc::projection_function<1>{}, cc::projection_function<2>{}); 59 | } 60 | -------------------------------------------------------------------------------- /tests/hash.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST("cc::hash") 6 | { 7 | struct foo 8 | { 9 | int f = 6; 10 | bool b = true; 11 | }; 12 | struct trivial_foo 13 | { 14 | int a = 2; 15 | int c = 4; 16 | }; 17 | 18 | static_assert(cc::can_hash); 19 | static_assert(cc::can_hash); 20 | static_assert(cc::can_hash); 21 | static_assert(cc::can_hash); 22 | static_assert(!cc::can_hash); 23 | 24 | CHECK(true); // silence warning 25 | } 26 | -------------------------------------------------------------------------------- /tests/indices_of.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | TEST("cc::indices_of") 9 | { 10 | { 11 | cc::vector v = {10, 7, 3}; 12 | CHECK(cc::indices_of(v) == nx::range{0, 1, 2}); 13 | } 14 | { 15 | cc::array v = {10, 7, 3}; 16 | CHECK(cc::indices_of(v) == nx::range{0, 1, 2}); 17 | } 18 | { 19 | cc::array v = {10, 7, 3}; 20 | CHECK(cc::indices_of(v) == nx::range{0, 1, 2}); 21 | } 22 | { 23 | int v[] = {10, 7, 3}; 24 | CHECK(cc::indices_of(v) == nx::range{0, 1, 2}); 25 | } 26 | 27 | // NOTE: currently not supported because indices_of is based on collection size type (index type is harder to infer) 28 | // { 29 | // enum class custom_idx : int; 30 | // struct vec_traits 31 | // { 32 | // using element_t = bool; 33 | // using index_t = custom_idx; 34 | // }; 35 | // cc::vector_ex v = {true, false, true}; 36 | // CHECK(cc::indices_of(v) == nx::range{custom_idx(0), custom_idx(1), custom_idx(2)}); 37 | // } 38 | } 39 | 40 | TEST("cc::indices_of reversed") 41 | { 42 | cc::vector v = {10, 7, 3}; 43 | CHECK(cc::indices_of(v).reversed() == nx::range{2, 1, 0}); 44 | CHECK(cc::indices_of(v).reversed().reversed() == nx::range{0, 1, 2}); 45 | } 46 | 47 | TEST("cc::indices_of empty") 48 | { 49 | cc::vector v; 50 | CHECK(cc::indices_of(v) == nx::range{}); 51 | CHECK(cc::indices_of(v).reversed() == nx::range{}); 52 | CHECK(cc::indices_of(v).reversed().reversed() == nx::range{}); 53 | } 54 | -------------------------------------------------------------------------------- /tests/invoke.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace 6 | { 7 | int plus_two(int x) { return x + 2; } 8 | int& ref_id(int& x) { return x; } 9 | } 10 | 11 | TEST("cc::invoke") 12 | { 13 | int x = 10; 14 | auto lambda_f = [&](int a) { return a + x; }; 15 | 16 | struct foo 17 | { 18 | int v; 19 | int& get_v() { return v; } 20 | int bar() { return v + 1; } 21 | }; 22 | 23 | CHECK(cc::invoke(lambda_f, 7) == 17); 24 | CHECK(cc::invoke(plus_two, 7) == 9); 25 | CHECK(cc::invoke(ref_id, x) == 10); 26 | cc::invoke(ref_id, x) = 4; 27 | CHECK(x == 4); 28 | 29 | foo f; 30 | f.v = 9; 31 | CHECK(cc::invoke(&foo::v, f) == 9); 32 | 33 | cc::invoke(&foo::v, f) = 5; 34 | CHECK(f.v == 5); 35 | 36 | CHECK(cc::invoke(&foo::bar, f) == 6); 37 | CHECK(cc::invoke(&foo::get_v, f) == 5); 38 | 39 | cc::invoke(&foo::get_v, f) = 11; 40 | CHECK(f.v == 11); 41 | } 42 | -------------------------------------------------------------------------------- /tests/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char** argv) { return nx::run(argc, argv); } 4 | -------------------------------------------------------------------------------- /tests/optional.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | TEST("cc::optional basics") 10 | { 11 | cc::optional v; 12 | CHECK(!v.has_value()); 13 | CHECK(v != 0); 14 | 15 | v = 7; 16 | CHECK(v == 7); 17 | CHECK(v != 8); 18 | CHECK(v != cc::nullopt); 19 | 20 | auto vv = cc::make_optional(13); 21 | CHECK(v != vv); 22 | vv = 7; 23 | CHECK(v == vv); 24 | 25 | auto vf = cc::make_optional(7.f); 26 | CHECK(v == vf); 27 | vf = 8; 28 | CHECK(v != vf); 29 | 30 | v = vf; 31 | CHECK(v == 8); 32 | 33 | v = {}; 34 | CHECK(!v.has_value()); 35 | 36 | v = 3; 37 | CHECK(v.has_value()); 38 | 39 | v = cc::nullopt; 40 | CHECK(!v.has_value()); 41 | } 42 | 43 | TEST("cc::optional string") 44 | { 45 | cc::optional v; 46 | CHECK(!v.has_value()); 47 | 48 | v = "hello"; 49 | CHECK(v == "hello"); 50 | 51 | v = {}; 52 | CHECK(v != "hello"); 53 | CHECK(!v.has_value()); 54 | } 55 | 56 | TEST("cc::optional map") 57 | { 58 | cc::optional i = 17; 59 | CHECK(i == 17); 60 | 61 | i = i.map([](int x) { return -x * 2; }); 62 | CHECK(i == -34); 63 | 64 | auto iabs = [](int x) { return tg::abs(x); }; 65 | i = i.map(iabs); 66 | CHECK(i == 34); 67 | 68 | auto s = i.map([](int x) { return cc::to_string(x); }); 69 | CHECK(s == "34"); 70 | 71 | i = cc::nullopt; 72 | CHECK(!i.has_value()); 73 | 74 | s = i.map([](int x) { return cc::to_string(x); }); 75 | CHECK(!s.has_value()); 76 | 77 | i = i.map(iabs); 78 | CHECK(!i.has_value()); 79 | 80 | i = 123; 81 | s = i.map([](int x) { return cc::to_string(x); }); 82 | CHECK(s == "123"); 83 | 84 | i = s.map(&cc::string::size); 85 | CHECK(i == 3); 86 | 87 | cc::optional p = tg::pos3(1, 2, 3); 88 | i = p.map(&tg::pos3::y); 89 | CHECK(i == 2); 90 | } 91 | 92 | TEST("cc::optional transform") 93 | { 94 | cc::optional i = 17; 95 | CHECK(i == 17); 96 | 97 | i.transform([](int& x) { x *= 2; }); 98 | CHECK(i == 34); 99 | 100 | i = cc::nullopt; 101 | CHECK(!i.has_value()); 102 | 103 | i.transform([](int& x) { x *= 2; }); 104 | CHECK(!i.has_value()); 105 | 106 | cc::optional s = cc::string("hello"); 107 | CHECK(s == "hello"); 108 | 109 | s.transform(&cc::string::clear); 110 | CHECK(s == ""); 111 | 112 | s = cc::nullopt; 113 | CHECK(!s.has_value()); 114 | 115 | s.transform(&cc::string::clear); 116 | CHECK(!s.has_value()); 117 | } 118 | -------------------------------------------------------------------------------- /tests/result.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | TEST("cc::result basics") 8 | { 9 | using result_t = cc::result, cc::string>; 10 | 11 | result_t res; 12 | CHECK(res.is_error()); 13 | CHECK(!res.is_value()); 14 | CHECK(res.is_error("")); 15 | CHECK(!res.is_error("error")); 16 | CHECK(res.error() == ""); 17 | 18 | res = cc::string("error"); 19 | CHECK(res.is_error()); 20 | CHECK(!res.is_value()); 21 | CHECK(!res.is_error("")); 22 | CHECK(res.is_error("error")); 23 | CHECK(res.error() == "error"); 24 | 25 | res = cc::make_unique(17); 26 | CHECK(!res.is_error()); 27 | CHECK(res.is_value()); 28 | CHECK(*res.value() == 17); 29 | } 30 | -------------------------------------------------------------------------------- /tests/set.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | TEST("cc::set") 9 | { 10 | bool b; 11 | cc::set s; 12 | 13 | static_assert(cc::is_range, int const>); 14 | static_assert(cc::is_range&, int const>); 15 | 16 | CHECK(s.empty()); 17 | CHECK(s.size() == 0); 18 | CHECK(!s.contains(3)); 19 | 20 | s.add(3); 21 | CHECK(s.size() == 1); 22 | CHECK(s.contains(3)); 23 | CHECK(!s.contains(4)); 24 | 25 | s.add(3); 26 | CHECK(s.size() == 1); 27 | 28 | s.add(5); 29 | CHECK(s.size() == 2); 30 | 31 | b = s.remove(7); 32 | CHECK(!b); 33 | CHECK(s.size() == 2); 34 | 35 | b = s.remove(3); 36 | CHECK(b); 37 | CHECK(s.size() == 1); 38 | CHECK(!s.contains(3)); 39 | 40 | b = s.remove(5); 41 | CHECK(b); 42 | CHECK(s.size() == 0); 43 | CHECK(!s.contains(5)); 44 | 45 | s = {1, 2, 3, 2}; 46 | CHECK(s.size() == 3); 47 | CHECK(s.contains(2)); 48 | 49 | auto s2 = s; // copy set 50 | CHECK(s2.size() == 3); 51 | CHECK(s2.contains(1)); 52 | 53 | auto cnt = 0; 54 | for (auto i : s) 55 | { 56 | ++cnt; 57 | CHECK(i == cc::any_of(1, 2, 3)); 58 | CHECK(i >= 1); 59 | CHECK(i <= 3); 60 | } 61 | CHECK(cnt == 3); 62 | 63 | s |= 2; 64 | CHECK(s.size() == 3); 65 | 66 | s |= 5; 67 | CHECK(s.size() == 4); 68 | 69 | s |= {1, 3, 5, 7}; 70 | CHECK(s.size() == 5); 71 | 72 | s = {1, 3, 5}; 73 | s2 = {5, 1, -3}; 74 | CHECK(s.size() == 3); 75 | CHECK(s2.size() == 3); 76 | 77 | s = s | s2; 78 | CHECK(s.size() == 4); 79 | for (auto i : s) 80 | CHECK(i == cc::any_of(-3, 1, 3, 5)); 81 | } 82 | -------------------------------------------------------------------------------- /tests/special_types.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct regular_type 4 | { 5 | }; 6 | 7 | struct move_only_type 8 | { 9 | int value; 10 | move_only_type() = default; 11 | explicit move_only_type(int i) : value(i) {} 12 | 13 | move_only_type(move_only_type const&) = delete; 14 | move_only_type(move_only_type&&) = default; 15 | move_only_type& operator=(move_only_type const&) = delete; 16 | move_only_type& operator=(move_only_type&&) = default; 17 | 18 | bool operator==(move_only_type const& rhs) const { return value == rhs.value; } 19 | bool operator!=(move_only_type const& rhs) const { return value != rhs.value; } 20 | }; 21 | 22 | struct no_default_type 23 | { 24 | no_default_type() = delete; 25 | explicit no_default_type(int i) : value(i) {} 26 | 27 | int value; 28 | 29 | bool operator==(no_default_type const& rhs) const { return value == rhs.value; } 30 | bool operator!=(no_default_type const& rhs) const { return value != rhs.value; } 31 | }; 32 | -------------------------------------------------------------------------------- /tests/strided_span.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #ifdef HAS_CLEAN_RANGES 7 | #include 8 | #endif 9 | 10 | TEST("cc::strided_span (span equivalent)") 11 | { 12 | cc::vector v = {1, 2, 3}; 13 | 14 | auto s = cc::strided_span(v); 15 | CHECK(s.size() == 3); 16 | CHECK(s[0] == 1); 17 | CHECK(s[2] == 3); 18 | 19 | s = s.subspan(1, 2); 20 | CHECK(s.size() == 2); 21 | CHECK(s[0] == 2); 22 | CHECK(s[1] == 3); 23 | 24 | int va[] = {3, 2, 5, 6}; 25 | s = cc::strided_span(va); 26 | CHECK(s.size() == 4); 27 | CHECK(s[0] == 3); 28 | CHECK(s[3] == 6); 29 | s[1] += 2; 30 | CHECK(va[1] == 4); 31 | 32 | int x = 8; 33 | s = cc::strided_span(x); 34 | CHECK(s.size() == 1); 35 | CHECK(s[0] == 8); 36 | x = 9; 37 | CHECK(s[0] == 9); 38 | 39 | s = {v.data(), v.size()}; 40 | CHECK(s.size() == 3); 41 | CHECK(s[0] == 1); 42 | CHECK(s[2] == 3); 43 | 44 | s = s.subspan(2); 45 | CHECK(s.size() == 1); 46 | CHECK(s[0] == 3); 47 | 48 | s = s.subspan(1); 49 | CHECK(s.size() == 0); 50 | CHECK(s.empty()); 51 | 52 | s = v; 53 | CHECK(s.size() == 3); 54 | 55 | #ifdef HAS_CLEAN_RANGES 56 | CHECK(cr::range(s) == cc::vector{1, 2, 3}); 57 | CHECK(cr::range(s.reversed()) == cc::vector{3, 2, 1}); 58 | 59 | s = s.first(2); 60 | CHECK(cr::range(s) == cc::vector{1, 2}); 61 | 62 | s = cc::strided_span(v).last(2); 63 | CHECK(cr::range(s) == cc::vector{2, 3}); 64 | #endif 65 | } 66 | -------------------------------------------------------------------------------- /tests/string_stream.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST("cc::string_stream") 6 | { 7 | cc::string_stream ss; 8 | CHECK(ss.empty()); 9 | ss << ""; 10 | CHECK(ss.empty()); 11 | ss << "foo"; 12 | CHECK(ss.size() == 3); 13 | CHECK(ss.to_string() == "foo"); 14 | ss << "bar"; 15 | CHECK(ss.size() == 6); 16 | CHECK(ss.to_string() == "foobar"); 17 | ss.clear(); 18 | CHECK(ss.empty()); 19 | 20 | cc::string_stream ss2; 21 | ss2 = ss; 22 | CHECK(ss.empty()); 23 | CHECK(ss2.empty()); 24 | ss << "foo"; 25 | ss2 = cc::move(ss); 26 | CHECK(ss.empty()); 27 | CHECK(ss2.to_string() == "foo"); 28 | 29 | cc::string_stream ss3(ss2); 30 | CHECK(ss3.to_string() == "foo"); 31 | 32 | cc::string_stream ss4(cc::move(ss2)); 33 | CHECK(ss4.to_string() == "foo"); 34 | CHECK(ss2.empty()); 35 | 36 | ss << "foo" 37 | << "bar"; 38 | CHECK(ss.to_string() == "foobar"); 39 | } 40 | -------------------------------------------------------------------------------- /tests/structured_bindings.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | TEST("structured bindings") 8 | { 9 | { 10 | cc::array a = {3, 5, 7}; 11 | auto [i0, i1, i2] = a; 12 | 13 | CHECK(i0 == 3); 14 | CHECK(i1 == 5); 15 | CHECK(i2 == 7); 16 | } 17 | 18 | { 19 | cc::tuple t; 20 | 21 | t.get<0>() = 1; 22 | t.get<1>() = 3.25f; 23 | t.get<2>() = 'c'; 24 | 25 | auto [i0, i1, i2] = t; 26 | 27 | CHECK(i0 == 1); 28 | CHECK(i1 == 3.25f); 29 | CHECK(i2 == 'c'); 30 | } 31 | 32 | { 33 | cc::pair p = {3, true}; 34 | 35 | auto [a, b] = p; 36 | 37 | CHECK(a == 3); 38 | CHECK(b == true); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/swap.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace foo 9 | { 10 | bool used_custom_swap = false; 11 | 12 | struct bar 13 | { 14 | }; 15 | void swap(bar&, bar&) { used_custom_swap = true; } 16 | } 17 | 18 | namespace fuz 19 | { 20 | struct baz 21 | { 22 | }; 23 | } 24 | 25 | TEST("cc::swap") 26 | { 27 | foo::bar a, b; 28 | 29 | // found via ADL 30 | foo::used_custom_swap = false; 31 | swap(a, b); 32 | CHECK(foo::used_custom_swap); 33 | 34 | // std::swap does NOT find via ADL 35 | foo::used_custom_swap = false; 36 | std::swap(a, b); 37 | CHECK(!foo::used_custom_swap); 38 | 39 | // cc::swap finds via ADL 40 | foo::used_custom_swap = false; 41 | cc::swap(a, b); 42 | CHECK(foo::used_custom_swap); 43 | 44 | cc::vector u, v; 45 | // swap(u, v); - ERROR: no adl swap 46 | std::swap(u, v); // OK via move 47 | cc::swap(u, v); // OK via move 48 | } 49 | 50 | TEST("cc::swap - using std::swap") 51 | { 52 | using std::swap; 53 | foo::bar a, b; 54 | 55 | // found via ADL 56 | foo::used_custom_swap = false; 57 | swap(a, b); 58 | CHECK(foo::used_custom_swap); 59 | 60 | // std::swap does NOT find via ADL 61 | foo::used_custom_swap = false; 62 | std::swap(a, b); 63 | CHECK(!foo::used_custom_swap); 64 | 65 | // cc::swap finds via ADL 66 | foo::used_custom_swap = false; 67 | cc::swap(a, b); 68 | CHECK(foo::used_custom_swap); 69 | 70 | cc::vector u, v; 71 | swap(u, v); // OK, uses std::move 72 | std::swap(u, v); // OK via move 73 | cc::swap(u, v); // OK via move 74 | } 75 | -------------------------------------------------------------------------------- /tests/to_string.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | TEST("cc::to_string basics") 13 | { 14 | cc::string s = "234"; 15 | 16 | CHECK(cc::to_string(12345) == "12345"); 17 | CHECK(cc::to_string(12345LL) == "12345"); 18 | CHECK(cc::to_string(12345u) == "12345"); 19 | CHECK(cc::to_string(12345uLL) == "12345"); 20 | CHECK(cc::to_string("123") == "123"); 21 | CHECK(cc::to_string(s) == "234"); 22 | CHECK(cc::to_string(true) == "true"); 23 | CHECK(cc::to_string(false) == "false"); 24 | CHECK(cc::to_string('z') == "z"); 25 | CHECK(cc::to_string(nullptr) == "[nullptr]"); 26 | CHECK(cc::to_string((void*)0x1234) == "0x0000000000001234"); 27 | CHECK(cc::to_string(std::byte(1)) == "01"); 28 | CHECK(cc::to_string(std::byte(255)) == "FF"); 29 | } 30 | 31 | TEST("cc::to_string std") 32 | { 33 | CHECK(cc::to_string(std::string("hello")) == "hello"); 34 | CHECK(cc::to_string(std::string_view("hello")) == "hello"); 35 | } 36 | 37 | TEST("cc::to_string pointers") 38 | { 39 | { 40 | int* p = nullptr; 41 | CHECK(cc::to_string(p) == "[nullptr]"); 42 | } 43 | { 44 | int* p = (int*)0x1234; 45 | CHECK(cc::to_string(p) == "0x0000000000001234"); 46 | } 47 | { 48 | int const* p = nullptr; 49 | CHECK(cc::to_string(p) == "[nullptr]"); 50 | } 51 | { 52 | char const* p = nullptr; 53 | CHECK(cc::to_string(p) == "[nullptr]"); 54 | } 55 | { 56 | char const* p = "hello"; 57 | CHECK(cc::to_string(p) == "hello"); 58 | } 59 | { 60 | char s[] = {'A', 'B', 'C', 0}; 61 | char* p = s; 62 | CHECK(cc::to_string(p) == "ABC"); 63 | } 64 | } 65 | 66 | namespace 67 | { 68 | template 69 | T gen_random(tg::rng& rng) 70 | { 71 | cc::array v; 72 | for (auto& i : v) 73 | i = rng(); 74 | return cc::bit_cast(v); 75 | } 76 | } 77 | 78 | MONTE_CARLO_TEST("cc::to_string mct") 79 | { 80 | addOp("gen", gen_random); 81 | addOp("gen", gen_random); 82 | addOp("gen", gen_random); 83 | addOp("gen", gen_random); 84 | addOp("gen", gen_random); 85 | addOp("gen", gen_random); 86 | addOp("gen", gen_random); 87 | 88 | addValue("+inf", tg::inf); 89 | addValue("+inf", tg::inf); 90 | addValue("-inf", -tg::inf); 91 | addValue("-inf", -tg::inf); 92 | addValue("nan", tg::nan); 93 | addValue("nan", tg::nan); 94 | 95 | addOp("to_string", (cc::string(*)(int32_t))cc::to_string); 96 | addOp("to_string", (cc::string(*)(int64_t))cc::to_string); 97 | addOp("to_string", (cc::string(*)(uint32_t))cc::to_string); 98 | addOp("to_string", (cc::string(*)(uint64_t))cc::to_string); 99 | addOp("to_string", (cc::string(*)(float))cc::to_string); 100 | addOp("to_string", (cc::string(*)(double))cc::to_string); 101 | addOp("to_string", (cc::string(*)(void*))cc::to_string); 102 | 103 | addOp("round-trip", [](int i) { CHECK(i == std::stoi(cc::to_string(i).c_str())); }); 104 | 105 | addInvariant("non-empty", [](cc::string const& s) { CHECK(!s.empty()); }); 106 | } 107 | -------------------------------------------------------------------------------- /tests/tuple.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | TEST("cc::tuple") 7 | { 8 | cc::tuple t; 9 | 10 | t.get<0>() = 1; 11 | t.get<1>() = 3.25f; 12 | t.get<2>() = 'c'; 13 | 14 | static_assert(sizeof(t) == 3 * sizeof(int)); 15 | 16 | CHECK(t.get<0>() == 1); 17 | CHECK(t.get<1>() == 3.25f); 18 | CHECK(t.get<2>() == 'c'); 19 | 20 | t = {2, 1.5f, 'a'}; 21 | 22 | auto [a, b, c] = t; 23 | CHECK(a == 2); 24 | CHECK(b == 1.5f); 25 | CHECK(c == 'a'); 26 | 27 | auto t2 = cc::tuple(2, 1.6f, 'a'); 28 | CHECK(t != t2); 29 | 30 | float bf = 10; 31 | auto lambda_f = [&](int i, float f, char c) { 32 | bf += 1; 33 | return int(i + f + c + bf); 34 | }; 35 | 36 | CHECK(cc::apply(lambda_f, t) == 2 + 1 + int('a') + 11); 37 | CHECK(bf == 11); 38 | } 39 | -------------------------------------------------------------------------------- /tests/types.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // checking the "sanity" of the c++ type system 5 | 6 | static_assert(!std::is_same_v); 7 | static_assert(!std::is_same_v); 8 | 9 | static_assert(std::is_same_v); 10 | static_assert(std::is_same_v); 11 | 12 | static_assert(!std::is_same_v); 13 | static_assert(!std::is_same_v); 14 | 15 | static_assert(sizeof(int) == sizeof(long) || sizeof(long) == sizeof(long long)); 16 | -------------------------------------------------------------------------------- /tests/unique_function.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace 6 | { 7 | struct callable_t 8 | { 9 | void operator()() {} 10 | }; 11 | 12 | void void_func() {} 13 | } 14 | 15 | TEST("cc::unique_function") 16 | { 17 | cc::unique_function f; 18 | 19 | CHECK(!f); 20 | 21 | f = [](int i) { return i * 2; }; 22 | 23 | CHECK(f(7) == 14); 24 | 25 | auto f2 = cc::move(f); 26 | 27 | CHECK(!f); 28 | CHECK(f2(8) == 16); 29 | } 30 | 31 | TEST("cc::unique_function compilation", disabled) 32 | { 33 | auto lambda = []() -> void {}; 34 | cc::function_ptr ptr = +[]() -> void {}; 35 | callable_t type; 36 | 37 | cc::unique_function f_tl = type; 38 | cc::unique_function f_tr = callable_t{}; 39 | cc::unique_function f_ll = lambda; 40 | cc::unique_function f_lr = [type]() -> void { (void)type; }; 41 | cc::unique_function f_ptrl = ptr; 42 | cc::unique_function f_ptrr = +[]() -> void {}; 43 | cc::unique_function f_ptrf = void_func; 44 | 45 | CHECK(true); 46 | } 47 | -------------------------------------------------------------------------------- /tests/unique_ptr.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST("cc::unique_ptr") 6 | { 7 | auto a = cc::make_unique(7); 8 | auto b = cc::make_unique(7); 9 | auto pa = a.get(); 10 | cc::unique_ptr c; 11 | 12 | CHECK(a != b); 13 | CHECK(a != nullptr); 14 | CHECK(nullptr == c); 15 | CHECK(pa == a); 16 | CHECK(b != pa); 17 | } 18 | -------------------------------------------------------------------------------- /tests/utility.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | FUZZ_TEST("min/max/clamp fuzz")(tg::rng& rng) 8 | { 9 | int const num_a = tg::uniform(rng, INT_MIN, INT_MAX); 10 | int num_b; 11 | 12 | do 13 | num_b = tg::uniform(rng, INT_MIN, INT_MAX); 14 | while (num_a == num_b); 15 | 16 | REQUIRE(num_a != num_b); 17 | 18 | int const num_lo = num_a > num_b ? num_b : num_a; 19 | int const num_hi = num_a > num_b ? num_a : num_b; 20 | REQUIRE(cc::min(num_a, num_b) == num_lo); 21 | REQUIRE(cc::max(num_a, num_b) == num_hi); 22 | 23 | CHECK(cc::clamp(num_lo, num_lo, num_hi) == num_lo); 24 | CHECK(cc::clamp(num_hi, num_lo, num_hi) == num_hi); 25 | 26 | CHECK(cc::clamp(num_lo + 1, num_lo, num_hi) == num_lo + 1); 27 | CHECK(cc::clamp(num_hi - 1, num_lo, num_hi) == num_hi - 1); 28 | } 29 | 30 | TEST("utility") 31 | { 32 | CHECK(cc::wrapped_increment(0, 1) == 0); 33 | CHECK(cc::wrapped_increment(0, 5) == 1); 34 | CHECK(cc::wrapped_increment(4, 5) == 0); 35 | 36 | CHECK(cc::wrapped_decrement(0, 5) == 4); 37 | CHECK(cc::wrapped_decrement(4, 5) == 3); 38 | 39 | CHECK(cc::int_div_ceil(1, 1) == 1); 40 | CHECK(cc::int_div_ceil(6, 3) == 2); 41 | CHECK(cc::int_div_ceil(7, 3) == 3); 42 | CHECK(cc::int_div_ceil(8, 3) == 3); 43 | CHECK(cc::int_div_ceil(9, 3) == 3); 44 | CHECK(cc::int_div_ceil(10, 3) == 4); 45 | } 46 | -------------------------------------------------------------------------------- /tests/variant.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST("cc::variant basics") 6 | { 7 | cc::variant v; 8 | CC_ASSERT(v == 0); 9 | // CC_ASSERT(v != '\0'); -- TODO: what behavior is desired here? 10 | CC_ASSERT(v.is()); 11 | CC_ASSERT(!v.is()); 12 | CC_ASSERT(v.get() == 0); 13 | 14 | v = 'c'; 15 | CC_ASSERT(v == 'c'); 16 | // CC_ASSERT(v != int('c')); -- TODO: what behavior is desired here? 17 | CC_ASSERT(v.is()); 18 | CC_ASSERT(!v.is()); 19 | CC_ASSERT(v.get() == 'c'); 20 | 21 | v = cc::string("hello"); 22 | CHECK(v == "hello"); 23 | } 24 | -------------------------------------------------------------------------------- /tests/wrapped_span.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | TEST("cc::wrapped_span") 7 | { 8 | auto vec = cc::vector{1, 2, 3, 4, 5}; 9 | auto ws = cc::wrapped_span(vec); 10 | CHECK(ws[0] == 1); 11 | CHECK(ws[-1] == 5); 12 | CHECK(ws[-2] == 4); 13 | CHECK(ws[5] == 1); 14 | CHECK(ws[-6] == 5); 15 | } 16 | --------------------------------------------------------------------------------