├── .gitattributes ├── benchmark ├── src │ ├── main.cpp │ ├── shared_ptr.cpp │ ├── lock.cpp │ ├── singleton.cpp │ └── atomic_shared_ptr.cpp └── CMakeLists.txt ├── .gitignore ├── .github └── workflows │ ├── unit_test.yml │ ├── benchmark.yml │ └── check_format.yml ├── test ├── src │ ├── vector │ │ ├── print.cpp │ │ ├── capacity.cpp │ │ ├── max_size.cpp │ │ ├── empty.cpp │ │ ├── shrink_to_fit.cpp │ │ ├── size.cpp │ │ ├── compare.cpp │ │ ├── data.cpp │ │ ├── erase.cpp │ │ ├── swap.cpp │ │ ├── emplace_back.cpp │ │ ├── reserve.cpp │ │ └── resize.cpp │ ├── is_nullable.cpp │ ├── pipe.cpp │ ├── inplace_vector │ │ ├── assign.cpp │ │ ├── erase.cpp │ │ └── constructor.cpp │ ├── swap.cpp │ ├── main.cpp │ ├── finally.cpp │ ├── message.cpp │ ├── spinlock_ptr.cpp │ ├── cstring.cpp │ ├── observer_ptr.cpp │ ├── with_lock.cpp │ ├── avl_tree.cpp │ ├── function │ │ ├── constructor.cpp │ │ └── overload_resolution.cpp │ ├── compressed_pair.cpp │ ├── allocator_traits.cpp │ ├── singleton.cpp │ ├── treiber_stack.cpp │ ├── reference_counter.cpp │ ├── hazard_pointer.cpp │ ├── rb_tree.cpp │ ├── function.cpp │ ├── do_if_noexcept.cpp │ ├── mpsc_queue.cpp │ ├── shared_ptr.cpp │ ├── worth_move.cpp │ └── can_be_destroyed_from_base.cpp └── CMakeLists.txt ├── include └── ciel │ ├── core │ ├── is_complete_type.hpp │ ├── as_const.hpp │ ├── aligned_storage.hpp │ ├── is_nullable.hpp │ ├── exchange.hpp │ ├── is_final.hpp │ ├── pipe.hpp │ ├── is_reference.hpp │ ├── do_if_noexcept.hpp │ ├── logical.hpp │ ├── spinlock.hpp │ ├── is_trivially_relocatable.hpp │ ├── is_range.hpp │ ├── finally.hpp │ ├── datasizeof.hpp │ ├── array.hpp │ ├── packed_ptr.hpp │ ├── iterator_category.hpp │ ├── alignment.hpp │ ├── singleton.hpp │ ├── iterator_base.hpp │ ├── integer_sequence.hpp │ ├── cstring.hpp │ ├── treiber_stack.hpp │ ├── strip_signature.hpp │ ├── spinlock_ptr.hpp │ ├── can_be_destroyed_from_base.hpp │ ├── aba.hpp │ ├── reference_counter.hpp │ └── mpsc_queue.hpp │ ├── test │ ├── simple_latch.hpp │ ├── different_allocator.hpp │ ├── operator_hijacker.hpp │ ├── forward_iterator.hpp │ ├── range.hpp │ ├── input_iterator.hpp │ ├── propagate_allocator.hpp │ ├── int_wrapper.hpp │ └── random_access_iterator.hpp │ ├── memory.hpp │ ├── experimental │ ├── move_proxy.hpp │ └── worth_move.hpp │ ├── allocate_at_least.hpp │ ├── compare.hpp │ ├── demangle.hpp │ ├── allocator_traits.hpp │ ├── to_address.hpp │ ├── copy_n.hpp │ ├── range_destroyer.hpp │ ├── observer_ptr.hpp │ ├── swap.hpp │ └── split_buffer.hpp ├── CMakeLists.txt ├── portable_header_process.sh ├── format.sh ├── Makefile ├── .clang-tidy └── .clang-format /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /benchmark/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | BENCHMARK_MAIN(); 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | */.DS_Store 4 | cmake-build-* 5 | build 6 | .vscode 7 | portable_headers 8 | .cache 9 | compile_commands.json 10 | third_party 11 | -------------------------------------------------------------------------------- /.github/workflows/unit_test.yml: -------------------------------------------------------------------------------- 1 | name: unit_test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | job: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: test 16 | run: | 17 | make test 18 | -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: benchmark 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | job: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: benchmark 16 | run: | 17 | make benchmark 18 | -------------------------------------------------------------------------------- /test/src/vector/print.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | using namespace ciel; 8 | 9 | TEST(vector, print) { 10 | { 11 | vector v; 12 | v.reserve(10); 13 | std::cout << v << '\n'; 14 | } 15 | { 16 | vector v{0, 1, 2, 3, 4}; 17 | v.reserve(10); 18 | std::cout << v << '\n'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/src/is_nullable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | using namespace ciel; 8 | 9 | TEST(is_nullable, all) { 10 | static_assert(is_nullable::value, ""); 11 | static_assert(is_nullable::value, ""); 12 | 13 | static_assert(is_nullable>::value, ""); 14 | static_assert(is_nullable>::value, ""); 15 | } 16 | -------------------------------------------------------------------------------- /include/ciel/core/is_complete_type.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_IS_COMPLETE_TYPE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_IS_COMPLETE_TYPE_HPP_ 3 | 4 | #include 5 | 6 | NAMESPACE_CIEL_BEGIN 7 | 8 | #if CIEL_STD_VER >= 20 9 | template 10 | inline constexpr bool is_complete_type_v = requires { sizeof(T); }; 11 | #endif 12 | 13 | NAMESPACE_CIEL_END 14 | 15 | #endif // CIELLAB_INCLUDE_CIEL_CORE_IS_COMPLETE_TYPE_HPP_ 16 | -------------------------------------------------------------------------------- /test/src/vector/capacity.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace ciel; 6 | 7 | TEST(vector, capacity) { 8 | { 9 | const vector v; 10 | ASSERT_EQ(v.capacity(), 0); 11 | } 12 | { 13 | vector v(100); 14 | ASSERT_GE(v.capacity(), 100); 15 | 16 | const auto old_cap = v.capacity(); 17 | v.resize(old_cap); 18 | v.push_back(0); 19 | ASSERT_GT(v.capacity(), old_cap); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /include/ciel/core/as_const.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_AS_CONST_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_AS_CONST_HPP_ 3 | 4 | #include 5 | 6 | NAMESPACE_CIEL_BEGIN 7 | 8 | // https://en.cppreference.com/w/cpp/utility/as_const 9 | 10 | template 11 | add_const_t& as_const(T& t) noexcept { 12 | return t; 13 | } 14 | 15 | template 16 | void as_const(const T&&) = delete; 17 | 18 | NAMESPACE_CIEL_END 19 | 20 | #endif // CIELLAB_INCLUDE_CIEL_CORE_AS_CONST_HPP_ 21 | -------------------------------------------------------------------------------- /.github/workflows/check_format.yml: -------------------------------------------------------------------------------- 1 | name: check_format 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | job: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: install clang-format 16 | run: | 17 | sudo bash install_clang_format.sh 19 18 | sudo cp /usr/bin/clang-format-19 /usr/bin/clang-format 19 | clang-format --version 20 | 21 | - name: check format 22 | run: make check_format 23 | -------------------------------------------------------------------------------- /test/src/vector/max_size.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace ciel; 10 | 11 | TEST(vector, max_size) { 12 | const size_t ptrdiff_t_max = static_cast(std::numeric_limits::max()); 13 | { 14 | const vector c; 15 | ASSERT_LE(c.max_size(), ptrdiff_t_max); 16 | ASSERT_LE(c.max_size(), std::allocator_traits>::max_size(c.get_allocator())); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/src/pipe.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | using namespace ciel; 8 | 9 | namespace { 10 | 11 | struct TransformToConst { 12 | template 13 | using type = const T; 14 | }; 15 | 16 | struct TransformToPtr { 17 | template 18 | using type = T*; 19 | }; 20 | 21 | } // namespace 22 | 23 | TEST(pipe, all) { 24 | using T = ciel::pipe; 25 | 26 | static_assert(std::is_same::value, ""); 27 | } 28 | -------------------------------------------------------------------------------- /include/ciel/core/aligned_storage.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_ALIGNED_STORAGE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_ALIGNED_STORAGE_HPP_ 3 | 4 | #include 5 | 6 | NAMESPACE_CIEL_BEGIN 7 | 8 | template 9 | struct aligned_storage { 10 | union type { 11 | alignas(alignment) unsigned char buffer_[(size + alignment - 1) / alignment * alignment]; 12 | unsigned char null_state_{}; 13 | }; 14 | 15 | }; // aligned_storage 16 | 17 | NAMESPACE_CIEL_END 18 | 19 | #endif // CIELLAB_INCLUDE_CIEL_CORE_ALIGNED_STORAGE_HPP_ 20 | -------------------------------------------------------------------------------- /test/src/vector/empty.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | using namespace ciel; 7 | 8 | namespace { 9 | 10 | template 11 | void test_empty_impl(::testing::Test*) { 12 | C c; 13 | ASSERT_TRUE(c.empty()); 14 | 15 | c.push_back(1); 16 | ASSERT_FALSE(c.empty()); 17 | 18 | c.clear(); 19 | ASSERT_TRUE(c.empty()); 20 | } 21 | 22 | } // namespace 23 | 24 | TEST(vector, empty) { 25 | test_empty_impl>(this); 26 | test_empty_impl>>(this); 27 | } 28 | -------------------------------------------------------------------------------- /test/src/inplace_vector/assign.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | using namespace ciel; 9 | 10 | TEST(inplace_vector, issue_5) { 11 | int x = 1; 12 | int y = 2; 13 | int z = 3; 14 | int w = 4; 15 | 16 | ciel::inplace_vector, 2> v1{std::tie(x), std::tie(y)}; 17 | ciel::inplace_vector, 2> v2{std::tie(z), std::tie(w)}; 18 | 19 | v1 = std::move(v2); 20 | 21 | ASSERT_EQ(x, 3); 22 | ASSERT_EQ(y, 4); 23 | ASSERT_EQ(z, 3); 24 | ASSERT_EQ(w, 4); 25 | } 26 | -------------------------------------------------------------------------------- /include/ciel/core/is_nullable.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_IS_NULLABLE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_IS_NULLABLE_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | NAMESPACE_CIEL_BEGIN 10 | 11 | // std::is_convertible can't work with explicit conversion operator. 12 | template 13 | struct is_nullable : conjunction, std::is_constructible, 14 | std::is_constructible, std::is_assignable> {}; 15 | 16 | NAMESPACE_CIEL_END 17 | 18 | #endif // CIELLAB_INCLUDE_CIEL_CORE_IS_NULLABLE_HPP_ 19 | -------------------------------------------------------------------------------- /include/ciel/core/exchange.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_EXCHANGE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_EXCHANGE_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | NAMESPACE_CIEL_BEGIN 10 | 11 | template 12 | CIEL_NODISCARD T exchange(T& obj, U&& new_value) noexcept(std::is_nothrow_move_constructible::value 13 | && std::is_nothrow_assignable::value) { 14 | T old_value = std::move(obj); 15 | obj = std::forward(new_value); 16 | return old_value; 17 | } 18 | 19 | NAMESPACE_CIEL_END 20 | 21 | #endif // CIELLAB_INCLUDE_CIEL_CORE_EXCHANGE_HPP_ 22 | -------------------------------------------------------------------------------- /test/src/swap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | using namespace ciel; 9 | 10 | TEST(swap, issue_5) { 11 | /* Should not pick this version 12 | 13 | namespace std { 14 | template 15 | requires ciel::is_trivially_relocatable::value 16 | void swap(T& a, T& b) noexcept { 17 | ciel::relocatable_swap(a, b); 18 | } 19 | } // namespace std 20 | */ 21 | int x = 1; 22 | int y = 2; 23 | std::tuple tx = std::tie(x); 24 | std::tuple ty = std::tie(y); 25 | 26 | std::swap(tx, ty); 27 | 28 | ASSERT_EQ(x, 2); 29 | ASSERT_EQ(y, 1); 30 | } 31 | -------------------------------------------------------------------------------- /test/src/vector/shrink_to_fit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using namespace ciel; 9 | 10 | TEST(vector, shrink_to_fit) { 11 | { 12 | vector v(100); 13 | v.push_back(1); 14 | 15 | const size_t capacity = v.capacity(); 16 | v.shrink_to_fit(); 17 | ASSERT_LE(v.capacity(), capacity); 18 | ASSERT_EQ(v.size(), 101); 19 | } 20 | { 21 | vector> v(100); 22 | v.push_back(1); 23 | 24 | const size_t capacity = v.capacity(); 25 | v.shrink_to_fit(); 26 | ASSERT_LE(v.capacity(), capacity); 27 | ASSERT_EQ(v.size(), 101); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /include/ciel/core/is_final.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_IS_FINAL_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_IS_FINAL_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | NAMESPACE_CIEL_BEGIN 9 | 10 | #if CIEL_STD_VER >= 14 11 | template 12 | using is_final = std::is_final; 13 | #elif __has_builtin(__is_final) 14 | template 15 | struct is_final : bool_constant<__is_final(T)> {}; 16 | #else 17 | // If is_final is not available, it may be better to manually write explicit template specializations. 18 | // e.g. 19 | // template<> 20 | // struct is_final : std::false_type {}; 21 | // 22 | template 23 | struct is_final; 24 | #endif 25 | 26 | NAMESPACE_CIEL_END 27 | 28 | #endif // CIELLAB_INCLUDE_CIEL_CORE_IS_FINAL_HPP_ 29 | -------------------------------------------------------------------------------- /test/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | int main(int argc, char** argv) { 8 | std::cout << "\n==================================================\n" 9 | << "CIEL_STD_VER: " << CIEL_STD_VER << '\n' 10 | << std::boolalpha << "CIEL_HAS_EXCEPTIONS: " << 11 | #ifdef CIEL_HAS_EXCEPTIONS 12 | true 13 | #else 14 | false 15 | #endif 16 | << '\n' 17 | << "CIEL_HAS_RTTI: " << 18 | #ifdef CIEL_HAS_RTTI 19 | true 20 | #else 21 | false 22 | #endif 23 | << '\n' 24 | << "==================================================\n\n"; 25 | 26 | ::testing::InitGoogleTest(&argc, argv); 27 | return RUN_ALL_TESTS(); 28 | } 29 | -------------------------------------------------------------------------------- /benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(ciellab_benchmark LANGUAGES CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_BUILD_TYPE "Release") 7 | 8 | include(FetchContent) 9 | FetchContent_Declare( 10 | benchmark 11 | GIT_REPOSITORY https://github.com/google/benchmark.git 12 | GIT_TAG b04cec1bf90c3d8e47739bb3271607a18d8b5106 13 | ) 14 | FetchContent_MakeAvailable(benchmark) 15 | 16 | add_executable(ciellab_benchmark 17 | src/main.cpp 18 | src/atomic_shared_ptr.cpp 19 | src/lock.cpp 20 | src/shared_ptr.cpp 21 | src/singleton.cpp 22 | src/vector.cpp 23 | ) 24 | 25 | target_link_libraries(ciellab_benchmark ciellab benchmark::benchmark) 26 | 27 | target_compile_options(ciellab_benchmark PUBLIC -O3 -Wall -Wextra -fexceptions -frtti) 28 | -------------------------------------------------------------------------------- /test/src/vector/size.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | using namespace ciel; 7 | 8 | namespace { 9 | 10 | template 11 | void test_size_impl(::testing::Test*) { 12 | C c; 13 | ASSERT_EQ(c.size(), 0); 14 | 15 | c.push_back(2); 16 | ASSERT_EQ(c.size(), 1); 17 | 18 | c.push_back(1); 19 | ASSERT_EQ(c.size(), 2); 20 | 21 | c.push_back(3); 22 | ASSERT_EQ(c.size(), 3); 23 | 24 | c.erase(c.begin()); 25 | ASSERT_EQ(c.size(), 2); 26 | 27 | c.erase(c.begin()); 28 | ASSERT_EQ(c.size(), 1); 29 | 30 | c.erase(c.begin()); 31 | ASSERT_EQ(c.size(), 0); 32 | } 33 | 34 | } // namespace 35 | 36 | TEST(vector, size) { 37 | test_size_impl>(this); 38 | test_size_impl>>(this); 39 | } 40 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(ciellab LANGUAGES CXX) 3 | 4 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 5 | 6 | if (NOT CMAKE_BUILD_TYPE) 7 | set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose build type: Debug/Release/RelWithDebInfo/MinSizeRel." FORCE) 8 | endif (NOT CMAKE_BUILD_TYPE) 9 | 10 | set(FETCHCONTENT_BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party") 11 | 12 | option(CIELLAB_BUILD_TESTS "Build unit tests" ${PROJECT_IS_TOP_LEVEL}) 13 | option(CIELLAB_BUILD_BENCHMARKS "Build benchmarks" ${PROJECT_IS_TOP_LEVEL}) 14 | 15 | add_library(ciellab INTERFACE) 16 | 17 | target_include_directories(ciellab INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) 18 | 19 | if (CIELLAB_BUILD_TESTS) 20 | enable_testing() 21 | add_subdirectory(test) 22 | endif () 23 | 24 | if (CIELLAB_BUILD_BENCHMARKS) 25 | add_subdirectory(benchmark) 26 | endif () 27 | -------------------------------------------------------------------------------- /test/src/vector/compare.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | using namespace ciel; 7 | 8 | TEST(vector, equal) { 9 | { 10 | const vector v1{0, 1, 2, 3, 4}; 11 | const vector v2{0, 1, 2, 3, 4}; 12 | 13 | ASSERT_EQ(v1, v2); 14 | ASSERT_LE(v1, v2); 15 | ASSERT_GE(v1, v2); 16 | } 17 | } 18 | 19 | TEST(vector, not_equal) { 20 | { 21 | const vector v1{0, 1, 2, 3, 4}; 22 | const vector v2{0, 1, 2, 3, 5}; 23 | 24 | ASSERT_NE(v1, v2); 25 | } 26 | } 27 | 28 | TEST(vector, less) { 29 | { 30 | const vector v1{0, 1, 2, 3, 4}; 31 | const vector v2{0, 1, 3, 3, 4}; 32 | 33 | ASSERT_LT(v1, v2); 34 | ASSERT_LE(v1, v2); 35 | ASSERT_GT(v2, v1); 36 | ASSERT_GE(v2, v1); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/src/finally.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST(finally, defer) { 6 | bool deferCalled = false; 7 | { 8 | CIEL_DEFER({ deferCalled = true; }); 9 | } 10 | ASSERT_TRUE(deferCalled); 11 | } 12 | 13 | TEST(finally, defer_order) { 14 | int counter = 0; 15 | int a = 0, b = 0, c = 0; 16 | { 17 | CIEL_DEFER({ a = ++counter; }); 18 | CIEL_DEFER({ b = ++counter; }); 19 | CIEL_DEFER({ c = ++counter; }); 20 | } 21 | ASSERT_EQ(a, 3); 22 | ASSERT_EQ(b, 2); 23 | ASSERT_EQ(c, 1); 24 | } 25 | 26 | TEST(finally, defer_order_2) { 27 | int counter = 0; 28 | int a = 0, b = 0, c = 0; 29 | { 30 | CIEL_DEFER({ 31 | a = ++counter; 32 | b = ++counter; 33 | c = ++counter; 34 | }); 35 | } 36 | ASSERT_EQ(a, 1); 37 | ASSERT_EQ(b, 2); 38 | ASSERT_EQ(c, 3); 39 | } 40 | -------------------------------------------------------------------------------- /include/ciel/core/pipe.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_PIPE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_PIPE_HPP_ 3 | 4 | #include 5 | 6 | NAMESPACE_CIEL_BEGIN 7 | 8 | // Inspired by Microsoft snmalloc's implementation. 9 | 10 | namespace detail { 11 | 12 | template 13 | struct pipe_impl; 14 | 15 | template 16 | struct pipe_impl { 17 | using type = Input; 18 | }; 19 | 20 | template 21 | struct pipe_impl { 22 | using Transformed = typename Transformer::template type; 23 | 24 | using type = typename pipe_impl::type; 25 | }; 26 | 27 | } // namespace detail 28 | 29 | template 30 | using pipe = typename detail::pipe_impl::type; 31 | 32 | NAMESPACE_CIEL_END 33 | 34 | #endif // CIELLAB_INCLUDE_CIEL_CORE_PIPE_HPP_ 35 | -------------------------------------------------------------------------------- /test/src/message.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace ciel; 11 | 12 | TEST(message, print) { 13 | ciel::println("This is {} testing! This is {} testing! This is {} testing! This is {} testing!", "message.hpp", 14 | "message.hpp", "message.hpp", "message.hpp"); 15 | } 16 | 17 | TEST(message, print_address) { 18 | struct { 19 | int a{}; 20 | int b{}; 21 | int c{}; 22 | } s; 23 | 24 | ciel::println("Test addresses printing:\n{}\n{}\n{}", &s.a, &s.b, &s.c); 25 | } 26 | 27 | /* 28 | TEST(message, expect_message) { 29 | #ifndef CIEL_HAS_EXCEPTIONS 30 | CIEL_THROW_EXCEPTION(std::bad_array_new_length{}); 31 | #endif 32 | 33 | // CIEL_ASSERT(false); 34 | CIEL_ASSERT_M(false, "Test string: {}", "This is string."); 35 | } 36 | */ 37 | -------------------------------------------------------------------------------- /include/ciel/core/is_reference.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_IS_REFERENCE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_IS_REFERENCE_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | NAMESPACE_CIEL_BEGIN 10 | 11 | // is_const_lvalue_reference 12 | 13 | template 14 | struct is_const_lvalue_reference : std::false_type {}; 15 | 16 | template 17 | struct is_const_lvalue_reference : std::true_type {}; 18 | 19 | // is_const_rvalue_reference 20 | 21 | template 22 | struct is_const_rvalue_reference : std::false_type {}; 23 | 24 | template 25 | struct is_const_rvalue_reference : std::true_type {}; 26 | 27 | // is_const_reference 28 | 29 | template 30 | struct is_const_reference : disjunction, is_const_rvalue_reference> {}; 31 | 32 | NAMESPACE_CIEL_END 33 | 34 | #endif // CIELLAB_INCLUDE_CIEL_CORE_IS_REFERENCE_HPP_ 35 | -------------------------------------------------------------------------------- /include/ciel/test/simple_latch.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_TEST_SIMPLE_LATCH_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_TEST_SIMPLE_LATCH_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | NAMESPACE_CIEL_BEGIN 12 | 13 | class SimpleLatch { 14 | public: 15 | SimpleLatch(const size_t count_down) noexcept 16 | : count_down_(count_down) {} 17 | 18 | void arrive_and_wait() noexcept { 19 | std::unique_lock lock(mutex_); 20 | 21 | CIEL_ASSERT(count_down_ != 0); 22 | 23 | if (--count_down_ == 0) { 24 | cv_.notify_all(); 25 | 26 | } else { 27 | cv_.wait(lock); 28 | } 29 | } 30 | 31 | private: 32 | std::mutex mutex_; 33 | std::condition_variable cv_; 34 | size_t count_down_; 35 | 36 | }; // class SimpleLatch 37 | 38 | NAMESPACE_CIEL_END 39 | 40 | #endif // CIELLAB_INCLUDE_CIEL_TEST_SIMPLE_LATCH_HPP_ 41 | -------------------------------------------------------------------------------- /include/ciel/test/different_allocator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_TEST_DIFFERENT_ALLOCATOR_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_TEST_DIFFERENT_ALLOCATOR_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | NAMESPACE_CIEL_BEGIN 9 | 10 | template 11 | class different_allocator { 12 | public: 13 | using value_type = T; 14 | 15 | different_allocator() = default; 16 | 17 | template 18 | different_allocator(different_allocator) noexcept {} 19 | 20 | CIEL_NODISCARD T* allocate(ptrdiff_t n) { 21 | return std::allocator().allocate(n); 22 | } 23 | 24 | void deallocate(T* p, ptrdiff_t n) noexcept { 25 | std::allocator().deallocate(p, n); 26 | } 27 | 28 | CIEL_NODISCARD friend bool operator==(different_allocator, different_allocator) noexcept { 29 | return false; 30 | } 31 | 32 | }; // class different_allocator 33 | 34 | NAMESPACE_CIEL_END 35 | 36 | #endif // CIELLAB_INCLUDE_CIEL_TEST_DIFFERENT_ALLOCATOR_HPP_ 37 | -------------------------------------------------------------------------------- /include/ciel/memory.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_MEMORY_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_MEMORY_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | NAMESPACE_CIEL_BEGIN 10 | 11 | // allocate 12 | 13 | template 14 | CIEL_NODISCARD T* allocate(const size_t n) { 15 | #if CIEL_STD_VER >= 17 16 | if CIEL_UNLIKELY (ciel::is_overaligned_for_new(alignof(T))) { 17 | return static_cast(::operator new(sizeof(T) * n, static_cast(alignof(T)))); 18 | } 19 | #endif 20 | return static_cast(::operator new(sizeof(T) * n)); 21 | } 22 | 23 | // deallocate 24 | 25 | template 26 | void deallocate(T* ptr) noexcept { 27 | #if CIEL_STD_VER >= 17 28 | if CIEL_UNLIKELY (ciel::is_overaligned_for_new(alignof(T))) { 29 | ::operator delete(ptr, static_cast(alignof(T))); 30 | return; 31 | } 32 | #endif 33 | ::operator delete(ptr); 34 | } 35 | 36 | NAMESPACE_CIEL_END 37 | 38 | #endif // CIELLAB_INCLUDE_CIEL_MEMORY_HPP_ 39 | -------------------------------------------------------------------------------- /include/ciel/core/do_if_noexcept.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_DO_IF_NOEXCEPT_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_DO_IF_NOEXCEPT_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | NAMESPACE_CIEL_BEGIN 11 | 12 | template 13 | #ifdef CIEL_HAS_EXCEPTIONS 14 | conditional_t>, std::is_copy_constructible>::value, 15 | const T&, T&&> 16 | #else 17 | T&& 18 | #endif 19 | move_if_noexcept(T& x) noexcept { 20 | return std::move(x); 21 | } 22 | 23 | template> 24 | #ifdef CIEL_HAS_EXCEPTIONS 25 | conditional_t>, std::is_copy_constructible>::value, 26 | const RT&, T&&> 27 | #else 28 | T&& 29 | #endif 30 | forward_if_noexcept(T&& x) noexcept { 31 | return std::forward(x); 32 | } 33 | 34 | NAMESPACE_CIEL_END 35 | 36 | #endif // CIELLAB_INCLUDE_CIEL_CORE_DO_IF_NOEXCEPT_HPP_ 37 | -------------------------------------------------------------------------------- /include/ciel/core/logical.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_LOGICAL_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_LOGICAL_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | NAMESPACE_CIEL_BEGIN 9 | 10 | // conjunction 11 | 12 | // When the template pack is empty, derive from true_type. 13 | template 14 | struct conjunction : std::true_type {}; 15 | 16 | // Otherwise, derive from the first false template member (if all true, choose the last one). 17 | template 18 | struct conjunction : conditional_t(B1::value), conjunction, B1> {}; 19 | 20 | // disjunction 21 | 22 | template 23 | struct disjunction : std::false_type {}; 24 | 25 | template 26 | struct disjunction : conditional_t(B1::value), B1, disjunction> {}; 27 | 28 | // negation 29 | 30 | template 31 | struct negation : bool_constant(B::value)> {}; 32 | 33 | NAMESPACE_CIEL_END 34 | 35 | #endif // CIELLAB_INCLUDE_CIEL_CORE_LOGICAL_HPP_ 36 | -------------------------------------------------------------------------------- /test/src/spinlock_ptr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace ciel; 13 | 14 | TEST(spinlock_ptr, lock) { 15 | constexpr size_t threads_num = 64; 16 | constexpr size_t operations_num = 10000; 17 | 18 | const std::unique_ptr up{new int{0}}; 19 | const spinlock_ptr ptr{up.get()}; 20 | 21 | SimpleLatch go{threads_num}; 22 | 23 | ciel::vector threads(reserve_capacity, threads_num); 24 | 25 | for (size_t i = 0; i < threads_num; ++i) { 26 | threads.unchecked_emplace_back([&] { 27 | go.arrive_and_wait(); 28 | 29 | for (size_t j = 0; j < operations_num; ++j) { 30 | int* p = ptr.lock(); 31 | CIEL_DEFER({ ptr.unlock(); }); 32 | 33 | ++(*p); 34 | } 35 | }); 36 | } 37 | 38 | for (auto& t : threads) { 39 | t.join(); 40 | } 41 | 42 | ASSERT_EQ(*up, threads_num * operations_num); 43 | } 44 | -------------------------------------------------------------------------------- /test/src/cstring.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | using namespace ciel; 8 | 9 | TEST(cstring, find) { 10 | const std::string s = "This is a string."; 11 | const std::string sub1 = "is a"; 12 | const std::string sub2 = ""; 13 | const std::string sub3 = "This a"; 14 | const std::string sub4 = "This is a string..."; 15 | const std::string sub5 = "This is a string."; 16 | 17 | ASSERT_EQ(ciel::find(s.data(), s.data() + s.size(), '.'), s.data() + 16); 18 | ASSERT_EQ(ciel::find(s.data(), s.data() + s.size(), 'c'), nullptr); 19 | 20 | ASSERT_EQ(ciel::find(s.data(), s.data() + s.size(), sub1.data(), sub1.data() + sub1.size()), s.data() + 5); 21 | ASSERT_EQ(ciel::find(s.data(), s.data() + s.size(), sub2.data(), sub2.data() + sub2.size()), s.data() + 0); 22 | ASSERT_EQ(ciel::find(s.data(), s.data() + s.size(), sub3.data(), sub3.data() + sub3.size()), nullptr); 23 | ASSERT_EQ(ciel::find(s.data(), s.data() + s.size(), sub4.data(), sub4.data() + sub4.size()), nullptr); 24 | ASSERT_EQ(ciel::find(s.data(), s.data() + s.size(), sub5.data(), sub5.data() + sub5.size()), s.data() + 0); 25 | } 26 | -------------------------------------------------------------------------------- /test/src/observer_ptr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace ciel; 10 | 11 | TEST(observer_ptr, all) { 12 | struct Base {}; 13 | 14 | struct Derived : Base {}; 15 | 16 | observer_ptr p1; 17 | const observer_ptr p2{nullptr}; 18 | 19 | ASSERT_FALSE(p1); 20 | ASSERT_FALSE(p2); 21 | 22 | const std::unique_ptr up1{new int{123}}; 23 | observer_ptr p3{up1.get()}; 24 | 25 | ASSERT_TRUE(p3); 26 | ASSERT_EQ(up1.get(), p3.get()); 27 | ASSERT_EQ(*p3, 123); 28 | 29 | ASSERT_EQ(std::hash>()(p3), std::hash()(p3.get())); 30 | 31 | std::swap(p1, p3); 32 | 33 | ASSERT_FALSE(p3); 34 | ASSERT_EQ(up1.get(), p1.get()); 35 | 36 | const std::unique_ptr up2{new Derived{}}; 37 | observer_ptr p4{up2.get()}; 38 | 39 | ASSERT_TRUE(p4); 40 | ASSERT_EQ(up2.get(), p4.get()); 41 | ASSERT_EQ(up2.get(), p4.release()); 42 | ASSERT_FALSE(p4); 43 | 44 | p4.reset(); 45 | ASSERT_FALSE(p4); 46 | 47 | p4.reset(up2.get()); 48 | ASSERT_EQ(up2.get(), p4.get()); 49 | } 50 | -------------------------------------------------------------------------------- /include/ciel/experimental/move_proxy.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_EXPERIMENTAL_MOVE_PROXY_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_EXPERIMENTAL_MOVE_PROXY_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | NAMESPACE_CIEL_BEGIN 10 | 11 | template 12 | class move_proxy { 13 | public: 14 | template::value> = 0> 15 | move_proxy(Args&&... args) noexcept(std::is_nothrow_constructible::value) 16 | : data_(std::forward(args)...) {} 17 | 18 | template, Args&&...>::value> = 0> 20 | move_proxy(std::initializer_list il, 21 | Args&&... args) noexcept(std::is_nothrow_constructible, Args&&...>::value) 22 | : data_(il, std::forward(args)...) {} 23 | 24 | operator T&&() const noexcept { 25 | return std::move(data_); 26 | } 27 | 28 | private: 29 | mutable T data_; 30 | 31 | }; // class move_proxy 32 | 33 | NAMESPACE_CIEL_END 34 | 35 | #endif // CIELLAB_INCLUDE_CIEL_EXPERIMENTAL_MOVE_PROXY_HPP_ 36 | -------------------------------------------------------------------------------- /test/src/with_lock.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | using namespace ciel; 12 | 13 | namespace { 14 | 15 | template 16 | void test_impl(::testing::Test*) { 17 | constexpr size_t threads_num = 64; 18 | constexpr size_t operations_num = 10000; 19 | 20 | Lock lock; 21 | size_t count = 0; 22 | SimpleLatch go{threads_num}; 23 | 24 | ciel::vector threads(reserve_capacity, threads_num); 25 | 26 | for (size_t i = 0; i < threads_num; ++i) { 27 | threads.unchecked_emplace_back([&] { 28 | go.arrive_and_wait(); 29 | 30 | for (size_t j = 0; j < operations_num; ++j) { 31 | with(lock, [&]() { 32 | ++count; 33 | }); 34 | } 35 | }); 36 | } 37 | 38 | for (auto& t : threads) { 39 | t.join(); 40 | } 41 | 42 | ASSERT_EQ(count, threads_num * operations_num); 43 | } 44 | 45 | } // namespace 46 | 47 | TEST(with_lock, lock) { 48 | test_impl(this); 49 | test_impl(this); 50 | } 51 | -------------------------------------------------------------------------------- /include/ciel/allocate_at_least.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_ALLOCATE_AT_LEAST_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_ALLOCATE_AT_LEAST_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | NAMESPACE_CIEL_BEGIN 10 | 11 | #ifdef __cpp_lib_allocate_at_least 12 | 13 | template::size_type> 14 | CIEL_NODISCARD auto allocate_at_least(Allocator& allocator, const SizeType size) { 15 | return std::allocator_traits::allocate_at_least(allocator, size); 16 | } 17 | 18 | #else 19 | 20 | template 21 | struct allocation_result { 22 | Pointer ptr; 23 | SizeType count; 24 | 25 | }; // struct allocation_result 26 | 27 | template::pointer, 28 | class SizeType = typename std::allocator_traits::size_type> 29 | CIEL_NODISCARD allocation_result allocate_at_least(Allocator& allocator, const SizeType size) { 30 | return {std::allocator_traits::allocate(allocator, size), size}; 31 | } 32 | 33 | #endif 34 | 35 | NAMESPACE_CIEL_END 36 | 37 | #endif // CIELLAB_INCLUDE_CIEL_ALLOCATE_AT_LEAST_HPP_ 38 | -------------------------------------------------------------------------------- /include/ciel/core/spinlock.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_SPINLOCK_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_SPINLOCK_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | NAMESPACE_CIEL_BEGIN 12 | 13 | class spinlock { 14 | private: 15 | std::atomic flag_{false}; 16 | 17 | public: 18 | CIEL_NODISCARD bool is_locked(std::memory_order order = std::memory_order_seq_cst) const noexcept { 19 | return flag_.load(order); 20 | } 21 | 22 | void lock(std::memory_order order = std::memory_order_seq_cst) { 23 | do { 24 | while (is_locked(std::memory_order_relaxed)) { 25 | std::this_thread::yield(); 26 | } 27 | } while (flag_.exchange(true, order)); 28 | } 29 | 30 | void unlock(std::memory_order order = std::memory_order_seq_cst) { 31 | CIEL_ASSERT(is_locked(std::memory_order_relaxed)); 32 | 33 | flag_.store(false, order); 34 | } 35 | 36 | }; // class spinlock 37 | 38 | template 39 | void with(spinlock& lock, F&& f) { 40 | lock.lock(std::memory_order_acquire); 41 | CIEL_DEFER({ lock.unlock(std::memory_order_release); }); 42 | 43 | f(); 44 | } 45 | 46 | NAMESPACE_CIEL_END 47 | 48 | #endif // CIELLAB_INCLUDE_CIEL_CORE_SPINLOCK_HPP_ 49 | -------------------------------------------------------------------------------- /include/ciel/core/is_trivially_relocatable.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_IS_TRIVIALLY_RELOCATABLE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_IS_TRIVIALLY_RELOCATABLE_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | NAMESPACE_CIEL_BEGIN 12 | 13 | // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p1144r10.html 14 | 15 | template 16 | struct is_trivially_relocatable : disjunction, 17 | #ifdef _LIBCPP___TYPE_TRAITS_IS_TRIVIALLY_RELOCATABLE_H 18 | std::__libcpp_is_trivially_relocatable, 19 | #elif __has_builtin(__is_trivially_relocatable) 20 | bool_constant<__is_trivially_relocatable(T)>, 21 | #endif 22 | std::false_type> { 23 | }; 24 | 25 | template 26 | struct is_trivially_relocatable> 27 | : conjunction, is_trivially_relocatable> {}; 28 | 29 | template 30 | struct is_trivially_relocatable> : conjunction...> {}; 31 | 32 | NAMESPACE_CIEL_END 33 | 34 | #endif // CIELLAB_INCLUDE_CIEL_CORE_IS_TRIVIALLY_RELOCATABLE_HPP_ 35 | -------------------------------------------------------------------------------- /include/ciel/compare.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_COMPARE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_COMPARE_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | NAMESPACE_CIEL_BEGIN 10 | 11 | template::value && is_range_with_size::value> = 0> 12 | CIEL_NODISCARD bool operator==(const T& lhs, const U& rhs) noexcept { 13 | return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); 14 | } 15 | 16 | template::value && is_range::value> = 0> 17 | CIEL_NODISCARD bool operator<(const T& lhs, const U& rhs) noexcept { 18 | return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); 19 | } 20 | 21 | template 22 | CIEL_NODISCARD bool operator!=(const T& lhs, const U& rhs) noexcept { 23 | return !(lhs == rhs); 24 | } 25 | 26 | template 27 | CIEL_NODISCARD bool operator>(const T& lhs, const U& rhs) noexcept { 28 | return rhs < lhs; 29 | } 30 | 31 | template 32 | CIEL_NODISCARD bool operator<=(const T& lhs, const U& rhs) noexcept { 33 | return !(rhs < lhs); 34 | } 35 | 36 | template 37 | CIEL_NODISCARD bool operator>=(const T& lhs, const U& rhs) noexcept { 38 | return !(lhs < rhs); 39 | } 40 | 41 | NAMESPACE_CIEL_END 42 | 43 | #endif // CIELLAB_INCLUDE_CIEL_COMPARE_HPP_ 44 | -------------------------------------------------------------------------------- /portable_header_process.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | directory="include/ciel" 4 | headers=() 5 | 6 | for file in "${directory}"/*; do 7 | if [[ -f "${file}" ]]; then 8 | headers+=("$(basename "${file}")") 9 | fi 10 | done 11 | 12 | portable_headers="portable_headers" 13 | 14 | if [ -n "$1" ]; then 15 | portable_headers="$1" 16 | fi 17 | 18 | rm -rf ${portable_headers} 19 | mkdir -p ${portable_headers} 20 | 21 | included_headers=() 22 | 23 | function process() { 24 | local input="$1" 25 | local output="$2" 26 | 27 | if [ ! -f ${input} ]; then 28 | echo "File ${input} doesn't exist." 29 | exit 1 30 | fi 31 | 32 | read -r first_line < ${input} 33 | 34 | if [[ " ${included_headers[@]} " =~ " ${first_line} " ]]; then 35 | return 36 | fi 37 | 38 | included_headers+=("$first_line") 39 | 40 | while IFS= read -r line; do 41 | if [[ $line =~ ^#include[[:space:]]*\ ]]; then 42 | 43 | header_name="${BASH_REMATCH[1]}" 44 | 45 | include_path="include/ciel/${header_name}.hpp" 46 | 47 | process ${include_path} ${output} 48 | else 49 | echo "${line}" >> ${output} 50 | fi 51 | done < "${input}" 52 | } 53 | 54 | for header in "${headers[@]}"; do 55 | echo -n "Processing ${header}... " 56 | 57 | touch ${portable_headers}/${header} 58 | 59 | included_headers=() 60 | 61 | process include/ciel/${header} ${portable_headers}/${header} 62 | 63 | echo "done." 64 | done 65 | -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Checking if clang-format is installed..." 4 | 5 | clang-format --version || { echo "clang-format is not installed." && exit 1; } 6 | 7 | REPO_ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 8 | CLANG_FORMAT_FILE="${REPO_ROOT_DIR}/.clang-format" 9 | 10 | if [ ! -f "${CLANG_FORMAT_FILE}" ]; then 11 | echo ".clang-format not found in the repository root." 12 | exit 1 13 | fi 14 | 15 | if [ "$#" -lt 2 ]; then 16 | echo "Usage: $0 run/check path..." 17 | exit 1 18 | fi 19 | 20 | operation="$1" 21 | shift 22 | 23 | for each_path in "$@"; do 24 | PATHS="${PATHS} ${each_path}" 25 | done 26 | 27 | if [ "${operation}" == "run" ]; then 28 | echo "Formatting..." 29 | 30 | find ${PATHS} -type f \( -name "*.cpp" -or -name "*.c" -or -name "*.cc" -or -name "*.h" -or -name "*.hpp" \) -exec clang-format -i --style=file '{}' \; 31 | 32 | echo "All header and source files have been formatted." 33 | 34 | elif [ "${operation}" == "check" ]; then 35 | echo "Checking..." 36 | 37 | files=$(find ${PATHS} -name "*.cpp" -or -name "*.c" -or -name "*.cc" -or -name "*.h" -or -name "*.hpp") 38 | 39 | for file in ${files}; do 40 | if ! clang-format -style=file --dry-run -Werror "${file}"; then 41 | echo "${file} requires formatting." 42 | exit 1 43 | fi 44 | done 45 | 46 | echo "All header and source files have been formatted." 47 | 48 | else 49 | echo "Unknown operation: ${operation}" 50 | echo "Usage: $0 run/check path..." 51 | exit 1 52 | fi 53 | -------------------------------------------------------------------------------- /test/src/avl_tree.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace ciel; 11 | 12 | TEST(avl_tree, all) { 13 | ciel::vector> v(reserve_capacity, 10000); 14 | for (int i = 0; i < 10000; ++i) { 15 | v.unchecked_emplace_back(i); 16 | } 17 | std::random_device rd; 18 | std::mt19937 g(rd()); 19 | std::shuffle(v.begin(), v.end(), g); 20 | 21 | avl_tree> avl; 22 | for (auto& node : v) { 23 | ASSERT_TRUE(avl.insert_node_unique(&node).second); 24 | } 25 | 26 | avl_node node1{-1}; 27 | avl_node node2{-2}; 28 | avl_node node3{-3}; 29 | ASSERT_TRUE(avl.insert_node_unique(&node1).second); 30 | ASSERT_TRUE(avl.insert_node_unique(&node2).second); 31 | ASSERT_TRUE(avl.insert_node_unique(&node3).second); 32 | 33 | { 34 | ASSERT_EQ(avl.size(), 10003); 35 | auto it = avl.begin(); 36 | for (int i = -3; i < 10000; ++i, ++it) { 37 | ASSERT_EQ(*it, i); 38 | } 39 | ASSERT_EQ(it, avl.end()); 40 | } 41 | 42 | ASSERT_EQ(avl.pop_node(avl.begin()), &node3); 43 | ASSERT_EQ(avl.pop_node(avl.begin()), &node2); 44 | ASSERT_EQ(avl.pop_node(avl.begin()), &node1); 45 | for (auto& node : v) { 46 | auto it = avl.find(node.value); 47 | ASSERT_NE(it, avl.end()); 48 | 49 | ASSERT_EQ(avl.pop_node(it), &node); 50 | } 51 | ASSERT_TRUE(avl.empty()); 52 | } 53 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_SOURCE_DIR := $(abspath ./) 2 | BUILD_DIR ?= $(PROJECT_SOURCE_DIR)/build 3 | 4 | UNAME_S := $(shell uname -s) 5 | ifeq ($(UNAME_S), Linux) 6 | NUM_JOB := $(shell nproc) 7 | else ifeq ($(UNAME_S), Darwin) 8 | NUM_JOB := $(shell sysctl -n hw.ncpu) 9 | else 10 | NUM_JOB := 1 11 | endif 12 | 13 | COMPILER_PATH ?= 14 | COMPILER_FLAGS ?= 15 | 16 | clean: 17 | rm -rf $(BUILD_DIR) && rm -rf third_party/*build 18 | .PHONY: clean 19 | 20 | test: 21 | cmake -S . -B $(BUILD_DIR) -G Ninja $(if $(COMPILER_PATH),-DCMAKE_CXX_COMPILER=$(COMPILER_PATH)) $(if $(COMPILER_FLAGS),-DCMAKE_CXX_FLAGS="$(COMPILER_FLAGS)") 22 | cmake --build $(BUILD_DIR) --target ciellab_test_11_exceptions_on_rtti_on -j $(NUM_JOB) 23 | cmake --build $(BUILD_DIR) --target ciellab_test_20_exceptions_off_rtti_off -j $(NUM_JOB) 24 | $(BUILD_DIR)/test/ciellab_test_11_exceptions_on_rtti_on 25 | $(BUILD_DIR)/test/ciellab_test_20_exceptions_off_rtti_off 26 | .PHONY: test 27 | 28 | benchmark: 29 | cmake -S . -B $(BUILD_DIR) -G Ninja $(if $(COMPILER_PATH),-DCMAKE_CXX_COMPILER=$(COMPILER_PATH)) $(if $(COMPILER_FLAGS),-DCMAKE_CXX_FLAGS="$(COMPILER_FLAGS)") 30 | cmake --build $(BUILD_DIR) --target ciellab_benchmark -j $(NUM_JOB) 31 | $(BUILD_DIR)/benchmark/ciellab_benchmark 32 | .PHONY: benchmark 33 | 34 | format: 35 | ./format.sh run $(PROJECT_SOURCE_DIR)/include $(PROJECT_SOURCE_DIR)/test/src $(PROJECT_SOURCE_DIR)/benchmark/src 36 | .PHONY: format 37 | 38 | check_format: 39 | ./format.sh check $(PROJECT_SOURCE_DIR)/include $(PROJECT_SOURCE_DIR)/test/src $(PROJECT_SOURCE_DIR)/benchmark/src 40 | .PHONY: check_format 41 | -------------------------------------------------------------------------------- /include/ciel/test/operator_hijacker.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_TEST_OPERATOR_HIJACKER_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_TEST_OPERATOR_HIJACKER_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | NAMESPACE_CIEL_BEGIN 11 | 12 | struct operator_hijacker { 13 | CIEL_NODISCARD bool operator==(const operator_hijacker&) const { 14 | return true; 15 | } 16 | 17 | CIEL_NODISCARD bool operator<(const operator_hijacker&) const { 18 | return true; 19 | } 20 | 21 | template 22 | friend void operator&(T&&) = delete; 23 | template 24 | friend void operator,(T&&, U&&) = delete; 25 | template 26 | friend void operator&&(T&&, U&&) = delete; 27 | template 28 | friend void operator||(T&&, U&&) = delete; 29 | 30 | }; // struct operator_hijacker 31 | 32 | template 33 | struct operator_hijacker_allocator : std::allocator, 34 | operator_hijacker { 35 | #if CIEL_STD_VER <= 17 36 | struct rebind { 37 | using other = operator_hijacker_allocator; 38 | }; 39 | #endif 40 | 41 | }; // struct operator_hijacker_allocator 42 | 43 | NAMESPACE_CIEL_END 44 | 45 | namespace std { 46 | 47 | template<> 48 | struct hash { 49 | CIEL_NODISCARD size_t operator()(ciel::operator_hijacker) const noexcept { 50 | return 0; 51 | } 52 | }; 53 | 54 | } // namespace std 55 | 56 | #endif // CIELLAB_INCLUDE_CIEL_TEST_OPERATOR_HIJACKER_HPP_ 57 | -------------------------------------------------------------------------------- /test/src/function/constructor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | using namespace ciel; 11 | 12 | namespace { 13 | 14 | struct TriviallyRelocatable { 15 | vector v{42}; 16 | 17 | void operator()() const noexcept { 18 | CIEL_ASSERT(v.size() == 1); 19 | CIEL_ASSERT(v[0] == 42); 20 | } 21 | }; 22 | 23 | struct NonTriviallyRelocatable { 24 | std::list list{42}; 25 | 26 | void operator()() const noexcept { 27 | auto it = list.begin(); 28 | ++it; 29 | 30 | CIEL_ASSERT(it == list.end()); 31 | } 32 | }; 33 | 34 | } // namespace 35 | 36 | TEST(function, copy_stack) { 37 | const function f1(assume_trivially_relocatable, TriviallyRelocatable{}); 38 | f1(); 39 | 40 | const function f2(f1); 41 | 42 | f1(); 43 | f2(); 44 | } 45 | 46 | TEST(function, copy_heap) { 47 | const function f1(NonTriviallyRelocatable{}); 48 | f1(); 49 | 50 | const function f2(f1); 51 | 52 | f1(); 53 | f2(); 54 | } 55 | 56 | TEST(function, move_stack) { 57 | function f1(assume_trivially_relocatable, TriviallyRelocatable{}); 58 | f1(); 59 | 60 | const function f2(std::move(f1)); 61 | ASSERT_TRUE(!f1); 62 | 63 | f2(); 64 | } 65 | 66 | TEST(function, move_heap) { 67 | function f1(NonTriviallyRelocatable{}); 68 | f1(); 69 | 70 | const function f2(std::move(f1)); 71 | ASSERT_TRUE(!f1); 72 | 73 | f2(); 74 | } 75 | -------------------------------------------------------------------------------- /test/src/function/overload_resolution.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | using namespace ciel; 8 | 9 | namespace { 10 | 11 | struct T1 { 12 | int operator()() noexcept { 13 | return 1; 14 | } 15 | 16 | int operator()() const noexcept { 17 | return 2; 18 | } 19 | 20 | int operator()() volatile noexcept { 21 | return 3; 22 | } 23 | 24 | int operator()() const volatile noexcept { 25 | return 4; 26 | } 27 | }; 28 | 29 | struct T2 { 30 | int operator()() const& { 31 | return 0; 32 | } 33 | 34 | int operator()() const&& { 35 | return 1; 36 | } 37 | }; 38 | 39 | struct T3 { 40 | int operator()() const { 41 | return 0; 42 | } 43 | 44 | int operator&() const { 45 | return 1; 46 | } 47 | }; 48 | 49 | } // namespace 50 | 51 | TEST(function, overload_resolution) { 52 | T1 t1; 53 | const T1 t2; 54 | volatile T1 t3; 55 | const volatile T1 t4; 56 | 57 | const function f1(std::ref(t1)); 58 | ASSERT_EQ(f1(), 1); 59 | 60 | const function f2(std::ref(t2)); 61 | ASSERT_EQ(f2(), 2); 62 | 63 | const function f3(std::ref(t3)); 64 | ASSERT_EQ(f3(), 3); 65 | 66 | const function f4(std::ref(t4)); 67 | ASSERT_EQ(f4(), 4); 68 | } 69 | 70 | TEST(function, overload_resolution_2) { 71 | const T2 t; 72 | 73 | const function f1(t); 74 | ASSERT_EQ(f1(), 0); 75 | 76 | const function f2(T2{}); 77 | ASSERT_EQ(f2(), 0); 78 | } 79 | 80 | TEST(function, overload_resolution_3) { 81 | const T3 t; 82 | 83 | const function f(t); 84 | ASSERT_EQ(f(), 0); 85 | } 86 | -------------------------------------------------------------------------------- /benchmark/src/shared_ptr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static void shared_ptr_inc_dec_ciel(benchmark::State& state) { 7 | ciel::shared_ptr sp = ciel::make_shared(1); 8 | for (auto _ : state) { 9 | auto copy = sp; 10 | 11 | benchmark::DoNotOptimize(copy); 12 | benchmark::ClobberMemory(); 13 | } 14 | } 15 | 16 | static void shared_ptr_inc_dec_std(benchmark::State& state) { 17 | std::shared_ptr sp = std::make_shared(1); 18 | for (auto _ : state) { 19 | auto copy = sp; 20 | 21 | benchmark::DoNotOptimize(copy); 22 | benchmark::ClobberMemory(); 23 | } 24 | } 25 | 26 | BENCHMARK(shared_ptr_inc_dec_ciel)->Threads(std::thread::hardware_concurrency()); 27 | BENCHMARK(shared_ptr_inc_dec_std)->Threads(std::thread::hardware_concurrency()); 28 | 29 | static void shared_ptr_lock_ciel(benchmark::State& state) { 30 | ciel::shared_ptr sp = ciel::make_shared(1); 31 | ciel::weak_ptr wp(sp); 32 | for (auto _ : state) { 33 | auto copy = wp.lock(); 34 | 35 | benchmark::DoNotOptimize(copy); 36 | benchmark::ClobberMemory(); 37 | } 38 | } 39 | 40 | static void shared_ptr_lock_std(benchmark::State& state) { 41 | std::shared_ptr sp = std::make_shared(1); 42 | std::weak_ptr wp(sp); 43 | for (auto _ : state) { 44 | auto copy = wp.lock(); 45 | 46 | benchmark::DoNotOptimize(copy); 47 | benchmark::ClobberMemory(); 48 | } 49 | } 50 | 51 | BENCHMARK(shared_ptr_lock_ciel)->Threads(std::thread::hardware_concurrency()); 52 | BENCHMARK(shared_ptr_lock_std)->Threads(std::thread::hardware_concurrency()); 53 | -------------------------------------------------------------------------------- /include/ciel/experimental/worth_move.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_EXPERIMENTAL_WORTH_MOVE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_EXPERIMENTAL_WORTH_MOVE_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | NAMESPACE_CIEL_BEGIN 9 | 10 | // Inspired by: 11 | // https://stackoverflow.com/questions/51901837/how-to-get-if-a-type-is-truly-move-constructible/51912859#51912859 12 | // 13 | // FIXME: Current implementation returns true for const&& constructor and assignment. 14 | 15 | template 16 | struct worth_move { 17 | static_assert(!std::is_const::value, ""); 18 | 19 | private: 20 | using U = decay_t; 21 | 22 | struct helper { 23 | operator const U&() noexcept; 24 | operator U&&() noexcept; 25 | }; // struct helper 26 | 27 | public: 28 | static constexpr bool construct = std::is_class::value && !std::is_trivially_copyable::value 29 | && std::is_move_constructible::value && !std::is_constructible::value; 30 | static constexpr bool assign = std::is_class::value && !std::is_trivially_copyable::value 31 | && std::is_move_assignable::value && !std::is_assignable::value; 32 | static constexpr bool value = construct || assign; 33 | 34 | }; // struct worth_move 35 | 36 | template 37 | struct worth_move_constructing { 38 | static constexpr bool value = worth_move::construct; 39 | 40 | }; // struct worth_move_constructing 41 | 42 | template 43 | struct worth_move_assigning { 44 | static constexpr bool value = worth_move::assign; 45 | 46 | }; // struct worth_move_assigning 47 | 48 | NAMESPACE_CIEL_END 49 | 50 | #endif // CIELLAB_INCLUDE_CIEL_EXPERIMENTAL_WORTH_MOVE_HPP_ 51 | -------------------------------------------------------------------------------- /include/ciel/core/is_range.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_IS_RANGE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_IS_RANGE_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | NAMESPACE_CIEL_BEGIN 11 | 12 | // is_range 13 | 14 | template 15 | struct is_range : std::false_type {}; 16 | 17 | template 18 | struct is_range().begin())>, void_t().end())>> 19 | : std::true_type {}; 20 | 21 | template 22 | struct is_range_with_size : std::false_type {}; 23 | 24 | template 25 | struct is_range_with_size().begin())>, void_t().end())>, 26 | void_t().size())>> : std::true_type {}; 27 | 28 | template 29 | struct is_range_without_size : bool_constant::value && !is_range_with_size::value> {}; 30 | 31 | // from_range_t 32 | 33 | struct from_range_t {}; 34 | 35 | static constexpr from_range_t from_range; 36 | 37 | // distance for range 38 | 39 | template::value> = 0, class Iter = decltype(std::declval().begin())> 40 | typename std::iterator_traits::difference_type distance(const R& rg) { 41 | return std::distance(rg.begin(), rg.end()); 42 | } 43 | 44 | template::value> = 0, class Iter = decltype(std::declval().begin())> 45 | typename std::iterator_traits::difference_type distance(const R& rg) { 46 | return rg.size(); 47 | } 48 | 49 | NAMESPACE_CIEL_END 50 | 51 | #endif // CIELLAB_INCLUDE_CIEL_CORE_IS_RANGE_HPP_ 52 | -------------------------------------------------------------------------------- /test/src/vector/data.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | using namespace ciel; 10 | 11 | TEST(vector, data) { 12 | { 13 | vector v; 14 | ASSERT_EQ(v.data(), nullptr); 15 | } 16 | { 17 | vector v(100); 18 | ASSERT_EQ(v.data(), std::addressof(v.front())); 19 | } 20 | { 21 | vector v(100); 22 | ASSERT_EQ(v.data(), std::addressof(v.front())); 23 | } 24 | { 25 | vector> v; 26 | ASSERT_EQ(v.data(), nullptr); 27 | } 28 | { 29 | vector> v(100); 30 | ASSERT_EQ(v.data(), &v.front()); 31 | } 32 | { 33 | vector> v(100); 34 | ASSERT_EQ(v.data(), std::addressof(v.front())); 35 | } 36 | } 37 | 38 | TEST(vector, data_const) { 39 | { 40 | const vector v; 41 | ASSERT_EQ(v.data(), nullptr); 42 | } 43 | { 44 | const vector v(100); 45 | ASSERT_EQ(v.data(), std::addressof(v.front())); 46 | } 47 | { 48 | const vector v(100); 49 | ASSERT_EQ(v.data(), std::addressof(v.front())); 50 | } 51 | { 52 | const vector> v; 53 | ASSERT_EQ(v.data(), nullptr); 54 | } 55 | { 56 | const vector> v(100); 57 | ASSERT_EQ(v.data(), &v.front()); 58 | } 59 | { 60 | const vector> v(100); 61 | ASSERT_EQ(v.data(), std::addressof(v.front())); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /include/ciel/core/finally.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_FINALLY_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_FINALLY_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | NAMESPACE_CIEL_BEGIN 10 | 11 | // Inspired by Microsoft GSL's implementation. 12 | // Exception throwing will result in resource leaking, so functions are not supposed to. 13 | 14 | template 15 | class finally { 16 | public: 17 | explicit finally(const F& f) noexcept 18 | : f_(f), valid_(true) {} 19 | 20 | explicit finally(F&& f) noexcept 21 | : f_(std::move(f)), valid_(true) {} 22 | 23 | finally(finally&& other) noexcept 24 | : f_(std::move(other.f_)), valid_(ciel::exchange(other.valid_, false)) {} 25 | 26 | ~finally() { 27 | if (valid_) { 28 | f_(); 29 | } 30 | } 31 | 32 | void release() noexcept { 33 | valid_ = false; 34 | } 35 | 36 | finally(const finally&) = delete; 37 | finally& operator=(const finally&) = delete; 38 | finally& operator=(finally&&) = delete; 39 | 40 | private: 41 | F f_; 42 | bool valid_; 43 | 44 | }; // class finally 45 | 46 | template> 47 | CIEL_NODISCARD finally make_finally(F&& f) noexcept { 48 | return finally(std::forward(f)); 49 | } 50 | 51 | NAMESPACE_CIEL_END 52 | 53 | // It will be "ab" without forwarding, so all variables' names will be defer___LINE__. 54 | // Forwarding get the real line number like defer_12. 55 | #define CIEL_CONCAT_(a, b) a##b 56 | #define CIEL_CONCAT(a, b) CIEL_CONCAT_(a, b) 57 | 58 | #define CIEL_DEFER(x) auto CIEL_CONCAT(defer_, __LINE__) = ciel::make_finally([&] x) 59 | 60 | #endif // CIELLAB_INCLUDE_CIEL_CORE_FINALLY_HPP_ 61 | -------------------------------------------------------------------------------- /test/src/compressed_pair.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace ciel; 6 | 7 | namespace { 8 | 9 | struct Empty {}; 10 | 11 | } // namespace 12 | 13 | TEST(compressed_pair, constructor) { 14 | using IntPair = compressed_pair; 15 | 16 | IntPair p1; 17 | ASSERT_EQ(p1.first(), 0); 18 | ASSERT_EQ(p1.second(), 0); 19 | 20 | p1.first() = 1; 21 | p1.second() = 2; 22 | ::new (&p1) IntPair; 23 | ASSERT_EQ(p1.first(), 0); 24 | ASSERT_EQ(p1.second(), 0); 25 | } 26 | 27 | TEST(compressed_pair, default_init) { 28 | using IntPair = compressed_pair; 29 | 30 | IntPair p1; 31 | p1.first() = 1; 32 | p1.second() = 2; 33 | 34 | ::new (&p1) IntPair(default_init, 3); 35 | ASSERT_EQ(p1.first(), 1); 36 | ASSERT_EQ(p1.second(), 3); 37 | 38 | ::new (&p1) IntPair(4, default_init); 39 | ASSERT_EQ(p1.first(), 4); 40 | ASSERT_EQ(p1.second(), 3); 41 | 42 | ::new (&p1) IntPair(default_init, default_init); 43 | ASSERT_EQ(p1.first(), 4); 44 | ASSERT_EQ(p1.second(), 3); 45 | } 46 | 47 | TEST(compressed_pair, both_same_empty_bases) { 48 | static_assert(sizeof(compressed_pair) == 2, ""); 49 | } 50 | 51 | #ifdef CIEL_HAS_EXCEPTIONS 52 | # include 53 | # include 54 | 55 | TEST(compressed_pair, exception_safety) { 56 | using EG = 57 | ExceptionGenerator<2, DefaultConstructor | CopyConstructor | MoveConstructor | CopyAssignment | MoveAssignment, 58 | false>; 59 | EG::reset(); 60 | EG::enabled = true; 61 | 62 | CIEL_TRY { 63 | const compressed_pair p(1, 2); 64 | 65 | ASSERT_TRUE(false); 66 | } 67 | CIEL_CATCH (...) {} 68 | } 69 | #endif 70 | -------------------------------------------------------------------------------- /benchmark/src/lock.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static void lock_atomic(benchmark::State& state) { 10 | std::atomic counter{0}; 11 | for (auto _ : state) { 12 | ++counter; 13 | 14 | benchmark::DoNotOptimize(counter); 15 | benchmark::ClobberMemory(); 16 | } 17 | } 18 | 19 | static void lock_mutex(benchmark::State& state) { 20 | std::mutex mutex; 21 | size_t counter = 0; 22 | for (auto _ : state) { 23 | { 24 | std::lock_guard lg(mutex); 25 | ++counter; 26 | } 27 | 28 | benchmark::DoNotOptimize(counter); 29 | benchmark::ClobberMemory(); 30 | } 31 | } 32 | 33 | static void lock_spinlock(benchmark::State& state) { 34 | ciel::spinlock lock; 35 | size_t counter = 0; 36 | for (auto _ : state) { 37 | with(lock, [&]() { 38 | ++counter; 39 | }); 40 | 41 | benchmark::DoNotOptimize(counter); 42 | benchmark::ClobberMemory(); 43 | } 44 | } 45 | 46 | static void lock_combininglock(benchmark::State& state) { 47 | ciel::combining_lock lock; 48 | size_t counter = 0; 49 | for (auto _ : state) { 50 | with(lock, [&]() { 51 | ++counter; 52 | }); 53 | 54 | benchmark::DoNotOptimize(counter); 55 | benchmark::ClobberMemory(); 56 | } 57 | } 58 | 59 | BENCHMARK(lock_atomic)->Threads(std::thread::hardware_concurrency()); 60 | BENCHMARK(lock_mutex)->Threads(std::thread::hardware_concurrency()); 61 | BENCHMARK(lock_spinlock)->Threads(std::thread::hardware_concurrency()); 62 | BENCHMARK(lock_combininglock)->Threads(std::thread::hardware_concurrency()); 63 | -------------------------------------------------------------------------------- /test/src/vector/erase.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using namespace ciel; 9 | 10 | namespace { 11 | 12 | template 13 | void test_erase_impl(::testing::Test*) { 14 | // erase single 15 | { 16 | vector v{0, 1, 2, 3, 4}; 17 | { 18 | auto it = v.erase(v.begin()); 19 | ASSERT_EQ(it, v.begin()); 20 | ASSERT_EQ(v, std::initializer_list({1, 2, 3, 4})); 21 | } 22 | { 23 | auto it = v.erase(v.end() - 2); 24 | ASSERT_EQ(it, v.end() - 1); 25 | ASSERT_EQ(v, std::initializer_list({1, 2, 4})); 26 | } 27 | { 28 | auto it = v.erase(v.end() - 1); 29 | ASSERT_EQ(it, v.end()); 30 | ASSERT_EQ(v, std::initializer_list({1, 2})); 31 | } 32 | } 33 | // erase count < pos_end_dis 34 | { 35 | vector v{0, 1, 2, 3, 4}; 36 | auto it = v.erase(v.begin(), v.begin() + 2); 37 | ASSERT_EQ(it, v.begin()); 38 | ASSERT_EQ(v, std::initializer_list({2, 3, 4})); 39 | } 40 | // erase count > pos_end_dis 41 | { 42 | vector v{0, 1, 2, 3, 4}; 43 | auto it = v.erase(v.begin(), v.begin() + 3); 44 | ASSERT_EQ(it, v.begin()); 45 | ASSERT_EQ(v, std::initializer_list({3, 4})); 46 | } 47 | // erase at end 48 | { 49 | vector v{0, 1, 2, 3, 4}; 50 | auto it = v.erase(v.begin(), v.end()); 51 | ASSERT_EQ(it, v.end()); 52 | ASSERT_TRUE(v.empty()); 53 | } 54 | } 55 | 56 | } // namespace 57 | 58 | TEST(vector, erase) { 59 | test_erase_impl(this); 60 | test_erase_impl(this); 61 | test_erase_impl(this); 62 | test_erase_impl(this); 63 | } 64 | -------------------------------------------------------------------------------- /test/src/allocator_traits.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | using namespace ciel; 8 | 9 | namespace { 10 | 11 | template 12 | struct EmptyAllocator { 13 | using value_type = T; 14 | }; 15 | 16 | template 17 | struct Allocator { 18 | using value_type = T; 19 | 20 | template 21 | void construct(U*, Args&&...) const noexcept {} 22 | 23 | template 24 | void destroy(U*) const noexcept {} 25 | }; 26 | 27 | } // namespace 28 | 29 | TEST(allocator_traits, trivial) { 30 | static_assert(allocator_has_trivial_construct, int*>::value, ""); 31 | static_assert(allocator_has_trivial_construct, int*, const int&>::value, ""); 32 | static_assert(allocator_has_trivial_construct, int*, int&&>::value, ""); 33 | static_assert(allocator_has_trivial_destroy, int*>::value, ""); 34 | } 35 | 36 | TEST(allocator_traits, not_trivial) { 37 | static_assert(not allocator_has_trivial_construct, int*>::value, ""); 38 | static_assert(not allocator_has_trivial_construct, int*, const int&>::value, ""); 39 | static_assert(not allocator_has_trivial_construct, int*, int&&>::value, ""); 40 | static_assert(not allocator_has_trivial_destroy, int*>::value, ""); 41 | } 42 | 43 | TEST(allocator_traits, std) { 44 | static_assert(allocator_has_trivial_construct, int*>::value, ""); 45 | static_assert(allocator_has_trivial_construct, int*, const int&>::value, ""); 46 | static_assert(allocator_has_trivial_construct, int*, int&&>::value, ""); 47 | static_assert(allocator_has_trivial_destroy, int*>::value, ""); 48 | } 49 | -------------------------------------------------------------------------------- /include/ciel/core/datasizeof.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_DATASIZEOF_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_DATASIZEOF_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | NAMESPACE_CIEL_BEGIN 10 | 11 | // Derived can reuse Base's tail padding. 12 | // e.g. 13 | // struct Base { 14 | // alignas(8) unsigned char buf[1]{}; 15 | // }; 16 | // struct Derived : Base { 17 | // int i{}; 18 | // }; 19 | // static_assert(sizeof(Base) == 8, ""); 20 | // static_assert(sizeof(Derived) == 8, ""); 21 | // 22 | // Swapping for trivially relocatable types can be performed using std::memcpy, 23 | // in which case there is no need to modify the tail padding. 24 | 25 | // Inspired by LLVM libc++'s implementation. 26 | 27 | CIEL_DIAGNOSTIC_PUSH 28 | CIEL_CLANG_DIAGNOSTIC_IGNORED("-Winvalid-offsetof") 29 | CIEL_GCC_DIAGNOSTIC_IGNORED("-Winvalid-offsetof") 30 | 31 | #if CIEL_STD_VER >= 20 32 | template 33 | struct datasizeof { 34 | struct FirstPaddingByte { 35 | [[no_unique_address]] T v; 36 | char first_padding_byte; 37 | }; 38 | 39 | static constexpr size_t value = offsetof(FirstPaddingByte, first_padding_byte); 40 | 41 | }; // struct datasizeof 42 | #else 43 | template 44 | struct datasizeof { 45 | template::value && !is_final::value> 46 | struct FirstPaddingByte { 47 | U v; 48 | char first_padding_byte; 49 | }; 50 | 51 | template 52 | struct FirstPaddingByte : U { 53 | char first_padding_byte; 54 | }; 55 | 56 | static constexpr size_t value = offsetof(FirstPaddingByte, first_padding_byte); 57 | 58 | }; // struct datasizeof 59 | #endif 60 | 61 | CIEL_DIAGNOSTIC_POP 62 | 63 | NAMESPACE_CIEL_END 64 | 65 | #endif // CIELLAB_INCLUDE_CIEL_CORE_DATASIZEOF_HPP_ 66 | -------------------------------------------------------------------------------- /include/ciel/core/array.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_ARRAY_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_ARRAY_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | NAMESPACE_CIEL_BEGIN 11 | 12 | template 13 | class array { 14 | private: 15 | std::array arr_; 16 | 17 | public: 18 | CIEL_NODISCARD CIEL_CONSTEXPR_SINCE_CXX17 T& operator[](size_t index) noexcept { 19 | CIEL_ASSERT_M(Begin <= index && index < End, "Call ciel::array::operator[] at index: {}", Begin, End, 20 | index); 21 | 22 | return arr_[index - Begin]; 23 | } 24 | 25 | CIEL_NODISCARD CIEL_CONSTEXPR_SINCE_CXX14 const T& operator[](size_t index) const noexcept { 26 | CIEL_ASSERT_M(Begin <= index && index < End, "Call ciel::array::operator[] at index: {}", Begin, End, 27 | index); 28 | 29 | return arr_[index - Begin]; 30 | } 31 | 32 | CIEL_NODISCARD CIEL_CONSTEXPR_SINCE_CXX17 T* begin() noexcept { 33 | return arr_.data(); 34 | } 35 | 36 | CIEL_NODISCARD CIEL_CONSTEXPR_SINCE_CXX17 const T* begin() const noexcept { 37 | return arr_.data(); 38 | } 39 | 40 | CIEL_NODISCARD CIEL_CONSTEXPR_SINCE_CXX17 T* end() noexcept { 41 | return arr_.data() + arr_.size(); 42 | } 43 | 44 | CIEL_NODISCARD CIEL_CONSTEXPR_SINCE_CXX17 const T* end() const noexcept { 45 | return arr_.data() + arr_.size(); 46 | } 47 | 48 | CIEL_NODISCARD constexpr size_t size_begin() const noexcept { 49 | return Begin; 50 | } 51 | 52 | CIEL_NODISCARD constexpr size_t size_end() const noexcept { 53 | return End; 54 | } 55 | 56 | }; // class array 57 | 58 | NAMESPACE_CIEL_END 59 | 60 | #endif // CIELLAB_INCLUDE_CIEL_CORE_ARRAY_HPP_ 61 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: '-*, 2 | bugprone-assert-side-effect, 3 | bugprone-bool-pointer-implicit-conversion, 4 | bugprone-copy-constructor-init, 5 | bugprone-forward-declaration-namespace, 6 | bugprone-forwarding-reference-overload, 7 | bugprone-macro-repeated-side-effects, 8 | bugprone-move-forwarding-reference, 9 | bugprone-misplaced-widening-cast, 10 | bugprone-swapped-arguments, 11 | bugprone-undelegated-constructor, 12 | bugprone-unused-raii, 13 | bugprone-use-after-move, 14 | clang-diagnostic-*, 15 | cppcoreguidelines-virtual-class-destructor, 16 | cppcoreguidelines-slicing, 17 | cppcoreguidelines-pro-type-member-init, 18 | cppcoreguidelines-no-suspend-with-lock, 19 | cppcoreguidelines-prefer-member-initializer, 20 | cppcoreguidelines-narrowing-conversions, 21 | cppcoreguidelines-misleading-capture-default-by-value, 22 | cppcoreguidelines-interfaces-global-init, 23 | cppcoreguidelines-init-variables, 24 | cppcoreguidelines-avoid-reference-coroutine-parameters, 25 | cppcoreguidelines-avoid-non-const-global-variables, 26 | cppcoreguidelines-avoid-const-or-ref-data-members, 27 | cppcoreguidelines-avoid-capturing-lambda-coroutines, 28 | google-readability-casting, 29 | llvm-include-order, 30 | llvm-namespace-comment, 31 | misc-*, 32 | -misc-misplaced-const, 33 | -misc-non-private-member-variables-in-classes, 34 | -misc-no-recursion, 35 | modernize-loop-convert, 36 | modernize-redundant-void-arg, 37 | modernize-return-braced-init-list, 38 | modernize-use-default-member-init, 39 | modernize-use-equals-default, 40 | modernize-use-equals-delete, 41 | modernize-use-nodiscard, 42 | modernize-use-nullptr, 43 | modernize-use-override, 44 | modernize-use-using, 45 | performance-noexcept-move-constructor, 46 | performance-unnecessary-copy-initialization, 47 | readability-braces-around-statements, 48 | readability-else-after-return, 49 | readability-non-const-parameter 50 | ' 51 | 52 | HeaderFilterRegex: 'include/ciel' 53 | UseColor: true 54 | -------------------------------------------------------------------------------- /test/src/inplace_vector/erase.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using namespace ciel; 9 | 10 | namespace { 11 | 12 | template 13 | void test_erase_impl(::testing::Test*) { 14 | // erase single 15 | { 16 | inplace_vector v{0, 1, 2, 3, 4}; 17 | { 18 | auto it = v.erase(v.begin()); 19 | ASSERT_EQ(it, v.begin()); 20 | ASSERT_EQ(v, std::initializer_list({1, 2, 3, 4})); 21 | } 22 | { 23 | auto it = v.erase(v.end() - 2); 24 | ASSERT_EQ(it, v.end() - 1); 25 | ASSERT_EQ(v, std::initializer_list({1, 2, 4})); 26 | } 27 | { 28 | auto it = v.erase(v.end() - 1); 29 | ASSERT_EQ(it, v.end()); 30 | ASSERT_EQ(v, std::initializer_list({1, 2})); 31 | } 32 | } 33 | // erase count < pos_end_dis 34 | { 35 | inplace_vector v{0, 1, 2, 3, 4}; 36 | auto it = v.erase(v.begin(), v.begin() + 2); 37 | ASSERT_EQ(it, v.begin()); 38 | ASSERT_EQ(v, std::initializer_list({2, 3, 4})); 39 | } 40 | // erase count > pos_end_dis 41 | { 42 | inplace_vector v{0, 1, 2, 3, 4}; 43 | auto it = v.erase(v.begin(), v.begin() + 3); 44 | ASSERT_EQ(it, v.begin()); 45 | ASSERT_EQ(v, std::initializer_list({3, 4})); 46 | } 47 | // erase at end 48 | { 49 | inplace_vector v{0, 1, 2, 3, 4}; 50 | auto it = v.erase(v.begin(), v.end()); 51 | ASSERT_EQ(it, v.end()); 52 | ASSERT_TRUE(v.empty()); 53 | } 54 | } 55 | 56 | } // namespace 57 | 58 | TEST(inplace_vector, erase) { 59 | test_erase_impl(this); 60 | test_erase_impl(this); 61 | test_erase_impl(this); 62 | test_erase_impl(this); 63 | } 64 | -------------------------------------------------------------------------------- /test/src/vector/swap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using namespace ciel; 9 | 10 | namespace { 11 | 12 | template 13 | void test_swap_impl(::testing::Test*, C& lhs, C& rhs) { 14 | const auto lhs_copy = lhs; 15 | const auto rhs_copy = rhs; 16 | 17 | lhs.swap(rhs); 18 | 19 | ASSERT_EQ(lhs.size(), 200); 20 | ASSERT_EQ(rhs.size(), 100); 21 | 22 | if (std::allocator_traits::propagate_on_container_swap::value) { 23 | ASSERT_EQ(lhs.get_allocator(), rhs_copy.get_allocator()); 24 | ASSERT_EQ(rhs.get_allocator(), lhs_copy.get_allocator()); 25 | } 26 | } 27 | 28 | } // namespace 29 | 30 | TEST(vector, swap) { 31 | { 32 | // propagate_on_container_swap: false_type, equal 33 | vector> l(100, non_pocs_allocator(5)); 34 | vector> l2(200, non_pocs_allocator(5)); 35 | test_swap_impl(this, l, l2); 36 | } 37 | { 38 | // propagate_on_container_swap: false_type, unequal 39 | vector> l(100, non_pocs_allocator(5)); 40 | vector> l2(200, non_pocs_allocator(3)); 41 | test_swap_impl(this, l, l2); 42 | } 43 | { 44 | // propagate_on_container_swap: true_type, equal 45 | vector> l(100, pocs_allocator(5)); 46 | vector> l2(200, pocs_allocator(5)); 47 | test_swap_impl(this, l, l2); 48 | } 49 | { 50 | // propagate_on_container_swap: true_type, unequal 51 | vector> l(100, pocs_allocator(5)); 52 | vector> l2(200, pocs_allocator(3)); 53 | test_swap_impl(this, l, l2); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /include/ciel/core/packed_ptr.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_PACKED_PTR_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_PACKED_PTR_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | NAMESPACE_CIEL_BEGIN 12 | 13 | template 14 | class alignas(size_t) packed_ptr { 15 | private: 16 | uintptr_t ptr_ : 48; 17 | size_t count_ : 16; 18 | 19 | public: 20 | packed_ptr(T* ptr = nullptr, const size_t count = 0) noexcept 21 | : ptr_(reinterpret_cast(ptr)), count_(count) { 22 | CIEL_ASSERT(reinterpret_cast(ptr) < (1ULL << 48)); 23 | // For use cases like ABA, It might still be ok when count exceeds 2^16. 24 | // CIEL_ASSERT(count < (1ULL << 16)); 25 | } 26 | 27 | CIEL_NODISCARD T* ptr() const noexcept { 28 | return reinterpret_cast(ptr_); 29 | } 30 | 31 | CIEL_NODISCARD size_t count() const noexcept { 32 | return count_; 33 | } 34 | 35 | void set_ptr(T* ptr) noexcept { 36 | ptr_ = reinterpret_cast(ptr); 37 | } 38 | 39 | void set_count(const size_t count) noexcept { 40 | count_ = count; 41 | } 42 | 43 | void increment_count() noexcept { 44 | ++count_; 45 | } 46 | 47 | void decrement_count() noexcept { 48 | --count_; 49 | } 50 | 51 | CIEL_NODISCARD friend bool operator==(const packed_ptr& lhs, const packed_ptr& rhs) noexcept { 52 | return lhs.ptr() == rhs.ptr() && lhs.count() == rhs.count(); 53 | } 54 | 55 | CIEL_NODISCARD friend bool operator!=(const packed_ptr& lhs, const packed_ptr& rhs) noexcept { 56 | return !(lhs == rhs); 57 | } 58 | 59 | }; // class packed_ptr 60 | 61 | template 62 | using atomic_packed_ptr = std::atomic>; 63 | 64 | NAMESPACE_CIEL_END 65 | 66 | #endif // CIELLAB_INCLUDE_CIEL_CORE_PACKED_PTR_HPP_ 67 | -------------------------------------------------------------------------------- /include/ciel/test/forward_iterator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_TEST_FORWARD_ITERATOR_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_TEST_FORWARD_ITERATOR_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | NAMESPACE_CIEL_BEGIN 12 | 13 | // Simulate forward_iterator using T array base. 14 | 15 | template 16 | class ForwardIterator : public input_iterator_base> { 17 | public: 18 | using difference_type = ptrdiff_t; 19 | using value_type = T; 20 | using pointer = T*; 21 | using reference = T&; 22 | using iterator_category = std::forward_iterator_tag; 23 | using iterator_concept = std::forward_iterator_tag; 24 | 25 | private: 26 | pointer ptr; 27 | 28 | public: 29 | ForwardIterator() = default; 30 | 31 | ForwardIterator(const pointer p) noexcept 32 | : ptr(p) {} 33 | 34 | void go_next() noexcept { 35 | CIEL_ASSERT(ptr != nullptr); 36 | ++ptr; 37 | } 38 | 39 | CIEL_NODISCARD reference operator*() const noexcept { 40 | CIEL_ASSERT(ptr != nullptr); 41 | return *ptr; 42 | } 43 | 44 | CIEL_NODISCARD pointer operator->() const noexcept { 45 | CIEL_ASSERT(ptr != nullptr); 46 | return ptr; 47 | } 48 | 49 | CIEL_NODISCARD pointer base() const noexcept { 50 | return ptr; 51 | } 52 | 53 | CIEL_NODISCARD friend bool operator==(const ForwardIterator& lhs, const ForwardIterator& rhs) noexcept { 54 | return lhs.base() == rhs.base(); 55 | } 56 | 57 | template 58 | void operator,(const U&) = delete; 59 | 60 | }; // class ForwardIterator 61 | 62 | #if CIEL_STD_VER >= 20 63 | static_assert(std::forward_iterator>); 64 | #endif 65 | 66 | NAMESPACE_CIEL_END 67 | 68 | #endif // CIELLAB_INCLUDE_CIEL_TEST_FORWARD_ITERATOR_HPP_ 69 | -------------------------------------------------------------------------------- /include/ciel/test/range.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_TEST_RANGE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_TEST_RANGE_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | NAMESPACE_CIEL_BEGIN 9 | 10 | template 11 | class range { 12 | static_assert(!std::is_reference::value, ""); 13 | 14 | private: 15 | Iter begin_; 16 | Iter end_; 17 | 18 | public: 19 | range(Iter begin, Iter end) noexcept 20 | : begin_(begin), end_(end) {} 21 | 22 | range(const range&) = default; 23 | range& operator=(const range&) = default; 24 | 25 | CIEL_NODISCARD Iter begin() const noexcept { 26 | return begin_; 27 | } 28 | 29 | CIEL_NODISCARD Iter end() const noexcept { 30 | return end_; 31 | } 32 | 33 | }; // class range 34 | 35 | template 36 | class range { 37 | static_assert(!std::is_reference::value, ""); 38 | 39 | private: 40 | Iter begin_; 41 | Iter end_; 42 | size_t size_; 43 | 44 | public: 45 | range(Iter begin, Iter end, size_t size) noexcept 46 | : begin_(begin), end_(end), size_(size) {} 47 | 48 | range(const range&) = default; 49 | range& operator=(const range&) = default; 50 | 51 | CIEL_NODISCARD Iter begin() const noexcept { 52 | return begin_; 53 | } 54 | 55 | CIEL_NODISCARD Iter end() const noexcept { 56 | return end_; 57 | } 58 | 59 | CIEL_NODISCARD size_t size() const noexcept { 60 | return size_; 61 | } 62 | 63 | }; // class range 64 | 65 | template 66 | CIEL_NODISCARD range make_range(Iter begin, Iter end) noexcept { 67 | return range(begin, end); 68 | } 69 | 70 | template 71 | CIEL_NODISCARD range make_range(Iter begin, Iter end, size_t size) noexcept { 72 | return range(begin, end, size); 73 | } 74 | 75 | NAMESPACE_CIEL_END 76 | 77 | #endif // CIELLAB_INCLUDE_CIEL_TEST_RANGE_HPP_ 78 | -------------------------------------------------------------------------------- /include/ciel/demangle.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_DEMANGLE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_DEMANGLE_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #if __has_include() 11 | # define CIEL_HAS_CXXABI_H 12 | # include 13 | #endif 14 | 15 | NAMESPACE_CIEL_BEGIN 16 | 17 | // Inspired by Boost: https://github.com/boostorg/core/blob/develop/include/boost/core/demangle.hpp 18 | 19 | #ifdef CIEL_HAS_CXXABI_H 20 | CIEL_NODISCARD inline const char* demangle_alloc(const char* name) noexcept { 21 | size_t size = 0; 22 | int status = 0; 23 | return abi::__cxa_demangle(name, nullptr, &size, &status); 24 | } 25 | 26 | inline void demangle_free(const char* name) noexcept { 27 | std::free(const_cast(name)); 28 | } 29 | #else 30 | CIEL_NODISCARD inline const char* demangle_alloc(const char* name) noexcept { 31 | return name; 32 | } 33 | 34 | inline void demangle_free(const char*) noexcept {} 35 | #endif 36 | 37 | class scoped_demangled_name { 38 | private: 39 | const char* m_p; 40 | 41 | public: 42 | explicit scoped_demangled_name(const char* name) noexcept 43 | : m_p(demangle_alloc(name)) {} 44 | 45 | scoped_demangled_name(const scoped_demangled_name&) = delete; 46 | scoped_demangled_name& operator=(const scoped_demangled_name&) = delete; 47 | 48 | ~scoped_demangled_name() noexcept { 49 | demangle_free(m_p); 50 | } 51 | 52 | CIEL_NODISCARD const char* get() const noexcept { 53 | return m_p; 54 | } 55 | 56 | }; // class scoped_demangled_name 57 | 58 | #ifdef CIEL_HAS_CXXABI_H 59 | CIEL_NODISCARD inline std::string demangle(const char* name) { 60 | const scoped_demangled_name demangled_name(name); 61 | const char* p = demangled_name.get(); 62 | if (!p) { 63 | p = name; 64 | } 65 | return p; 66 | } 67 | #else 68 | CIEL_NODISCARD inline std::string demangle(const char* name) { 69 | return name; 70 | } 71 | #endif 72 | 73 | NAMESPACE_CIEL_END 74 | 75 | #endif // CIELLAB_INCLUDE_CIEL_DEMANGLE_HPP_ 76 | -------------------------------------------------------------------------------- /include/ciel/core/iterator_category.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_ITERATOR_CATEGORY_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_ITERATOR_CATEGORY_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | NAMESPACE_CIEL_BEGIN 10 | 11 | // is_exactly_input_iterator 12 | 13 | template 14 | struct is_exactly_input_iterator : std::false_type {}; 15 | 16 | template 17 | struct is_exactly_input_iterator::iterator_category>> 18 | : std::is_same::iterator_category, std::input_iterator_tag> {}; 19 | 20 | // is_forward_iterator 21 | 22 | template 23 | struct is_forward_iterator : std::false_type {}; 24 | 25 | template 26 | struct is_forward_iterator::iterator_category>> 27 | : std::is_convertible::iterator_category, std::forward_iterator_tag> {}; 28 | 29 | // is_input_iterator 30 | 31 | template 32 | struct is_input_iterator : std::false_type {}; 33 | 34 | template 35 | struct is_input_iterator::iterator_category>> 36 | : std::is_convertible::iterator_category, std::input_iterator_tag> {}; 37 | 38 | // is_contiguous_iterator 39 | 40 | template 41 | struct is_contiguous_iterator : std::false_type {}; 42 | 43 | #if CIEL_STD_VER >= 20 44 | template 45 | struct is_contiguous_iterator::iterator_concept>> 46 | : std::is_convertible::iterator_concept, std::contiguous_iterator_tag> {}; 47 | #endif 48 | 49 | template 50 | struct is_contiguous_iterator : std::true_type {}; 51 | 52 | template 53 | struct is_contiguous_iterator> : is_contiguous_iterator {}; 54 | 55 | NAMESPACE_CIEL_END 56 | 57 | #endif // CIELLAB_INCLUDE_CIEL_CORE_ITERATOR_CATEGORY_HPP_ 58 | -------------------------------------------------------------------------------- /include/ciel/core/alignment.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_ALIGNMENT_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_ALIGNMENT_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | NAMESPACE_CIEL_BEGIN 11 | 12 | // is_pow2 13 | 14 | CIEL_NODISCARD CIEL_CONSTEXPR_SINCE_CXX20 inline bool is_pow2(const size_t x) noexcept { 15 | CIEL_ASSERT(x != 0); 16 | 17 | return (x & (x - 1)) == 0; 18 | } 19 | 20 | // is_aligned 21 | 22 | CIEL_NODISCARD CIEL_CONSTEXPR_SINCE_CXX20 inline bool is_aligned(const uintptr_t ptr, const size_t alignment) noexcept { 23 | CIEL_ASSERT(ptr != 0); 24 | CIEL_ASSERT(ciel::is_pow2(alignment)); 25 | 26 | return ptr % alignment == 0; 27 | } 28 | 29 | CIEL_NODISCARD inline bool is_aligned(const void* ptr, const size_t alignment) noexcept { 30 | return ciel::is_aligned(reinterpret_cast(ptr), alignment); 31 | } 32 | 33 | // align_up 34 | 35 | CIEL_NODISCARD CIEL_CONSTEXPR_SINCE_CXX20 inline uintptr_t align_up(const uintptr_t sz, 36 | const size_t alignment) noexcept { 37 | CIEL_ASSERT(ciel::is_pow2(alignment)); 38 | 39 | const uintptr_t mask = alignment - 1; 40 | 41 | return (sz + mask) & ~mask; 42 | } 43 | 44 | // align_down 45 | 46 | CIEL_NODISCARD CIEL_CONSTEXPR_SINCE_CXX20 inline uintptr_t align_down(const uintptr_t sz, 47 | const size_t alignment) noexcept { 48 | CIEL_ASSERT(ciel::is_pow2(alignment)); 49 | 50 | const uintptr_t mask = alignment - 1; 51 | 52 | return sz & ~mask; 53 | } 54 | 55 | // max_align 56 | 57 | static constexpr size_t max_align = 58 | #ifdef __STDCPP_DEFAULT_NEW_ALIGNMENT__ 59 | __STDCPP_DEFAULT_NEW_ALIGNMENT__ 60 | #else 61 | alignof(std::max_align_t) 62 | #endif 63 | ; 64 | 65 | // is_overaligned_for_new 66 | 67 | CIEL_NODISCARD inline bool is_overaligned_for_new(const size_t alignment) noexcept { 68 | return alignment > max_align; 69 | } 70 | 71 | NAMESPACE_CIEL_END 72 | 73 | #endif // CIELLAB_INCLUDE_CIEL_CORE_ALIGNMENT_HPP_ 74 | -------------------------------------------------------------------------------- /include/ciel/allocator_traits.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_ALLOCATOR_TRAITS_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_ALLOCATOR_TRAITS_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | NAMESPACE_CIEL_BEGIN 12 | 13 | namespace detail { 14 | 15 | // allocator_has_construct 16 | 17 | template 18 | struct allocator_has_construct_impl : std::false_type {}; 19 | 20 | template 21 | struct allocator_has_construct_impl().construct(std::declval()...))>, Alloc, 22 | Args...> : std::true_type {}; 23 | 24 | template 25 | struct allocator_has_construct : allocator_has_construct_impl {}; 26 | 27 | // allocator_has_destroy 28 | 29 | template 30 | struct allocator_has_destroy : std::false_type {}; 31 | 32 | template 33 | struct allocator_has_destroy().destroy(std::declval()))>> 34 | : std::true_type {}; 35 | 36 | } // namespace detail 37 | 38 | // allocator_has_trivial_construct 39 | // allocator_has_trivial_destroy 40 | // Note that Pointer type may not be same as the pointer to typename Alloc::value_type. 41 | 42 | template 43 | struct allocator_has_trivial_construct : negation> {}; 44 | 45 | template 46 | struct allocator_has_trivial_destroy : negation> {}; 47 | 48 | // specializations for std::allocator 49 | 50 | template 51 | struct allocator_has_trivial_construct, Pointer, Args...> : std::true_type {}; 52 | 53 | template 54 | struct allocator_has_trivial_destroy, Pointer> : std::true_type {}; 55 | 56 | NAMESPACE_CIEL_END 57 | 58 | #endif // CIELLAB_INCLUDE_CIEL_ALLOCATOR_TRAITS_HPP_ 59 | -------------------------------------------------------------------------------- /include/ciel/test/input_iterator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_TEST_INPUT_ITERATOR_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_TEST_INPUT_ITERATOR_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | NAMESPACE_CIEL_BEGIN 12 | 13 | // Simulate input_iterator using T array base. 14 | 15 | template 16 | class InputIterator : public input_iterator_base> { 17 | public: 18 | using difference_type = ptrdiff_t; 19 | using value_type = T; 20 | using pointer = T*; 21 | using reference = T&; 22 | using iterator_category = std::input_iterator_tag; 23 | using iterator_concept = std::input_iterator_tag; 24 | 25 | private: 26 | pointer ptr; 27 | 28 | static value_type invalid() noexcept { 29 | return value_type{-1234}; 30 | } 31 | 32 | public: 33 | InputIterator() = default; 34 | 35 | InputIterator(const pointer p) noexcept 36 | : ptr(p) {} 37 | 38 | void go_next() noexcept { 39 | CIEL_ASSERT(ptr != nullptr); 40 | CIEL_ASSERT(*ptr != invalid()); 41 | if (*ptr != -1) { 42 | *ptr = invalid(); 43 | } 44 | ++ptr; 45 | } 46 | 47 | CIEL_NODISCARD reference operator*() const noexcept { 48 | CIEL_ASSERT(ptr != nullptr); 49 | CIEL_ASSERT(*ptr != invalid()); 50 | return *ptr; 51 | } 52 | 53 | CIEL_NODISCARD pointer operator->() const noexcept { 54 | CIEL_ASSERT(ptr != nullptr); 55 | CIEL_ASSERT(*ptr != invalid()); 56 | return ptr; 57 | } 58 | 59 | CIEL_NODISCARD pointer base() const noexcept { 60 | return ptr; 61 | } 62 | 63 | CIEL_NODISCARD friend bool operator==(const InputIterator& lhs, const InputIterator& rhs) noexcept { 64 | return lhs.base() == rhs.base(); 65 | } 66 | 67 | template 68 | void operator,(const U&) = delete; 69 | 70 | }; // class InputIterator 71 | 72 | #if CIEL_STD_VER >= 20 73 | static_assert(std::input_iterator>); 74 | #endif 75 | 76 | NAMESPACE_CIEL_END 77 | 78 | #endif // CIELLAB_INCLUDE_CIEL_TEST_INPUT_ITERATOR_HPP_ 79 | -------------------------------------------------------------------------------- /include/ciel/core/singleton.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_SINGLETON_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_SINGLETON_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | NAMESPACE_CIEL_BEGIN 10 | 11 | // Inspired by Microsoft snmalloc's implementation. 12 | 13 | // Derived must be defaultly constructible. 14 | template 15 | class singleton { 16 | protected: 17 | singleton() = default; 18 | 19 | enum struct State { 20 | Uninitialised = 0, 21 | Initialising, 22 | Initialised 23 | 24 | }; // enum struct State 25 | 26 | public: 27 | singleton(const singleton&) = delete; 28 | singleton& operator=(const singleton&) = delete; 29 | 30 | CIEL_NODISCARD static Derived& get() { 31 | static std::atomic state; 32 | alignas(Derived) static unsigned char buffer[sizeof(Derived)]; 33 | static Derived* res; 34 | 35 | State s = state.load(std::memory_order_acquire); 36 | 37 | while (s != State::Initialised) { 38 | // s is either Uninitialised or Initialising. If s is Uninitialised, 39 | // it will either win CAS and perform construction of Derived 40 | // or lose CAS and load as Initialising or Initialised. 41 | if (s == State::Uninitialised 42 | && state.compare_exchange_strong(s, State::Initialising, std::memory_order_relaxed)) { 43 | CIEL_TRY { 44 | res = ::new (&buffer) Derived; 45 | } 46 | CIEL_CATCH (...) { 47 | state.store(State::Uninitialised, std::memory_order_release); 48 | CIEL_THROW; 49 | } 50 | 51 | state.store(State::Initialised, std::memory_order_release); 52 | 53 | } else { 54 | while (s == State::Initialising) { 55 | std::this_thread::yield(); 56 | s = state.load(std::memory_order_acquire); 57 | } 58 | } 59 | } 60 | 61 | return *res; 62 | } 63 | 64 | }; // class singleton 65 | 66 | NAMESPACE_CIEL_END 67 | 68 | #endif // CIELLAB_INCLUDE_CIEL_CORE_SINGLETON_HPP_ 69 | -------------------------------------------------------------------------------- /include/ciel/core/iterator_base.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_ITERATOR_BASE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_ITERATOR_BASE_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | NAMESPACE_CIEL_BEGIN 9 | 10 | template 11 | struct input_iterator_base { 12 | Derived& operator++() noexcept { 13 | Derived& self = static_cast(*this); 14 | self.go_next(); 15 | return self; 16 | } 17 | 18 | CIEL_NODISCARD Derived operator++(int) noexcept { 19 | Derived& self = static_cast(*this); 20 | Derived res(self); 21 | ++self; 22 | return res; 23 | } 24 | 25 | }; // struct input_iterator_base 26 | 27 | template 28 | struct bidirectional_iterator_base : input_iterator_base { 29 | Derived& operator--() noexcept { 30 | Derived& self = static_cast(*this); 31 | self.go_prev(); 32 | return self; 33 | } 34 | 35 | CIEL_NODISCARD Derived operator--(int) noexcept { 36 | Derived& self = static_cast(*this); 37 | Derived res(self); 38 | --self; 39 | return res; 40 | } 41 | 42 | }; // struct bidirectional_iterator_base 43 | 44 | template 45 | struct random_access_iterator_base : bidirectional_iterator_base { 46 | using difference_type = ptrdiff_t; 47 | 48 | Derived& operator+=(difference_type n) noexcept { 49 | Derived& self = static_cast(*this); 50 | self.advance(n); 51 | return self; 52 | } 53 | 54 | Derived& operator-=(difference_type n) noexcept { 55 | Derived& self = static_cast(*this); 56 | return self += -n; 57 | } 58 | 59 | CIEL_NODISCARD Derived operator+(difference_type n) noexcept { 60 | Derived& self = static_cast(*this); 61 | Derived res(self); 62 | res += n; 63 | return res; 64 | } 65 | 66 | CIEL_NODISCARD Derived operator-(difference_type n) noexcept { 67 | Derived& self = static_cast(*this); 68 | Derived res(self); 69 | res -= n; 70 | return res; 71 | } 72 | 73 | }; // struct random_access_iterator_base 74 | 75 | NAMESPACE_CIEL_END 76 | 77 | #endif // CIELLAB_INCLUDE_CIEL_CORE_ITERATOR_BASE_HPP_ 78 | -------------------------------------------------------------------------------- /test/src/singleton.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace ciel; 14 | 15 | namespace { 16 | 17 | struct NonThrow : singleton { 18 | static std::atomic counter; 19 | 20 | NonThrow() noexcept { 21 | ++counter; 22 | } 23 | }; 24 | 25 | std::atomic NonThrow::counter{0}; 26 | 27 | #ifdef CIEL_HAS_EXCEPTIONS 28 | std::random_device rd; 29 | std::mt19937_64 g(rd()); 30 | 31 | struct CanThrow : singleton { 32 | static std::atomic counter; 33 | 34 | CanThrow() { 35 | if (g() % 20 > 0) { 36 | throw 0; 37 | } 38 | 39 | ++counter; 40 | } 41 | }; 42 | 43 | std::atomic CanThrow::counter{0}; 44 | #endif 45 | 46 | } // namespace 47 | 48 | TEST(singleton, non_throw) { 49 | constexpr size_t threads_num = 64; 50 | 51 | SimpleLatch go{threads_num}; 52 | ciel::vector threads(reserve_capacity, threads_num); 53 | 54 | for (size_t i = 0; i < threads_num; ++i) { 55 | threads.unchecked_emplace_back([&] { 56 | go.arrive_and_wait(); 57 | 58 | CIEL_UNUSED(NonThrow::get()); 59 | }); 60 | } 61 | 62 | for (auto& t : threads) { 63 | t.join(); 64 | } 65 | 66 | ASSERT_EQ(NonThrow::counter, 1); 67 | } 68 | 69 | #ifdef CIEL_HAS_EXCEPTIONS 70 | TEST(singleton, can_throw) { 71 | constexpr size_t threads_num = 64; 72 | 73 | SimpleLatch go{threads_num}; 74 | ciel::vector threads(reserve_capacity, threads_num); 75 | 76 | std::atomic throws{0}; 77 | 78 | for (size_t i = 0; i < threads_num; ++i) { 79 | threads.unchecked_emplace_back([&] { 80 | go.arrive_and_wait(); 81 | 82 | try { 83 | CIEL_UNUSED(CanThrow::get()); 84 | 85 | } catch (...) { 86 | ++throws; 87 | } 88 | }); 89 | } 90 | 91 | for (auto& t : threads) { 92 | t.join(); 93 | } 94 | 95 | ASSERT_TRUE(CanThrow::counter == 1 || throws == threads_num) 96 | << "counter: " << CanThrow::counter << ", throws: " << throws; 97 | } 98 | #endif 99 | -------------------------------------------------------------------------------- /include/ciel/core/integer_sequence.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_INTEGER_SEQUENCE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_INTEGER_SEQUENCE_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | NAMESPACE_CIEL_BEGIN 9 | 10 | template 11 | struct integer_sequence { 12 | static_assert(std::is_integral::value, ""); 13 | 14 | using value_type = T; 15 | 16 | static constexpr size_t size() noexcept { 17 | return sizeof...(Ints); 18 | } 19 | 20 | }; // struct integer_sequence 21 | 22 | template 23 | using index_sequence = integer_sequence; 24 | 25 | namespace details { 26 | 27 | // Inspired by: https://stackoverflow.com/questions/17424477/implementation-c14-make-integer-sequence 28 | 29 | template 30 | struct merge_and_renumber; 31 | 32 | template 33 | struct merge_and_renumber, index_sequence> { 34 | using type = index_sequence; 35 | }; 36 | 37 | template 38 | struct make_index_sequence_helper : merge_and_renumber::type, 39 | typename make_index_sequence_helper::type> {}; 40 | 41 | template<> 42 | struct make_index_sequence_helper<0> { 43 | using type = index_sequence<>; 44 | }; 45 | 46 | template<> 47 | struct make_index_sequence_helper<1> { 48 | using type = index_sequence<0>; 49 | }; 50 | 51 | } // namespace details 52 | 53 | template 54 | using make_index_sequence = typename details::make_index_sequence_helper::type; 55 | 56 | namespace details { 57 | 58 | template> 59 | struct make_integer_sequence_helper; 60 | 61 | template 62 | struct make_integer_sequence_helper> { 63 | using type = integer_sequence(Indices)...>; 64 | }; 65 | 66 | } // namespace details 67 | 68 | template 69 | using make_integer_sequence = typename details::make_integer_sequence_helper::type; 70 | 71 | template 72 | using index_sequence_for = make_index_sequence; 73 | 74 | NAMESPACE_CIEL_END 75 | 76 | #endif // CIELLAB_INCLUDE_CIEL_CORE_INTEGER_SEQUENCE_HPP_ 77 | -------------------------------------------------------------------------------- /test/src/treiber_stack.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace ciel; 14 | 15 | namespace { 16 | 17 | struct Node { 18 | size_t value{1}; 19 | std::atomic next{this}; // Intentionally 20 | 21 | }; // struct Node 22 | 23 | template 24 | void test_concurrent_push_and_pop_impl(::testing::Test*) { 25 | constexpr size_t threads_num = 64; 26 | constexpr size_t operations_num = 1000; 27 | 28 | std::array, threads_num / 2> arr; 29 | SimpleLatch go{threads_num}; 30 | Stack stack; 31 | std::atomic count{0}; 32 | 33 | ciel::vector push_threads(reserve_capacity, threads_num / 2); 34 | for (size_t i = 0; i < threads_num / 2; ++i) { 35 | push_threads.unchecked_emplace_back([&, i] { 36 | go.arrive_and_wait(); 37 | 38 | for (size_t j = 0; j < operations_num; ++j) { 39 | stack.push(std::addressof(arr[i][j])); 40 | } 41 | }); 42 | } 43 | 44 | ciel::vector pop_threads(reserve_capacity, threads_num / 2); 45 | for (size_t i = 0; i < threads_num / 2; ++i) { 46 | pop_threads.unchecked_emplace_back([&] { 47 | go.arrive_and_wait(); 48 | 49 | for (size_t j = 0; j < operations_num; ++j) { 50 | Node* node = stack.pop(); 51 | if (node != nullptr) { 52 | count += node->value; 53 | } 54 | } 55 | }); 56 | } 57 | 58 | for (auto& t : push_threads) { 59 | t.join(); 60 | } 61 | 62 | for (auto& t : pop_threads) { 63 | t.join(); 64 | } 65 | 66 | Node* top = stack.pop_all(); 67 | while (top != nullptr) { 68 | count += top->value; 69 | top = top->next; 70 | } 71 | 72 | ASSERT_EQ(count, threads_num / 2 * operations_num); 73 | } 74 | 75 | } // namespace 76 | 77 | TEST(treiber_stack, concurrent_push_and_pop) { 78 | test_concurrent_push_and_pop_impl>(this); 79 | test_concurrent_push_and_pop_impl>(this); 80 | } 81 | -------------------------------------------------------------------------------- /test/src/vector/emplace_back.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | using namespace ciel; 10 | 11 | namespace { 12 | 13 | template 14 | void test_emplace_back_impl(::testing::Test*) { 15 | constexpr int N = 64; 16 | 17 | C v; 18 | 19 | for (int i = 0; i < N; ++i) { 20 | v.emplace_back(i); 21 | } 22 | 23 | ASSERT_EQ(v.size(), N); 24 | for (int i = 0; i < N; ++i) { 25 | ASSERT_EQ(v[i], Int(i)); 26 | } 27 | } 28 | 29 | template 30 | void test_emplace_back_self_reference_impl(::testing::Test*) { 31 | C v{0, 1, 2, 3, 4}; 32 | 33 | v.resize(v.capacity(), 123); 34 | v.emplace_back(v[0]); 35 | ASSERT_EQ(v.back(), v[0]); 36 | 37 | v.resize(v.capacity(), 234); 38 | v.emplace_back(v[1]); 39 | ASSERT_EQ(v.back(), v[1]); 40 | } 41 | 42 | } // namespace 43 | 44 | TEST(vector, emplace_back) { 45 | test_emplace_back_impl>(this); 46 | test_emplace_back_impl>(this); 47 | test_emplace_back_impl>(this); 48 | test_emplace_back_impl>(this); 49 | 50 | test_emplace_back_impl>>(this); 51 | test_emplace_back_impl>>(this); 52 | test_emplace_back_impl>>(this); 53 | test_emplace_back_impl>>(this); 54 | } 55 | 56 | TEST(vector, emplace_back_self_reference) { 57 | test_emplace_back_self_reference_impl>(this); 58 | test_emplace_back_self_reference_impl>(this); 59 | test_emplace_back_self_reference_impl>(this); 60 | test_emplace_back_self_reference_impl>(this); 61 | 62 | test_emplace_back_self_reference_impl>>(this); 63 | test_emplace_back_self_reference_impl>>(this); 64 | test_emplace_back_self_reference_impl>>(this); 65 | test_emplace_back_self_reference_impl>>(this); 66 | } 67 | 68 | TEST(vector, emplace_back_initializer_list) { 69 | { 70 | vector> v1; 71 | v1.emplace_back({0, 1, 2, 3, 4}); 72 | ASSERT_EQ(v1, std::initializer_list>({ 73 | {0, 1, 2, 3, 4} 74 | })); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /include/ciel/core/cstring.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_CSTRING_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_CSTRING_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | NAMESPACE_CIEL_BEGIN 13 | 14 | // memcpy 15 | 16 | inline void memcpy(void* dest, const void* src, const size_t count) noexcept { 17 | CIEL_ASSERT(dest != nullptr); 18 | CIEL_ASSERT(src != nullptr); 19 | CIEL_ASSERT(reinterpret_cast(dest) + count <= reinterpret_cast(src) 20 | || reinterpret_cast(src) + count <= reinterpret_cast(dest)); 21 | 22 | std::memcpy(dest, src, count); 23 | } 24 | 25 | // memmove 26 | 27 | inline void memmove(void* dest, const void* src, const size_t count) noexcept { 28 | CIEL_ASSERT(dest != nullptr); 29 | CIEL_ASSERT(src != nullptr); 30 | 31 | std::memmove(dest, src, count); 32 | } 33 | 34 | // find 35 | // Corner case: 36 | // Return first1 if substring is empty. 37 | // Return nullptr if substring is not found. 38 | 39 | template::value || std::is_same::value> = 0> 40 | CIEL_NODISCARD Iter find(const Iter first1, const char* const last1, const char c) noexcept { 41 | return static_cast(std::memchr(first1, c, last1 - first1)); 42 | } 43 | 44 | template::value || std::is_same::value> = 0> 45 | CIEL_NODISCARD Iter find(const Iter first1, const char* const last1, const char* const first2, 46 | const char* const last2) noexcept { 47 | const size_t len1 = last1 - first1; 48 | const size_t len2 = last2 - first2; 49 | 50 | if CIEL_UNLIKELY (len2 == 0) { 51 | return first1; 52 | } 53 | 54 | if CIEL_UNLIKELY (len1 < len2) { 55 | return nullptr; 56 | } 57 | 58 | auto match = first1; 59 | while (true) { 60 | match = ciel::find(match, last1, *first2); 61 | if (match == nullptr) { 62 | return nullptr; 63 | } 64 | 65 | // It's faster to compare from the first byte of first2 even if we already know that it matches, 66 | // this is because first2 is most likely aligned, as it's user's "pattern" string. 67 | if (std::memcmp(match, first2, len2) == 0) { 68 | return match; 69 | } 70 | 71 | ++match; 72 | } 73 | } 74 | 75 | NAMESPACE_CIEL_END 76 | 77 | #endif // CIELLAB_INCLUDE_CIEL_CORE_CSTRING_HPP_ 78 | -------------------------------------------------------------------------------- /test/src/reference_counter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace ciel; 12 | 13 | TEST(reference_counter, singlethread) { 14 | reference_counter counter; 15 | ASSERT_EQ(counter.load(), 1); 16 | 17 | ASSERT_TRUE(counter.increment_if_not_zero(1)); 18 | ASSERT_EQ(counter.load(), 2); 19 | ASSERT_FALSE(counter.decrement(1)); 20 | ASSERT_EQ(counter.load(), 1); 21 | ASSERT_TRUE(counter.increment_if_not_zero(2)); 22 | ASSERT_EQ(counter.load(), 3); 23 | 24 | ASSERT_TRUE(counter.decrement(3)); 25 | ASSERT_EQ(counter.load(), 0); 26 | 27 | ASSERT_FALSE(counter.increment_if_not_zero(1)); 28 | ASSERT_EQ(counter.load(), 0); 29 | } 30 | 31 | TEST(reference_counter, multithread) { 32 | constexpr size_t threads_num = 64; 33 | constexpr size_t operations_num = 10000; 34 | 35 | SimpleLatch go{threads_num + 1}; 36 | 37 | reference_counter counter; 38 | std::atomic cleanup_count{0}; 39 | std::atomic hits_zero{false}; 40 | 41 | ciel::vector write_threads(reserve_capacity, threads_num / 2); 42 | for (size_t i = 0; i < threads_num / 2; ++i) { 43 | write_threads.unchecked_emplace_back([&] { 44 | go.arrive_and_wait(); 45 | 46 | for (size_t j = 0; j < operations_num; ++j) { 47 | if (counter.increment_if_not_zero(1)) { 48 | if (counter.decrement(1)) { 49 | ++cleanup_count; 50 | } 51 | } 52 | } 53 | }); 54 | } 55 | 56 | ciel::vector read_threads(reserve_capacity, threads_num / 2); 57 | for (size_t i = 0; i < threads_num / 2; ++i) { 58 | read_threads.unchecked_emplace_back([&] { 59 | go.arrive_and_wait(); 60 | 61 | for (size_t j = 0; j < operations_num; ++j) { 62 | if (hits_zero.load()) { 63 | ASSERT_EQ(counter.load(), 0); 64 | 65 | } else if (counter.load() == 0) { 66 | hits_zero = true; 67 | } 68 | } 69 | }); 70 | } 71 | 72 | go.arrive_and_wait(); 73 | if (counter.decrement(1)) { 74 | ++cleanup_count; 75 | } 76 | 77 | for (auto& t : write_threads) { 78 | t.join(); 79 | } 80 | 81 | for (auto& t : read_threads) { 82 | t.join(); 83 | } 84 | 85 | ASSERT_EQ(cleanup_count.load(), 1); 86 | } 87 | -------------------------------------------------------------------------------- /benchmark/src/singleton.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace { 9 | 10 | class SingletonOfCiel : public ciel::singleton {}; 11 | 12 | class SingletonOfStatic { 13 | private: 14 | SingletonOfStatic() = default; 15 | 16 | public: 17 | CIEL_NODISCARD static SingletonOfStatic& get() { 18 | static SingletonOfStatic res; 19 | return res; 20 | } 21 | 22 | SingletonOfStatic(const SingletonOfStatic&) = delete; 23 | SingletonOfStatic& operator=(const SingletonOfStatic&) = delete; 24 | 25 | }; // class SingletonOfStatic 26 | 27 | class SingletonOfDCLP { 28 | private: 29 | SingletonOfDCLP() = default; 30 | 31 | public: 32 | CIEL_NODISCARD static SingletonOfDCLP& get() { 33 | ciel::spinlock lock; 34 | alignas(SingletonOfDCLP) static unsigned char buffer[sizeof(SingletonOfDCLP)]; 35 | static std::atomic ptr; 36 | 37 | SingletonOfDCLP* tmp = ptr.load(std::memory_order_acquire); 38 | 39 | if CIEL_UNLIKELY (tmp == nullptr) { 40 | with(lock, [&]() { 41 | tmp = ptr.load(std::memory_order_relaxed); 42 | 43 | if (tmp == nullptr) { 44 | tmp = ::new (&buffer) SingletonOfDCLP; 45 | ptr.store(tmp, std::memory_order_release); 46 | } 47 | }); 48 | } 49 | 50 | return *tmp; 51 | } 52 | 53 | SingletonOfDCLP(const SingletonOfDCLP&) = delete; 54 | SingletonOfDCLP& operator=(const SingletonOfDCLP&) = delete; 55 | 56 | }; // class SingletonOfDCLP 57 | 58 | } // namespace 59 | 60 | static void singleton_ciel(benchmark::State& state) { 61 | for (auto _ : state) { 62 | benchmark::DoNotOptimize(SingletonOfCiel::get()); 63 | } 64 | 65 | benchmark::ClobberMemory(); 66 | } 67 | 68 | static void singleton_dclp(benchmark::State& state) { 69 | for (auto _ : state) { 70 | benchmark::DoNotOptimize(SingletonOfDCLP::get()); 71 | } 72 | 73 | benchmark::ClobberMemory(); 74 | } 75 | 76 | static void singleton_static(benchmark::State& state) { 77 | for (auto _ : state) { 78 | benchmark::DoNotOptimize(SingletonOfStatic::get()); 79 | } 80 | 81 | benchmark::ClobberMemory(); 82 | } 83 | 84 | BENCHMARK(singleton_ciel)->Threads(std::thread::hardware_concurrency()); 85 | BENCHMARK(singleton_dclp)->Threads(std::thread::hardware_concurrency()); 86 | BENCHMARK(singleton_static)->Threads(std::thread::hardware_concurrency()); 87 | -------------------------------------------------------------------------------- /include/ciel/core/treiber_stack.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_TREIBER_STACK_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_TREIBER_STACK_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | NAMESPACE_CIEL_BEGIN 12 | 13 | // Inspired by Microsoft snmalloc's implementation. 14 | 15 | template 16 | class treiber_stack { 17 | static_assert(std::is_same>::value, ""); 18 | 19 | private: 20 | alignas(cacheline_size) aba stack_; 21 | 22 | public: 23 | treiber_stack() = default; 24 | 25 | treiber_stack(const treiber_stack&) = delete; 26 | treiber_stack& operator=(const treiber_stack&) = delete; 27 | 28 | void push(T* t) noexcept { 29 | push(t, t); 30 | } 31 | 32 | void push(T* first, T* last) noexcept { 33 | CIEL_ASSERT(first != nullptr); 34 | CIEL_ASSERT(last != nullptr); 35 | 36 | auto impl = stack_.read(); 37 | 38 | do { 39 | T* top = impl.ptr(); 40 | last->next.store(top, std::memory_order_release); 41 | 42 | } while (!impl.store_conditional(first)); 43 | } 44 | 45 | CIEL_NODISCARD T* pop() noexcept { 46 | auto impl = stack_.read(); 47 | T* top = nullptr; 48 | T* next = nullptr; 49 | 50 | do { 51 | top = impl.ptr(); 52 | 53 | if (top == nullptr) { 54 | break; 55 | } 56 | 57 | // The returned `top` shall not be unmapped immediately as the losers of CAS 58 | // may read uninitialized memory by loading `top->next`, leading to crash. 59 | // Simply reusing the memory for something else wouldn't be a problem in the real world, 60 | // but it's still undefined behavior for this atomic load to race with a non-atomic store. 61 | next = top->next.load(std::memory_order_relaxed); 62 | 63 | } while (!impl.store_conditional(next)); 64 | 65 | return top; 66 | } 67 | 68 | CIEL_NODISCARD T* pop_all() noexcept { 69 | auto impl = stack_.read(); 70 | T* top = nullptr; 71 | 72 | do { 73 | top = impl.ptr(); 74 | 75 | if (top == nullptr) { 76 | break; 77 | } 78 | 79 | } while (!impl.store_conditional(nullptr)); 80 | 81 | return top; 82 | } 83 | 84 | }; // class treiber_stack 85 | 86 | NAMESPACE_CIEL_END 87 | 88 | #endif // CIELLAB_INCLUDE_CIEL_CORE_TREIBER_STACK_HPP_ 89 | -------------------------------------------------------------------------------- /test/src/hazard_pointer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace ciel; 14 | 15 | namespace { 16 | 17 | struct Garbage : hazard_pointer_obj_base { 18 | int i{1}; 19 | 20 | ~Garbage() { 21 | i = 0; 22 | } 23 | 24 | }; // struct Garbage 25 | 26 | } // namespace 27 | 28 | TEST(hazard_pointer, singlethread) { 29 | constexpr size_t garbage_num = 10000; 30 | 31 | ciel::inplace_vector, garbage_num> v; 32 | for (size_t i = 0; i < garbage_num; ++i) { 33 | v.unchecked_emplace_back(new Garbage); 34 | } 35 | 36 | hazard_pointer hp = make_hazard_pointer(); 37 | std::atomic count{0}; 38 | for (std::atomic& p : v) { 39 | Garbage* res = hp.protect(p); 40 | res->retire(); 41 | count += res->i; 42 | hp.reset_protection(); 43 | } 44 | 45 | ASSERT_EQ(count, garbage_num); 46 | } 47 | 48 | TEST(hazard_pointer, multithread) { 49 | constexpr size_t threads_num = 64; 50 | constexpr size_t operations_num = 10000; 51 | 52 | std::atomic ptr{nullptr}; 53 | SimpleLatch go{threads_num}; 54 | 55 | ciel::vector store_threads(reserve_capacity, threads_num / 2); 56 | for (size_t i = 0; i < threads_num / 2; ++i) { 57 | store_threads.unchecked_emplace_back([&] { 58 | go.arrive_and_wait(); 59 | 60 | for (size_t j = 0; j < operations_num; ++j) { 61 | Garbage* p = ptr.exchange(new Garbage); 62 | if (p != nullptr) { 63 | p->retire(); 64 | } 65 | } 66 | }); 67 | } 68 | 69 | ciel::vector load_threads(reserve_capacity, threads_num / 2); 70 | for (size_t i = 0; i < threads_num / 2; ++i) { 71 | load_threads.unchecked_emplace_back([&] { 72 | go.arrive_and_wait(); 73 | 74 | hazard_pointer hp = make_hazard_pointer(); 75 | 76 | for (size_t j = 0; j < operations_num; ++j) { 77 | Garbage* p = hp.protect(ptr); 78 | if (p != nullptr) { 79 | ASSERT_EQ(p->i, 1); 80 | } 81 | hp.reset_protection(); 82 | } 83 | }); 84 | } 85 | 86 | for (auto& t : store_threads) { 87 | t.join(); 88 | } 89 | 90 | for (auto& t : load_threads) { 91 | t.join(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /include/ciel/test/propagate_allocator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_TEST_MAYBE_PROPAGATE_ALLOCATOR_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_TEST_MAYBE_PROPAGATE_ALLOCATOR_HPP_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | NAMESPACE_CIEL_BEGIN 10 | 11 | template 12 | class propagate_allocator { 13 | private: 14 | int id_ = 0; 15 | 16 | template 17 | friend class propagate_allocator; 18 | 19 | public: 20 | using value_type = T; 21 | using propagate_on_container_copy_assignment = bool_constant; 22 | using propagate_on_container_move_assignment = bool_constant; 23 | using propagate_on_container_swap = bool_constant; 24 | 25 | template 26 | struct rebind { 27 | using other = propagate_allocator; 28 | }; 29 | 30 | propagate_allocator() = default; 31 | 32 | propagate_allocator(const int id) noexcept 33 | : id_(id) {} 34 | 35 | propagate_allocator(const propagate_allocator&) = default; 36 | 37 | template 38 | propagate_allocator(const propagate_allocator& that) noexcept 39 | : id_(that.id_) {} 40 | 41 | propagate_allocator& operator=(const propagate_allocator& a) = default; 42 | 43 | CIEL_NODISCARD T* allocate(const size_t n) { 44 | return std::allocator().allocate(n); 45 | } 46 | 47 | void deallocate(T* ptr, const size_t n) noexcept { 48 | std::allocator().deallocate(ptr, n); 49 | } 50 | 51 | CIEL_NODISCARD int id() const noexcept { 52 | return id_; 53 | } 54 | 55 | template 56 | CIEL_NODISCARD friend bool operator==( 57 | const propagate_allocator& lhs, const propagate_allocator& rhs) noexcept { 58 | return lhs.id() == rhs.id(); 59 | } 60 | 61 | }; // class propagate_allocator 62 | 63 | template 64 | using pocca_allocator = propagate_allocator; 65 | template 66 | using non_pocca_allocator = propagate_allocator; 67 | 68 | template 69 | using pocma_allocator = propagate_allocator; 70 | template 71 | using non_pocma_allocator = propagate_allocator; 72 | 73 | template 74 | using pocs_allocator = propagate_allocator; 75 | template 76 | using non_pocs_allocator = propagate_allocator; 77 | 78 | NAMESPACE_CIEL_END 79 | 80 | #endif // CIELLAB_INCLUDE_CIEL_TEST_MAYBE_PROPAGATE_ALLOCATOR_HPP_ 81 | -------------------------------------------------------------------------------- /test/src/rb_tree.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace ciel; 13 | 14 | namespace { 15 | 16 | template 17 | struct RBNode : rb_node_base { 18 | using value_type = T; 19 | 20 | private: 21 | value_type value_; 22 | 23 | public: 24 | template 25 | RBNode(Args&&... args) noexcept(std::is_nothrow_constructible::value) 26 | : value_(std::forward(args)...) {} 27 | 28 | CIEL_NODISCARD value_type& value() noexcept { 29 | return value_; 30 | } 31 | 32 | CIEL_NODISCARD const value_type& value() const noexcept { 33 | return value_; 34 | } 35 | 36 | }; // struct RBNode 37 | 38 | } // namespace 39 | 40 | TEST(rb_tree, int) { 41 | using NodeType = RBNode; 42 | using Compare = std::less; 43 | 44 | ciel::vector v(reserve_capacity, 10000); 45 | for (int i = 0; i < 10000; ++i) { 46 | v.unchecked_emplace_back(i); 47 | } 48 | std::random_device rd; 49 | std::mt19937 g(rd()); 50 | std::shuffle(v.begin(), v.end(), g); 51 | 52 | rb_tree tree; 53 | for (auto& node : v) { 54 | ASSERT_TRUE(tree.insert(&node)); 55 | } 56 | ASSERT_TRUE(std::is_sorted(tree.begin(), tree.end(), Compare{})); 57 | ASSERT_EQ(tree.size(), v.size()); 58 | 59 | for (auto& node : v) { 60 | auto p = tree.find(node.value()); 61 | ASSERT_NE(p, nullptr); 62 | 63 | tree.remove(p); 64 | } 65 | 66 | ASSERT_TRUE(tree.empty()); 67 | } 68 | 69 | TEST(rb_tree, void) { 70 | struct RBNodeInplace : rb_node_base { 71 | using value_type = uintptr_t; 72 | 73 | CIEL_NODISCARD value_type value() const noexcept { 74 | return reinterpret_cast(this); 75 | } 76 | 77 | }; // struct RBNode 78 | 79 | using Compare = std::less; 80 | 81 | ciel::vector v(10000); 82 | std::random_device rd; 83 | std::mt19937 g(rd()); 84 | std::shuffle(v.begin(), v.end(), g); 85 | 86 | rb_tree tree; 87 | for (auto& node : v) { 88 | ASSERT_TRUE(tree.insert(&node)); 89 | } 90 | ASSERT_TRUE(std::is_sorted(tree.begin(), tree.end(), Compare{})); 91 | ASSERT_EQ(tree.size(), v.size()); 92 | 93 | for (auto& node : v) { 94 | auto p = tree.find(node.value()); 95 | ASSERT_NE(p, nullptr); 96 | 97 | tree.remove(p); 98 | } 99 | 100 | ASSERT_TRUE(tree.empty()); 101 | } 102 | -------------------------------------------------------------------------------- /test/src/function.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace ciel; 12 | 13 | namespace { 14 | 15 | void test1() noexcept {} 16 | 17 | int test2(double, float, long) noexcept { 18 | return 1; 19 | } 20 | 21 | } // namespace 22 | 23 | TEST(function, constructors_and_assignments) { 24 | const function f0{nullptr}; 25 | 26 | #ifdef CIEL_HAS_EXCEPTIONS 27 | ASSERT_THROW(f0(), ciel::bad_function_call); 28 | #endif 29 | 30 | const function f1{test1}; 31 | f1(); 32 | 33 | const function f2{test2}; 34 | static_assert(std::is_same::value, ""); 35 | CIEL_UNUSED(f2(1.0, 1.f, 1)); 36 | 37 | function f3{[] {}}; 38 | f3(); 39 | 40 | int i = 1; 41 | function f4{[&] { 42 | return i; 43 | }}; 44 | static_assert(std::is_same::value, ""); 45 | CIEL_UNUSED(f4()); 46 | 47 | const function f5{f4}; 48 | CIEL_UNUSED(f5()); 49 | 50 | function f6{std::move(f4)}; 51 | ASSERT_FALSE(f4); 52 | CIEL_UNUSED(f6()); 53 | 54 | f4 = nullptr; 55 | 56 | f4 = [] { 57 | return 1; 58 | }; 59 | CIEL_UNUSED(f4()); 60 | 61 | f4 = f5; 62 | CIEL_UNUSED(f4()); 63 | 64 | f4 = std::move(f6); 65 | ASSERT_FALSE(f6); 66 | CIEL_UNUSED(f4()); 67 | 68 | static_assert(not is_small_object>::value, ""); 69 | const std::deque deque{1, 2, 3, 4, 5}; 70 | function f7{[deque] { 71 | CIEL_UNUSED(deque); 72 | }}; 73 | f7(); 74 | 75 | f3 = f7; 76 | f3(); 77 | 78 | f3 = f0; 79 | #ifdef CIEL_HAS_EXCEPTIONS 80 | ASSERT_THROW(f3(), ciel::bad_function_call); 81 | #endif 82 | 83 | f3 = f1; 84 | f3(); 85 | 86 | f7 = std::move(f3); 87 | ASSERT_FALSE(f3); 88 | f7(); 89 | } 90 | 91 | TEST(function, swap) { 92 | const std::deque d{1, 2, 3, 4, 5}; 93 | const std::vector v{6, 7, 8, 9, 10}; 94 | auto large_lambda = [d] { 95 | return std::vector{d.begin(), d.end()}; 96 | }; 97 | auto small_lambda = [v] { 98 | return v; 99 | }; 100 | 101 | function()> large_function{large_lambda}; 102 | function()> small_function{assume_trivially_relocatable, small_lambda}; 103 | 104 | ASSERT_EQ(large_function(), std::vector({1, 2, 3, 4, 5})); 105 | ASSERT_EQ(small_function(), std::vector({6, 7, 8, 9, 10})); 106 | 107 | std::swap(large_function, small_function); 108 | 109 | ASSERT_EQ(large_function(), std::vector({6, 7, 8, 9, 10})); 110 | ASSERT_EQ(small_function(), std::vector({1, 2, 3, 4, 5})); 111 | } 112 | -------------------------------------------------------------------------------- /test/src/do_if_noexcept.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | using namespace ciel; 9 | 10 | namespace { 11 | 12 | struct NoexceptMove { 13 | NoexceptMove() = default; 14 | 15 | NoexceptMove(const NoexceptMove&) noexcept {} 16 | 17 | NoexceptMove(NoexceptMove&&) noexcept {} 18 | }; 19 | 20 | struct NonNothrowMove { 21 | NonNothrowMove() = default; 22 | 23 | NonNothrowMove(const NonNothrowMove&) {} 24 | 25 | NonNothrowMove(NonNothrowMove&&) {} 26 | }; 27 | 28 | struct NonCopy { 29 | NonCopy() = default; 30 | NonCopy(const NonCopy&) = delete; 31 | 32 | NonCopy(NonCopy&&) {} 33 | }; 34 | 35 | } // namespace 36 | 37 | TEST(do_if_noexcept, move) { 38 | NoexceptMove a; 39 | NonNothrowMove b; 40 | NonCopy c; 41 | 42 | #ifdef CIEL_HAS_EXCEPTIONS 43 | static_assert(std::is_same::value, ""); 44 | static_assert(std::is_same::value, ""); 45 | static_assert(std::is_same::value, ""); 46 | #else 47 | static_assert(std::is_same::value, ""); 48 | static_assert(std::is_same::value, ""); 49 | static_assert(std::is_same::value, ""); 50 | #endif 51 | } 52 | 53 | TEST(do_if_noexcept, forward) { 54 | NoexceptMove a; 55 | NonNothrowMove b; 56 | NonCopy c; 57 | 58 | #ifdef CIEL_HAS_EXCEPTIONS 59 | static_assert(std::is_same::value, ""); 60 | static_assert(std::is_same::value, ""); 61 | static_assert(std::is_same::value, ""); 62 | static_assert(std::is_same::value, ""); 63 | static_assert(std::is_same::value, ""); 64 | static_assert(std::is_same::value, ""); 65 | #else 66 | static_assert(std::is_same::value, ""); 67 | static_assert(std::is_same::value, ""); 68 | static_assert(std::is_same::value, ""); 69 | static_assert(std::is_same::value, ""); 70 | static_assert(std::is_same::value, ""); 71 | static_assert(std::is_same::value, ""); 72 | #endif 73 | } 74 | -------------------------------------------------------------------------------- /include/ciel/to_address.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_TO_ADDRESS_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_TO_ADDRESS_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | NAMESPACE_CIEL_BEGIN 12 | 13 | // https://en.cppreference.com/w/cpp/memory/to_address 14 | // to_address is to unpack any fancy pointers to raw pointers, while std::addressof(*p) cannot be used 15 | // because there may be no valid object for p to dereference to. 16 | 17 | // Inspired by LLVM libc++'s implementation. 18 | 19 | CIEL_DIAGNOSTIC_PUSH 20 | #if CIEL_STD_VER >= 20 21 | // std::move_iterator's operator-> has been deprecated in C++20 22 | CIEL_CLANG_DIAGNOSTIC_IGNORED("-Wdeprecated-declarations") 23 | #endif 24 | 25 | template 26 | struct to_address_helper; 27 | 28 | template 29 | T* to_address(T* p) noexcept { 30 | static_assert(!std::is_function::value, "T is a function type"); 31 | return p; 32 | } 33 | 34 | // has_to_address 35 | 36 | template 37 | struct has_to_address : std::false_type {}; 38 | 39 | template 40 | struct has_to_address::to_address(std::declval()))> 41 | : std::true_type {}; 42 | 43 | // has_arrow 44 | 45 | template 46 | struct has_arrow : std::false_type {}; 47 | 48 | template 49 | struct has_arrow().operator->())> : std::true_type {}; 50 | 51 | // is_fancy_pointer 52 | 53 | template 54 | struct is_fancy_pointer { 55 | static constexpr bool value = has_arrow::value || has_to_address::value; 56 | }; 57 | 58 | // to_address 59 | 60 | // enable_if is needed here to avoid instantiating checks for fancy pointers on raw pointers. 61 | template, is_fancy_pointer>::value> = 0> 62 | decay_t::call(std::declval()))> 63 | to_address(const Pointer& p) noexcept { 64 | return to_address_helper::call(p); 65 | } 66 | 67 | // to_address_helper 68 | 69 | template 70 | struct to_address_helper { 71 | static decltype(ciel::to_address(std::declval().operator->())) call(const Pointer& p) noexcept { 72 | return ciel::to_address(p.operator->()); 73 | } 74 | }; 75 | 76 | template 77 | struct to_address_helper::to_address(std::declval()))> { 79 | static decltype(std::pointer_traits::to_address(std::declval())) 80 | call(const Pointer& p) noexcept { 81 | return std::pointer_traits::to_address(p); 82 | } 83 | }; 84 | 85 | CIEL_DIAGNOSTIC_POP 86 | 87 | NAMESPACE_CIEL_END 88 | 89 | #endif // CIELLAB_INCLUDE_CIEL_TO_ADDRESS_HPP_ 90 | -------------------------------------------------------------------------------- /include/ciel/test/int_wrapper.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_TEST_INT_WRAPPER_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_TEST_INT_WRAPPER_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | NAMESPACE_CIEL_BEGIN 11 | 12 | template 13 | struct int_wrapper { 14 | private: 15 | int i_; 16 | 17 | public: 18 | int_wrapper(const int i = 0) noexcept 19 | : i_(i) {} 20 | 21 | int_wrapper(const int_wrapper& other) noexcept 22 | : i_(other.i_) {} 23 | 24 | int_wrapper(int_wrapper&& other) noexcept(IsNothrowMovable) 25 | : i_(ciel::exchange(other.i_, -1)) {} 26 | 27 | int_wrapper& operator=(const int_wrapper& other) noexcept { 28 | i_ = other.i_; 29 | return *this; 30 | } 31 | 32 | int_wrapper& operator=(int_wrapper&& other) noexcept(IsNothrowMovable) { 33 | i_ = ciel::exchange(other.i_, -1); 34 | return *this; 35 | } 36 | 37 | ~int_wrapper() { 38 | i_ = -2; 39 | } 40 | 41 | int_wrapper& operator++() noexcept { 42 | ++i_; 43 | return *this; 44 | } 45 | 46 | CIEL_NODISCARD int_wrapper operator++(int) noexcept { 47 | auto res = *this; 48 | ++(*this); 49 | return res; 50 | } 51 | 52 | int_wrapper& operator--() noexcept { 53 | --i_; 54 | return *this; 55 | } 56 | 57 | CIEL_NODISCARD int_wrapper operator--(int) noexcept { 58 | auto res = *this; 59 | --(*this); 60 | return res; 61 | } 62 | 63 | int_wrapper& operator+=(const int_wrapper other) noexcept { 64 | i_ += other.i_; 65 | return *this; 66 | } 67 | 68 | int_wrapper& operator-=(const int_wrapper other) noexcept { 69 | return (*this) += (-other); 70 | } 71 | 72 | CIEL_NODISCARD int_wrapper operator-() noexcept { 73 | int_wrapper res(-i_); 74 | return res; 75 | } 76 | 77 | CIEL_NODISCARD 78 | operator int() const noexcept { 79 | return i_; 80 | } 81 | 82 | CIEL_NODISCARD friend int_wrapper operator+(int_wrapper lhs, const int_wrapper rhs) noexcept { 83 | lhs.i_ += rhs.i_; 84 | return lhs; 85 | } 86 | 87 | CIEL_NODISCARD friend int_wrapper operator-(int_wrapper lhs, const int_wrapper rhs) noexcept { 88 | lhs.i_ -= rhs.i_; 89 | return lhs; 90 | } 91 | 92 | }; // struct int_wrapper 93 | 94 | using Int = int_wrapper; 95 | using TRInt = int_wrapper; 96 | using TMInt = int_wrapper; 97 | 98 | template<> 99 | struct is_trivially_relocatable : std::false_type {}; 100 | 101 | template<> 102 | struct is_trivially_relocatable : std::false_type {}; 103 | 104 | template<> 105 | struct is_trivially_relocatable : std::true_type {}; 106 | 107 | NAMESPACE_CIEL_END 108 | 109 | #endif // CIELLAB_INCLUDE_CIEL_TEST_INT_WRAPPER_HPP_ 110 | -------------------------------------------------------------------------------- /test/src/mpsc_queue.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace ciel; 15 | 16 | namespace { 17 | 18 | struct Node { 19 | size_t value{1}; 20 | std::atomic next{this}; // Intentionally 21 | 22 | }; // struct Node 23 | 24 | } // namespace 25 | 26 | TEST(mpsc_queue, singlethread) { 27 | std::array arr; 28 | mpsc_queue queue; 29 | std::atomic count{0}; 30 | queue.process([&](Node*) { 31 | ciel::unreachable(); 32 | return true; 33 | }); 34 | 35 | queue.push(arr.data()); 36 | queue.process([&](Node*) { 37 | ciel::unreachable(); 38 | return true; 39 | }); 40 | 41 | (arr.data() + 1)->next.store(arr.data() + 2); 42 | (arr.data() + 2)->next.store(arr.data() + 3); 43 | (arr.data() + 3)->next.store(arr.data() + 4); 44 | (arr.data() + 4)->next.store(arr.data() + 5); 45 | queue.push(arr.data() + 1, arr.data() + 5); 46 | queue.process([&](Node* node) { 47 | count += node->value; 48 | return true; 49 | }); 50 | ASSERT_EQ(count, 5); 51 | 52 | queue.destructive_process([&](Node* node) { 53 | count += node->value; 54 | }); 55 | ASSERT_EQ(count, 6); 56 | } 57 | 58 | TEST(mpsc_queue, multithread) { 59 | constexpr size_t producer_threads_num = 64; 60 | constexpr size_t operations_num = 10000; 61 | 62 | using ArrayType = std::array, producer_threads_num>; 63 | std::unique_ptr arr{new ArrayType{}}; 64 | SimpleLatch go{producer_threads_num + 1}; 65 | mpsc_queue queue; 66 | std::atomic count{0}; 67 | 68 | ciel::vector producer_threads(reserve_capacity, producer_threads_num); 69 | for (size_t i = 0; i < producer_threads_num; ++i) { 70 | producer_threads.unchecked_emplace_back([&, i] { 71 | go.arrive_and_wait(); 72 | 73 | for (size_t j = 0; j < operations_num; ++j) { 74 | queue.push(std::addressof((*arr)[i][j])); 75 | } 76 | }); 77 | } 78 | 79 | std::atomic flag{false}; 80 | std::thread consumer([&] { 81 | go.arrive_and_wait(); 82 | 83 | while (!flag) { 84 | queue.process([&](Node* node) { 85 | count += node->value; 86 | return true; 87 | }); 88 | 89 | // std::this_thread::yield(); 90 | } 91 | }); 92 | 93 | for (auto& t : producer_threads) { 94 | t.join(); 95 | } 96 | flag = true; 97 | consumer.join(); 98 | 99 | queue.destructive_process([&](Node* node) { 100 | count += node->value; 101 | }); 102 | 103 | ASSERT_EQ(count, producer_threads_num * operations_num); 104 | } 105 | -------------------------------------------------------------------------------- /test/src/vector/reserve.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | using namespace ciel; 10 | 11 | namespace { 12 | 13 | template 14 | void test_reserve_impl(::testing::Test*) { 15 | { 16 | C v; 17 | v.reserve(10); 18 | ASSERT_GE(v.capacity(), 10); 19 | } 20 | { 21 | C v(100); 22 | ASSERT_GE(v.capacity(), 100); 23 | 24 | v.reserve(50); 25 | ASSERT_EQ(v.size(), 100); 26 | ASSERT_GE(v.capacity(), 100); 27 | 28 | v.reserve(150); 29 | ASSERT_EQ(v.size(), 100); 30 | ASSERT_GE(v.capacity(), 150); 31 | } 32 | } 33 | 34 | template 35 | void test_reserve_data_validity_impl(::testing::Test*) { 36 | using T = typename C::value_type; 37 | 38 | vector v{0, 1, 2, 3, 4}; 39 | v.reserve(v.capacity() + 1); 40 | ASSERT_EQ(v, std::initializer_list({0, 1, 2, 3, 4})); 41 | } 42 | 43 | } // namespace 44 | 45 | TEST(vector, reserve) { 46 | test_reserve_impl>(this); 47 | test_reserve_impl>>(this); 48 | } 49 | 50 | TEST(vector, reserve_data_validity) { 51 | test_reserve_data_validity_impl>(this); 52 | test_reserve_data_validity_impl>(this); 53 | test_reserve_data_validity_impl>(this); 54 | test_reserve_data_validity_impl>(this); 55 | 56 | test_reserve_data_validity_impl>>(this); 57 | test_reserve_data_validity_impl>>(this); 58 | test_reserve_data_validity_impl>>(this); 59 | test_reserve_data_validity_impl>>(this); 60 | } 61 | 62 | #ifdef CIEL_HAS_EXCEPTIONS 63 | # include 64 | # include 65 | # include 66 | 67 | TEST(vector, reserve_beyond_max_size) { 68 | { 69 | vector v; 70 | const size_t sz = v.max_size() + 1; 71 | 72 | try { 73 | v.reserve(sz); 74 | ASSERT_TRUE(false); 75 | 76 | } catch (const std::length_error&) { 77 | ASSERT_EQ(v.size(), 0); 78 | ASSERT_EQ(v.capacity(), 0); 79 | } 80 | } 81 | { 82 | vector v(10, 42); 83 | const int* previous_data = v.data(); 84 | const size_t previous_capacity = v.capacity(); 85 | const size_t sz = v.max_size() + 1; 86 | 87 | try { 88 | v.reserve(sz); 89 | ASSERT_TRUE(false); 90 | 91 | } catch (std::length_error&) { 92 | ASSERT_EQ(v.size(), 10); 93 | ASSERT_EQ(v.capacity(), previous_capacity); 94 | ASSERT_EQ(v.data(), previous_data); 95 | ASSERT_TRUE(std::all_of(v.begin(), v.end(), [](int i) { 96 | return i == 42; 97 | })); 98 | } 99 | } 100 | } 101 | #endif 102 | -------------------------------------------------------------------------------- /include/ciel/copy_n.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_COPY_N_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_COPY_N_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | NAMESPACE_CIEL_BEGIN 13 | 14 | // To extract the input iterator `first` 15 | 16 | template 17 | InputIt copy_n(InputIt first, Size count, OutputIt result) { 18 | using T = typename std::iterator_traits::value_type; 19 | using U = typename std::iterator_traits::value_type; 20 | 21 | if (is_contiguous_iterator::value && is_contiguous_iterator::value && std::is_same::value 22 | && std::is_trivially_copy_assignable::value) { 23 | if (count != 0) { 24 | // assume no overlap 25 | ciel::memcpy(ciel::to_address(result), ciel::to_address(first), count * sizeof(T)); 26 | } 27 | 28 | return std::next(first, count); 29 | } 30 | 31 | for (Size i = 0; i < count; ++i) { 32 | *result = *first; 33 | ++result; 34 | ++first; 35 | } 36 | 37 | return first; 38 | } 39 | 40 | template 41 | InputIt uninitialized_copy_n(Alloc& alloc, InputIt first, Size count, OutputIt result) { 42 | using T = typename std::iterator_traits::value_type; 43 | using U = typename std::iterator_traits::value_type; 44 | 45 | if (is_contiguous_iterator::value && is_contiguous_iterator::value && std::is_same::value 46 | && std::is_trivially_copy_constructible::value) { 47 | if (count != 0) { 48 | ciel::memcpy(ciel::to_address(result), ciel::to_address(first), count * sizeof(T)); 49 | } 50 | 51 | return std::next(first, count); 52 | } 53 | 54 | for (Size i = 0; i < count; ++i) { 55 | std::allocator_traits::construct(alloc, ciel::to_address(result), *first); 56 | ++result; 57 | ++first; 58 | } 59 | 60 | return first; 61 | } 62 | 63 | template 64 | void uninitialized_copy(Alloc& alloc, InputIt first, InputIt last, OutputIt& result) { 65 | using T = typename std::iterator_traits::value_type; 66 | using U = typename std::iterator_traits::value_type; 67 | 68 | if (is_contiguous_iterator::value && is_contiguous_iterator::value && std::is_same::value 69 | && std::is_trivially_copy_constructible::value) { 70 | const size_t count = std::distance(first, last); 71 | if (count != 0) { 72 | ciel::memcpy(ciel::to_address(result), ciel::to_address(first), count * sizeof(T)); 73 | result += count; 74 | } 75 | 76 | } else { 77 | for (; first != last; ++first) { 78 | std::allocator_traits::construct(alloc, ciel::to_address(result), *first); 79 | ++result; 80 | } 81 | } 82 | } 83 | 84 | NAMESPACE_CIEL_END 85 | 86 | #endif // CIELLAB_INCLUDE_CIEL_COPY_N_HPP_ 87 | -------------------------------------------------------------------------------- /include/ciel/core/strip_signature.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_STRIP_SIGNATURE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_STRIP_SIGNATURE_HPP_ 3 | 4 | #if CIEL_STD_VER >= 17 5 | 6 | # include 7 | 8 | NAMESPACE_CIEL_BEGIN 9 | 10 | template 11 | struct strip_signature; 12 | 13 | template 14 | struct strip_signature { 15 | using type = R(Args...); 16 | }; 17 | 18 | template 19 | struct strip_signature { 20 | using type = R(Args...); 21 | }; 22 | 23 | template 24 | struct strip_signature { 25 | using type = R(Args...); 26 | }; 27 | 28 | template 29 | struct strip_signature { 30 | using type = R(Args...); 31 | }; 32 | 33 | // clang-format off 34 | template 35 | struct strip_signature { 36 | using type = R(Args...); 37 | }; 38 | 39 | // clang-format on 40 | 41 | template 42 | struct strip_signature { 43 | using type = R(Args...); 44 | }; 45 | 46 | template 47 | struct strip_signature { 48 | using type = R(Args...); 49 | }; 50 | 51 | template 52 | struct strip_signature { 53 | using type = R(Args...); 54 | }; 55 | 56 | template 57 | struct strip_signature { 58 | using type = R(Args...); 59 | }; 60 | 61 | template 62 | struct strip_signature { 63 | using type = R(Args...); 64 | }; 65 | 66 | template 67 | struct strip_signature { 68 | using type = R(Args...); 69 | }; 70 | 71 | template 72 | struct strip_signature { 73 | using type = R(Args...); 74 | }; 75 | 76 | template 77 | struct strip_signature { 78 | using type = R(Args...); 79 | }; 80 | 81 | template 82 | struct strip_signature { 83 | using type = R(Args...); 84 | }; 85 | 86 | template 87 | struct strip_signature { 88 | using type = R(Args...); 89 | }; 90 | 91 | template 92 | struct strip_signature { 93 | using type = R(Args...); 94 | }; 95 | 96 | template 97 | using strip_signature_t = typename strip_signature::type; 98 | 99 | NAMESPACE_CIEL_END 100 | 101 | #endif 102 | 103 | #endif // CIELLAB_INCLUDE_CIEL_CORE_STRIP_SIGNATURE_HPP_ 104 | -------------------------------------------------------------------------------- /include/ciel/core/spinlock_ptr.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_SPINLOCK_PTR_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_SPINLOCK_PTR_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | NAMESPACE_CIEL_BEGIN 12 | 13 | // Inspired by GNU libstdc++'s implementation. 14 | 15 | template 16 | class spinlock_ptr { 17 | static_assert(alignof(T) > 1, "We can't use the LSB as the lock bit if alignof(T) == 1"); 18 | 19 | private: 20 | using pointer = T*; 21 | 22 | mutable std::atomic ptr_{0}; 23 | 24 | static constexpr uintptr_t lock_bit{1}; 25 | 26 | public: 27 | spinlock_ptr() = default; 28 | 29 | spinlock_ptr(nullptr_t) noexcept {} 30 | 31 | spinlock_ptr(pointer ptr) noexcept 32 | : ptr_(reinterpret_cast(ptr)) {} 33 | 34 | spinlock_ptr(const spinlock_ptr&) = delete; 35 | spinlock_ptr& operator=(const spinlock_ptr&) = delete; 36 | 37 | ~spinlock_ptr() { 38 | CIEL_ASSERT(!is_locked()); 39 | } 40 | 41 | CIEL_NODISCARD bool is_locked(const uintptr_t value) const noexcept { 42 | return value & lock_bit; 43 | } 44 | 45 | CIEL_NODISCARD bool is_locked() const noexcept { 46 | return ptr_.load(std::memory_order_relaxed) & lock_bit; 47 | } 48 | 49 | CIEL_NODISCARD pointer lock(const std::memory_order order = std::memory_order_seq_cst) const noexcept { 50 | uintptr_t cur = 0; 51 | do { 52 | while (is_locked(cur = ptr_.load(std::memory_order_relaxed))) { 53 | std::this_thread::yield(); 54 | } 55 | 56 | } while (!ptr_.compare_exchange_strong(cur, cur | lock_bit, order, std::memory_order_relaxed)); 57 | 58 | return reinterpret_cast(cur); 59 | } 60 | 61 | void unlock(const std::memory_order order = std::memory_order_seq_cst) const noexcept { 62 | CIEL_ASSERT(is_locked()); 63 | 64 | ptr_.fetch_sub(1, order); 65 | } 66 | 67 | void swap_unlock(pointer& p, std::memory_order order = std::memory_order_seq_cst) const noexcept { 68 | CIEL_ASSERT(is_locked()); 69 | 70 | if (order != std::memory_order_seq_cst) { 71 | order = std::memory_order_release; 72 | } 73 | 74 | uintptr_t temp = reinterpret_cast(p); 75 | temp = ptr_.exchange(temp, order); 76 | p = reinterpret_cast(temp & ~lock_bit); 77 | } 78 | 79 | CIEL_NODISCARD pointer ptr() const noexcept { 80 | CIEL_ASSERT(is_locked()); 81 | 82 | uintptr_t cur = ptr_.load(std::memory_order_relaxed); 83 | --cur; 84 | return reinterpret_cast(cur); 85 | } 86 | 87 | void store(pointer p, std::memory_order order = std::memory_order_seq_cst) const noexcept { 88 | CIEL_ASSERT(is_locked()); 89 | 90 | uintptr_t temp = reinterpret_cast(p); 91 | ++temp; 92 | ptr_.store(temp, order); 93 | } 94 | 95 | }; // class spinlock_ptr 96 | 97 | NAMESPACE_CIEL_END 98 | 99 | #endif // CIELLAB_INCLUDE_CIEL_CORE_SPINLOCK_PTR_HPP_ 100 | -------------------------------------------------------------------------------- /benchmark/src/atomic_shared_ptr.cpp: -------------------------------------------------------------------------------- 1 | // __cpp_lib_atomic_shared_ptr doesn't work. 2 | #if !defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 12 3 | 4 | # include 5 | # include 6 | # include 7 | # include 8 | # include 9 | 10 | static void atomic_shared_ptr_load_ciel(benchmark::State& state) { 11 | ciel::atomic_shared_ptr sp = ciel::make_shared(1); 12 | for (auto _ : state) { 13 | auto copy = sp.load(std::memory_order_acquire); 14 | 15 | benchmark::DoNotOptimize(copy); 16 | benchmark::ClobberMemory(); 17 | } 18 | } 19 | 20 | static void atomic_shared_ptr_load_std(benchmark::State& state) { 21 | std::atomic> sp = std::make_shared(1); 22 | for (auto _ : state) { 23 | auto copy = sp.load(std::memory_order_acquire); 24 | 25 | benchmark::DoNotOptimize(copy); 26 | benchmark::ClobberMemory(); 27 | } 28 | } 29 | 30 | BENCHMARK(atomic_shared_ptr_load_ciel)->Threads(std::thread::hardware_concurrency()); 31 | BENCHMARK(atomic_shared_ptr_load_std)->Threads(std::thread::hardware_concurrency()); 32 | 33 | static void atomic_shared_ptr_exchange_ciel(benchmark::State& state) { 34 | ciel::atomic_shared_ptr sp = ciel::make_shared(1); 35 | for (auto _ : state) { 36 | auto copy = sp.exchange(ciel::make_shared(1), std::memory_order_acq_rel); 37 | 38 | benchmark::DoNotOptimize(copy); 39 | benchmark::ClobberMemory(); 40 | } 41 | } 42 | 43 | static void atomic_shared_ptr_exchange_std(benchmark::State& state) { 44 | std::atomic> sp = std::make_shared(1); 45 | for (auto _ : state) { 46 | auto copy = sp.exchange(std::make_shared(1), std::memory_order_acq_rel); 47 | 48 | benchmark::DoNotOptimize(copy); 49 | benchmark::ClobberMemory(); 50 | } 51 | } 52 | 53 | BENCHMARK(atomic_shared_ptr_exchange_ciel)->Threads(std::thread::hardware_concurrency()); 54 | BENCHMARK(atomic_shared_ptr_exchange_std)->Threads(std::thread::hardware_concurrency()); 55 | 56 | static void atomic_shared_ptr_cas_ciel(benchmark::State& state) { 57 | ciel::atomic_shared_ptr sp = ciel::make_shared(1); 58 | ciel::shared_ptr copy; 59 | for (auto _ : state) { 60 | do { 61 | copy = sp.load(std::memory_order_relaxed); 62 | } while (!sp.compare_exchange_weak(copy, ciel::make_shared(1), std::memory_order_acq_rel)); 63 | 64 | benchmark::DoNotOptimize(copy); 65 | benchmark::ClobberMemory(); 66 | } 67 | } 68 | 69 | static void atomic_shared_ptr_cas_std(benchmark::State& state) { 70 | std::atomic> sp = std::make_shared(1); 71 | std::shared_ptr copy; 72 | for (auto _ : state) { 73 | do { 74 | copy = sp.load(std::memory_order_relaxed); 75 | } while (!sp.compare_exchange_weak(copy, std::make_shared(1), std::memory_order_acq_rel)); 76 | 77 | benchmark::DoNotOptimize(copy); 78 | benchmark::ClobberMemory(); 79 | } 80 | } 81 | 82 | BENCHMARK(atomic_shared_ptr_cas_ciel)->Threads(std::thread::hardware_concurrency()); 83 | BENCHMARK(atomic_shared_ptr_cas_std)->Threads(std::thread::hardware_concurrency()); 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /include/ciel/core/can_be_destroyed_from_base.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_CAN_BE_DESTROYED_FROM_BASE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_CAN_BE_DESTROYED_FROM_BASE_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | NAMESPACE_CIEL_BEGIN 11 | 12 | // See https://eel.is/c++draft/intro.defs#defns.dynamic.type-example-1 for definitions of static and dynamic type: 13 | // If a pointer p whose static type is “pointer to class B” is pointing to an object of class D, 14 | // derived from B, the dynamic type of the expression *p is “D”. 15 | // 16 | // See https://eel.is/c++draft/conv.qual#2 for definitions of similar: 17 | // Two types T1 and T2 are similar if they have qualification-decompositions with the same n... 18 | // 19 | // See https://eel.is/c++draft/expr.delete#3: 20 | // In a single-object delete expression, if the static type of the object to be deleted is not similar to 21 | // its dynamic type and the selected deallocation function is not a destroying operator delete, 22 | // the static type shall be a base class of the dynamic type of the object to be deleted 23 | // and the static type shall have a virtual destructor or the behavior is undefined. 24 | // 25 | // In conclusion: This class is useful for std::default_delete (used by std::unique_ptr), 26 | // if D is a derived class of some base B, then std::unique_ptr is implicitly convertible to std::unique_ptr. 27 | // The default deleter of the resulting std::unique_ptr will use operator delete for B, 28 | // leading to undefined behavior unless the destructor of B is virtual. 29 | // 30 | // Note: The same issue doesn't apply on std::shared_ptr since the managed object will 31 | // always be deleted from the Derived's side by control_block. 32 | // 33 | // Note: It may be safe deleting trivially destructible objects from Base, but for some reasons 34 | // standard refused to make it well-defined, see https://cplusplus.github.io/EWG/ewg-closed.html#99. 35 | 36 | // Inspired by: https://github.com/Quuxplusone/llvm-project/commit/d40169c2feebfbddf48df4e4e81cc0b62fa884be 37 | 38 | template 39 | struct is_similar : conditional_t::value || std::is_volatile::value || std::is_const::value 40 | || std::is_volatile::value, 41 | is_similar, remove_cv_t>, std::is_same> {}; 42 | 43 | template 44 | struct is_similar : is_similar {}; 45 | 46 | template 47 | struct is_similar : is_similar {}; 48 | 49 | template 50 | struct is_similar : is_similar {}; 51 | 52 | template 53 | struct is_similar : is_similar {}; 54 | 55 | template 56 | struct is_similar : is_similar {}; 57 | 58 | template 59 | struct is_similar : is_similar {}; 60 | 61 | template 62 | struct can_be_destroyed_from_base 63 | : conjunction, std::is_base_of, 64 | disjunction, std::has_virtual_destructor>> {}; 65 | 66 | NAMESPACE_CIEL_END 67 | 68 | #endif // CIELLAB_INCLUDE_CIEL_CORE_CAN_BE_DESTROYED_FROM_BASE_HPP_ 69 | -------------------------------------------------------------------------------- /include/ciel/core/aba.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_ABA_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_ABA_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | NAMESPACE_CIEL_BEGIN 12 | 13 | enum struct aba_implementation { 14 | PackedPtr, 15 | SpinlockPtr 16 | 17 | }; // enum struct aba_implementation 18 | 19 | template 20 | class aba; 21 | 22 | // The current main implementation of ABA uses packed_ptr to avoid DWCAS, 23 | // which limits it to support only 2^16 operations. So theoretically, in some absurdly pathological case, 24 | // the ABA problem could still happen if one thread is pre-empted and then 25 | // another thread(s) somehow performs more than 2^16 operations before the original thread wakes up again. 26 | template 27 | class aba { 28 | private: 29 | atomic_packed_ptr ptr_{nullptr}; 30 | 31 | using linked_type = typename decltype(ptr_)::value_type; 32 | 33 | struct impl { 34 | linked_type old_; 35 | aba* parent_; 36 | 37 | impl(const linked_type old, aba* parent) noexcept 38 | : old_(old), parent_(parent) {} 39 | 40 | CIEL_NODISCARD T* ptr() const noexcept { 41 | return old_.ptr(); 42 | } 43 | 44 | CIEL_NODISCARD bool store_conditional(T* ptr) noexcept { 45 | const linked_type temp{ptr, old_.count() + 1}; 46 | return parent_->ptr_.compare_exchange_weak(old_, temp, std::memory_order_acq_rel, 47 | std::memory_order_relaxed); 48 | } 49 | 50 | }; // struct impl 51 | 52 | public: 53 | CIEL_NODISCARD impl read() noexcept { 54 | return {ptr_.load(std::memory_order_relaxed), this}; 55 | } 56 | 57 | #if CIEL_STD_VER >= 17 58 | static constexpr bool is_always_lock_free = decltype(ptr_)::is_always_lock_free; 59 | static_assert(is_always_lock_free); 60 | #endif 61 | 62 | }; // class aba 63 | 64 | // Use spinlock_based ABA as a backup. 65 | template 66 | class aba { 67 | private: 68 | spinlock_ptr ptr_; 69 | 70 | struct impl { 71 | aba* parent_; 72 | 73 | impl(aba* parent) noexcept 74 | : parent_(parent) { 75 | CIEL_ASSERT(parent_->ptr_.is_locked()); 76 | } 77 | 78 | ~impl() { 79 | parent_->ptr_.unlock(std::memory_order_release); 80 | } 81 | 82 | CIEL_NODISCARD T* ptr() const noexcept { 83 | return parent_->ptr_.ptr(); 84 | } 85 | 86 | CIEL_NODISCARD bool store_conditional(T* ptr) const noexcept { 87 | parent_->ptr_.store(ptr, std::memory_order_relaxed); 88 | return true; 89 | } 90 | 91 | }; // struct impl 92 | 93 | public: 94 | CIEL_NODISCARD impl read() noexcept { 95 | CIEL_UNUSED(ptr_.lock(std::memory_order_acquire)); 96 | 97 | return {this}; 98 | } 99 | 100 | #if CIEL_STD_VER >= 17 101 | static constexpr bool is_always_lock_free = false; 102 | #endif 103 | 104 | }; // class aba 105 | 106 | NAMESPACE_CIEL_END 107 | 108 | #endif // CIELLAB_INCLUDE_CIEL_CORE_ABA_HPP_ 109 | -------------------------------------------------------------------------------- /include/ciel/range_destroyer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_RANGE_DESTROYER_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_RANGE_DESTROYER_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | NAMESPACE_CIEL_BEGIN 16 | 17 | template 18 | class range_destroyer; 19 | 20 | // Destroy ranges in destructor for exception handling. 21 | // Note that Allocator should be reference type. 22 | template 23 | class range_destroyer { 24 | static_assert(std::is_lvalue_reference::value, ""); 25 | 26 | private: 27 | using allocator_type = remove_reference_t; 28 | using pointer = typename std::allocator_traits::pointer; 29 | using alloc_traits = std::allocator_traits; 30 | 31 | static_assert(std::is_same::value, ""); 32 | 33 | pointer begin_; 34 | pointer end_; 35 | Allocator allocator_; 36 | 37 | public: 38 | range_destroyer(pointer begin, pointer end, allocator_type& alloc) noexcept 39 | : begin_{begin}, end_{end}, allocator_{alloc} {} 40 | 41 | range_destroyer(const range_destroyer&) = delete; 42 | range_destroyer& operator=(const range_destroyer&) = delete; 43 | 44 | ~range_destroyer() { 45 | CIEL_ASSERT(begin_ <= end_); 46 | 47 | for (; begin_ != end_; ++begin_) { 48 | alloc_traits::destroy(allocator_, ciel::to_address(begin_)); 49 | } 50 | } 51 | 52 | void advance_forward() noexcept { 53 | ++end_; 54 | } 55 | 56 | void advance_forward(const ptrdiff_t n) noexcept { 57 | end_ += n; 58 | } 59 | 60 | void advance_backward() noexcept { 61 | --begin_; 62 | } 63 | 64 | void advance_backward(const ptrdiff_t n) noexcept { 65 | begin_ -= n; 66 | } 67 | 68 | void release() noexcept { 69 | end_ = begin_; 70 | } 71 | 72 | }; // class range_destroyer 73 | 74 | template 75 | class range_destroyer { 76 | private: 77 | using value_type = T; 78 | using pointer = T*; 79 | 80 | pointer begin_; 81 | pointer end_; 82 | 83 | public: 84 | range_destroyer(pointer begin, pointer end) noexcept 85 | : begin_{begin}, end_{end} {} 86 | 87 | range_destroyer(const range_destroyer&) = delete; 88 | range_destroyer& operator=(const range_destroyer&) = delete; 89 | 90 | ~range_destroyer() { 91 | CIEL_ASSERT(begin_ <= end_); 92 | 93 | for (; begin_ != end_; ++begin_) { 94 | begin_->~value_type(); 95 | } 96 | } 97 | 98 | void advance_forward() noexcept { 99 | ++end_; 100 | } 101 | 102 | void advance_forward(const ptrdiff_t n) noexcept { 103 | end_ += n; 104 | } 105 | 106 | void advance_backward() noexcept { 107 | --begin_; 108 | } 109 | 110 | void advance_backward(const ptrdiff_t n) noexcept { 111 | begin_ -= n; 112 | } 113 | 114 | void release() noexcept { 115 | end_ = begin_; 116 | } 117 | 118 | }; // class range_destroyer 119 | 120 | NAMESPACE_CIEL_END 121 | 122 | #endif // CIELLAB_INCLUDE_CIEL_RANGE_DESTROYER_HPP_ 123 | -------------------------------------------------------------------------------- /include/ciel/test/random_access_iterator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_TEST_RANDOM_ACCESS_ITERATOR_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_TEST_RANDOM_ACCESS_ITERATOR_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | NAMESPACE_CIEL_BEGIN 13 | 14 | // Simulate random_access_iterator using T array base. 15 | 16 | template 17 | class RandomAccessIterator : public random_access_iterator_base> { 18 | public: 19 | using difference_type = ptrdiff_t; 20 | using value_type = T; 21 | using pointer = T*; 22 | using reference = T&; 23 | using iterator_category = std::random_access_iterator_tag; 24 | using iterator_concept = std::random_access_iterator_tag; 25 | 26 | private: 27 | pointer ptr; 28 | 29 | public: 30 | RandomAccessIterator() = default; 31 | 32 | RandomAccessIterator(const pointer p) noexcept 33 | : ptr(p) {} 34 | 35 | void go_next() noexcept { 36 | CIEL_ASSERT(ptr != nullptr); 37 | ++ptr; 38 | } 39 | 40 | void go_prev() noexcept { 41 | CIEL_ASSERT(ptr != nullptr); 42 | --ptr; 43 | } 44 | 45 | void advance(difference_type n) noexcept { 46 | CIEL_ASSERT(ptr != nullptr); 47 | ptr += n; 48 | } 49 | 50 | CIEL_NODISCARD reference operator*() const noexcept { 51 | CIEL_ASSERT(ptr != nullptr); 52 | return *ptr; 53 | } 54 | 55 | CIEL_NODISCARD pointer operator->() const noexcept { 56 | CIEL_ASSERT(ptr != nullptr); 57 | return ptr; 58 | } 59 | 60 | CIEL_NODISCARD reference operator[](difference_type n) const noexcept { 61 | CIEL_ASSERT(ptr != nullptr); 62 | return *(ptr + n); 63 | } 64 | 65 | CIEL_NODISCARD pointer base() const noexcept { 66 | return ptr; 67 | } 68 | 69 | CIEL_NODISCARD friend bool operator==(const RandomAccessIterator& lhs, const RandomAccessIterator& rhs) noexcept { 70 | return lhs.base() == rhs.base(); 71 | } 72 | 73 | CIEL_NODISCARD friend bool operator<(const RandomAccessIterator& lhs, const RandomAccessIterator& rhs) noexcept { 74 | return lhs.base() < rhs.base(); 75 | } 76 | 77 | CIEL_NODISCARD friend RandomAccessIterator operator+(RandomAccessIterator iter, difference_type n) noexcept { 78 | iter += n; 79 | return iter; 80 | } 81 | 82 | CIEL_NODISCARD friend RandomAccessIterator operator+(difference_type n, RandomAccessIterator iter) noexcept { 83 | iter += n; 84 | return iter; 85 | } 86 | 87 | CIEL_NODISCARD friend RandomAccessIterator operator-(RandomAccessIterator iter, difference_type n) noexcept { 88 | iter -= n; 89 | return iter; 90 | } 91 | 92 | CIEL_NODISCARD friend difference_type operator-(const RandomAccessIterator& lhs, 93 | const RandomAccessIterator& rhs) noexcept { 94 | return lhs.base() - rhs.base(); 95 | } 96 | 97 | template 98 | void operator,(const U&) = delete; 99 | 100 | }; // class RandomAccessIterator 101 | 102 | #if CIEL_STD_VER >= 20 103 | static_assert(std::random_access_iterator>); 104 | #endif 105 | 106 | NAMESPACE_CIEL_END 107 | 108 | #endif // CIELLAB_INCLUDE_CIEL_TEST_RANDOM_ACCESS_ITERATOR_HPP_ 109 | -------------------------------------------------------------------------------- /include/ciel/observer_ptr.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_OBSERVER_PTR_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_OBSERVER_PTR_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | NAMESPACE_CIEL_BEGIN 14 | 15 | template 16 | class observer_ptr { 17 | static_assert(!std::is_reference::value, "W shall not be a reference type"); 18 | 19 | public: 20 | using element_type = W; 21 | 22 | private: 23 | element_type* ptr_; 24 | 25 | public: 26 | observer_ptr() noexcept 27 | : ptr_{nullptr} {} 28 | 29 | observer_ptr(nullptr_t) noexcept 30 | : ptr_{nullptr} {} 31 | 32 | explicit observer_ptr(element_type* p) noexcept 33 | : ptr_{p} {} 34 | 35 | template::value> = 0> 36 | observer_ptr(observer_ptr other) noexcept 37 | : ptr_{other.ptr_} {} 38 | 39 | CIEL_NODISCARD element_type* release() noexcept { 40 | element_type* res = ptr_; 41 | ptr_ = nullptr; 42 | return res; 43 | } 44 | 45 | void reset(element_type* p = nullptr) noexcept { 46 | ptr_ = p; 47 | } 48 | 49 | void swap(observer_ptr& other) noexcept { 50 | std::swap(ptr_, other.ptr_); 51 | } 52 | 53 | CIEL_NODISCARD element_type* get() const noexcept { 54 | return ptr_; 55 | } 56 | 57 | CIEL_NODISCARD explicit operator bool() const noexcept { 58 | return get() != nullptr; 59 | } 60 | 61 | CIEL_NODISCARD add_lvalue_reference_t operator*() const noexcept { 62 | CIEL_ASSERT(*this); 63 | 64 | return *get(); 65 | } 66 | 67 | CIEL_NODISCARD element_type* operator->() const noexcept { 68 | CIEL_ASSERT(*this); 69 | 70 | return get(); 71 | } 72 | 73 | CIEL_NODISCARD explicit operator element_type*() const noexcept { 74 | return ptr_; 75 | } 76 | 77 | }; // class observer_ptr 78 | 79 | template 80 | struct is_trivially_relocatable> : std::true_type {}; 81 | 82 | template 83 | CIEL_NODISCARD observer_ptr make_observer(W* p) noexcept { 84 | return observer_ptr{p}; 85 | } 86 | 87 | template 88 | CIEL_NODISCARD bool operator==(const observer_ptr& p1, const observer_ptr& p2) { 89 | return p1.get() == p2.get(); 90 | } 91 | 92 | template 93 | CIEL_NODISCARD bool operator==(const observer_ptr& p, nullptr_t) noexcept { 94 | return !p; 95 | } 96 | 97 | template 98 | CIEL_NODISCARD bool operator==(nullptr_t, const observer_ptr& p) noexcept { 99 | return !p; 100 | } 101 | 102 | template 103 | CIEL_NODISCARD bool operator<(const observer_ptr& p1, const observer_ptr& p2) { 104 | using W3 = common_type_t; 105 | return std::less()(p1.get(), p2.get()); 106 | } 107 | 108 | NAMESPACE_CIEL_END 109 | 110 | namespace std { 111 | 112 | template 113 | void swap(ciel::observer_ptr& lhs, ciel::observer_ptr& rhs) noexcept { 114 | lhs.swap(rhs); 115 | } 116 | 117 | template 118 | struct hash> { 119 | CIEL_NODISCARD size_t operator()(ciel::observer_ptr p) const noexcept { 120 | return hash()(p.get()); 121 | } 122 | 123 | }; // struct hash> 124 | 125 | } // namespace std 126 | 127 | #endif // CIELLAB_INCLUDE_CIEL_OBSERVER_PTR_HPP_ 128 | -------------------------------------------------------------------------------- /test/src/vector/resize.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace ciel; 8 | 9 | namespace { 10 | 11 | template 12 | void test_resize_impl(::testing::Test*) { 13 | using T = typename C::value_type; 14 | 15 | vector v(100); 16 | 17 | v.resize(50); 18 | ASSERT_EQ(v.size(), 50); 19 | ASSERT_GE(v.capacity(), 100); 20 | ASSERT_EQ(v, vector(50)); 21 | 22 | v.resize(200); 23 | ASSERT_EQ(v.size(), 200); 24 | ASSERT_GE(v.capacity(), 200); 25 | ASSERT_TRUE(std::all_of(v.begin(), v.end(), [](int i) { 26 | return i == 0; 27 | })); 28 | } 29 | 30 | template 31 | void test_resize_value_impl(::testing::Test*) { 32 | using T = typename C::value_type; 33 | 34 | vector v(100); 35 | 36 | v.resize(50, 1); 37 | ASSERT_EQ(v.size(), 50); 38 | ASSERT_GE(v.capacity(), 100); 39 | ASSERT_EQ(v, vector(50)); 40 | 41 | v.resize(200, 1); 42 | ASSERT_EQ(v.size(), 200); 43 | ASSERT_GE(v.capacity(), 200); 44 | ASSERT_TRUE(std::all_of(v.begin(), v.begin() + 50, [](int i) { 45 | return i == 0; 46 | })); 47 | ASSERT_TRUE(std::all_of(v.begin() + 50, v.end(), [](int i) { 48 | return i == 1; 49 | })); 50 | } 51 | 52 | template 53 | void test_resize_self_value_impl(::testing::Test*) { 54 | using T = typename C::value_type; 55 | 56 | { 57 | // expansion 58 | vector v(2, 42); 59 | 60 | v.resize(v.capacity() + 1, v[1]); 61 | ASSERT_TRUE(std::all_of(v.begin(), v.end(), [](int i) { 62 | return i == 42; 63 | })); 64 | } 65 | { 66 | vector v(2, 42); 67 | v.reserve(10); 68 | 69 | v.resize(4, v[1]); 70 | ASSERT_EQ(v.size(), 4); 71 | ASSERT_TRUE(std::all_of(v.begin(), v.end(), [](int i) { 72 | return i == 42; 73 | })); 74 | } 75 | } 76 | 77 | } // namespace 78 | 79 | TEST(vector, resize) { 80 | test_resize_impl>(this); 81 | test_resize_impl>(this); 82 | test_resize_impl>(this); 83 | test_resize_impl>(this); 84 | 85 | test_resize_impl>>(this); 86 | test_resize_impl>>(this); 87 | test_resize_impl>>(this); 88 | test_resize_impl>>(this); 89 | } 90 | 91 | TEST(vector, resize_value) { 92 | test_resize_value_impl>(this); 93 | test_resize_value_impl>(this); 94 | test_resize_value_impl>(this); 95 | test_resize_value_impl>(this); 96 | 97 | test_resize_value_impl>>(this); 98 | test_resize_value_impl>>(this); 99 | test_resize_value_impl>>(this); 100 | test_resize_value_impl>>(this); 101 | } 102 | 103 | TEST(vector, resize_self_value) { 104 | test_resize_self_value_impl>(this); 105 | test_resize_self_value_impl>(this); 106 | test_resize_self_value_impl>(this); 107 | test_resize_self_value_impl>(this); 108 | 109 | test_resize_self_value_impl>>(this); 110 | test_resize_self_value_impl>>(this); 111 | test_resize_self_value_impl>>(this); 112 | test_resize_self_value_impl>>(this); 113 | } 114 | -------------------------------------------------------------------------------- /include/ciel/core/reference_counter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_REFERENCE_COUNTER_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_REFERENCE_COUNTER_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | NAMESPACE_CIEL_BEGIN 12 | 13 | // Inspired by Daniel Anderson's CppCon talk: https://www.youtube.com/watch?v=kPh8pod0-gk 14 | // This class is useful for reference counting, i.e. shared_ptr's shared_count, when the count hits zero, 15 | // the managed object will be deleted and the count shall not increment from zero thereafter. 16 | // 17 | // Contracts: 18 | // 1. The counter should start at one, but for symmetry purposes, 19 | // it can start at zero and increment immediately afterwards. 20 | // 2. Once the counter decrements to zero, it will stuck at it and never increment again. 21 | // 3. Before the counter hits zero, the number of increment and decrement calls should be equal. 22 | // After the counter hits zero, no further decrement calls should be invoked. 23 | // This aligns with reference counting semantics, where the one that calls decrement 24 | // should be the same one that previously called increment, and there is no possibilities to decrement from zero. 25 | 26 | class reference_counter { 27 | private: 28 | static constexpr size_t zero_flag = static_cast(1) << (std::numeric_limits::digits - 1); 29 | std::atomic impl_; 30 | 31 | public: 32 | reference_counter(const size_t i = 1) noexcept 33 | : impl_(i) {} 34 | 35 | reference_counter(const reference_counter&) = delete; 36 | reference_counter& operator=(const reference_counter&) = delete; 37 | 38 | // Returns zero only if zero_flag is being set, returns one in zero_pending stage. 39 | size_t load(const std::memory_order order = std::memory_order_seq_cst) const noexcept { 40 | const size_t res = impl_.load(order); 41 | if (res & zero_flag) { 42 | return 0; 43 | } 44 | 45 | return res != 0 ? res : 1; 46 | } 47 | 48 | // Returns false if counter is already stuck to zero. 49 | bool increment_if_not_zero(const size_t diff, const std::memory_order order = std::memory_order_seq_cst) noexcept { 50 | const size_t res = impl_.fetch_add(diff, order); 51 | return (res & zero_flag) == 0; 52 | } 53 | 54 | // Returns true only if this operation is responsible for afterwards cleanup, i.e. shared_ptr object deletion. 55 | bool decrement(const size_t diff, const std::memory_order order = std::memory_order_seq_cst) noexcept { 56 | const size_t res = impl_.fetch_sub(diff, order); 57 | CIEL_ASSERT_M(res >= diff, "reference_counter::decrement: {} -= {};", res, diff); 58 | 59 | if (res == diff) { 60 | size_t expected = 0; 61 | // It must be `compare_exchange_strong` because the only way it can fail must be 62 | // if some other threads have performed `fetch_add` successfully. 63 | // Any spurious failure could lead to resource leaks. For instance, 64 | // if this is the last function call of the object, it must return true to prevent such leaks. 65 | if CIEL_LIKELY (impl_.compare_exchange_strong(expected, zero_flag)) { 66 | return true; 67 | } 68 | } 69 | 70 | return false; 71 | } 72 | 73 | operator size_t() const noexcept { 74 | return load(); 75 | } 76 | 77 | #if CIEL_STD_VER >= 17 78 | static constexpr bool is_always_lock_free = decltype(impl_)::is_always_lock_free; 79 | static_assert(is_always_lock_free); 80 | #endif 81 | 82 | }; // class reference_counter 83 | 84 | NAMESPACE_CIEL_END 85 | 86 | #endif // CIELLAB_INCLUDE_CIEL_CORE_REFERENCE_COUNTER_HPP_ 87 | -------------------------------------------------------------------------------- /include/ciel/swap.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_SWAP_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_SWAP_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | NAMESPACE_CIEL_BEGIN 14 | 15 | // Inspired by Arthur O’Dwyer's libc++ fork: 16 | // https://github.com/Quuxplusone/llvm-project/blob/6af94af4d0351649eaef8454e7fb58a1175a9580/libcxx/include/__utility/swap.h 17 | 18 | // relocatable_swap 19 | template 20 | void relocatable_swap(T& lhs, T& rhs) noexcept { 21 | constexpr size_t buffer_bytes = datasizeof::value; 22 | unsigned char buffer[buffer_bytes]; 23 | 24 | ciel::memcpy(std::addressof(buffer), std::addressof(lhs), buffer_bytes); 25 | ciel::memmove(std::addressof(lhs), std::addressof(rhs), buffer_bytes); 26 | ciel::memcpy(std::addressof(rhs), std::addressof(buffer), buffer_bytes); 27 | } 28 | 29 | template 30 | void relocatable_swap(T (&lhs)[N], T (&rhs)[N]) noexcept { 31 | constexpr size_t buffer_bytes = sizeof(lhs); 32 | unsigned char buffer[buffer_bytes]; 33 | 34 | ciel::memcpy(std::addressof(buffer), std::addressof(lhs), buffer_bytes); 35 | ciel::memmove(std::addressof(lhs), std::addressof(rhs), buffer_bytes); 36 | ciel::memcpy(std::addressof(rhs), std::addressof(buffer), buffer_bytes); 37 | } 38 | 39 | inline void relocatable_swap(void* f1, void* f2, size_t bytes) noexcept { 40 | constexpr size_t buffer_bytes = 128; 41 | unsigned char buffer[buffer_bytes]; 42 | 43 | unsigned char* first1 = static_cast(f1); 44 | unsigned char* first2 = static_cast(f2); 45 | 46 | while (bytes >= buffer_bytes) { 47 | ciel::memcpy(std::addressof(buffer), first1, buffer_bytes); 48 | ciel::memmove(first1, first2, buffer_bytes); 49 | ciel::memcpy(first2, std::addressof(buffer), buffer_bytes); 50 | 51 | first1 += buffer_bytes; 52 | first2 += buffer_bytes; 53 | bytes -= buffer_bytes; 54 | } 55 | 56 | if (bytes != 0) { 57 | ciel::memcpy(std::addressof(buffer), first1, bytes); 58 | ciel::memmove(first1, first2, bytes); 59 | ciel::memcpy(first2, std::addressof(buffer), bytes); 60 | } 61 | } 62 | 63 | NAMESPACE_CIEL_END 64 | 65 | namespace std { 66 | 67 | #if CIEL_STD_VER < 20 68 | template::value> = 0> 69 | T* swap_ranges(T* first1, T* last1, T* first2) noexcept { 70 | const size_t N = last1 - first1; 71 | const size_t swap_bytes = N * sizeof(T); 72 | 73 | ciel::relocatable_swap(first1, first2, swap_bytes); 74 | 75 | return first2 + N; 76 | } 77 | #else 78 | template 79 | requires ciel::is_trivially_relocatable::value 80 | void swap(T& a, T& b) noexcept { 81 | ciel::relocatable_swap(a, b); 82 | } 83 | 84 | template 85 | requires ciel::is_trivially_relocatable::value 86 | void swap(T (&a)[N], T (&b)[N]) noexcept { 87 | ciel::relocatable_swap(a, b); 88 | } 89 | 90 | template::value_type, 92 | class U = typename std::iterator_traits::value_type> 93 | requires std::is_same_v && ciel::is_trivially_relocatable::value 94 | ForwardIt2 swap_ranges(ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2) noexcept { 95 | const size_t N = last1 - first1; 96 | const size_t swap_bytes = N * sizeof(T); 97 | 98 | ciel::relocatable_swap(std::to_address(first1), std::to_address(first2), swap_bytes); 99 | 100 | return first2 + N; 101 | } 102 | #endif 103 | 104 | } // namespace std 105 | 106 | #endif // CIELLAB_INCLUDE_CIEL_SWAP_HPP_ 107 | -------------------------------------------------------------------------------- /test/src/shared_ptr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace ciel; 14 | 15 | TEST(shared_ptr, default_constuctor) { 16 | const shared_ptr s; 17 | } 18 | 19 | TEST(shared_ptr, move_constructor) { 20 | shared_ptr src(new int(1729)); 21 | 22 | ASSERT_TRUE(src); 23 | ASSERT_EQ(*src, 1729); 24 | 25 | const shared_ptr dest(std::move(src)); 26 | 27 | ASSERT_FALSE(src); 28 | ASSERT_TRUE(dest); 29 | ASSERT_EQ(*dest, 1729); 30 | } 31 | 32 | TEST(shared_ptr, move_assign) { 33 | shared_ptr src(new int(123)); 34 | shared_ptr dest(new int(888)); 35 | 36 | ASSERT_TRUE(src); 37 | ASSERT_EQ(*src, 123); 38 | 39 | ASSERT_TRUE(dest); 40 | ASSERT_EQ(*dest, 888); 41 | 42 | dest = std::move(src); 43 | 44 | ASSERT_FALSE(src); 45 | ASSERT_TRUE(dest); 46 | ASSERT_EQ(*dest, 123); 47 | } 48 | 49 | TEST(shared_ptr, alias_move_constructor) { 50 | class Base { 51 | public: 52 | CIEL_NODISCARD virtual std::string str() const noexcept { 53 | return "Base"; 54 | } 55 | 56 | protected: 57 | ~Base() = default; 58 | 59 | }; // class Base 60 | 61 | class Derived final : public Base { 62 | public: 63 | CIEL_NODISCARD std::string str() const noexcept override { 64 | return "Derived"; 65 | } 66 | 67 | }; // class Derived 68 | 69 | { 70 | shared_ptr src{new Derived{}}; 71 | 72 | ASSERT_TRUE(src); 73 | ASSERT_EQ(src->str(), "Derived"); 74 | 75 | const shared_ptr dest{std::move(src)}; 76 | 77 | ASSERT_FALSE(src); 78 | ASSERT_TRUE(dest); 79 | ASSERT_EQ(dest->str(), "Derived"); 80 | } 81 | 82 | { 83 | // We shall not call the deleter on the base class pointer. 84 | const shared_ptr s1{new Derived{}}; 85 | const shared_ptr s2 = make_shared(); 86 | } 87 | } 88 | 89 | TEST(shared_ptr, make_shared) { 90 | const shared_ptr p = make_shared(42); 91 | 92 | ASSERT_EQ(*p, 42); 93 | ASSERT_EQ(p.use_count(), 1); 94 | } 95 | 96 | TEST(shared_ptr, make_shared_non_trivial) { 97 | const shared_ptr s = make_shared(1000, 'b'); 98 | 99 | ASSERT_EQ(*s, std::string(1000, 'b')); 100 | ASSERT_EQ(s.use_count(), 1); 101 | } 102 | 103 | TEST(shared_ptr, custom_deleter) { 104 | int count = 0; 105 | 106 | { 107 | const shared_ptr s{new int{123}, [&count](const int* ptr) { 108 | ++count; 109 | delete ptr; 110 | }}; 111 | 112 | ASSERT_EQ(*s, 123); 113 | ASSERT_EQ(s.use_count(), 1); 114 | } 115 | 116 | ASSERT_EQ(count, 1); 117 | 118 | { 119 | const shared_ptr s{nullptr, [&count](int*) { 120 | ++count; 121 | }}; 122 | 123 | ASSERT_EQ(s.use_count(), 1); 124 | } 125 | 126 | ASSERT_EQ(count, 2); 127 | } 128 | 129 | TEST(shared_ptr, concurrent_store_and_loads) { 130 | constexpr size_t threads_num = 64; 131 | constexpr size_t operations_num = 10000; 132 | 133 | shared_ptr s{new size_t{123}}; 134 | SimpleLatch go{threads_num}; 135 | 136 | vector consumers(reserve_capacity, threads_num); 137 | 138 | for (size_t i = 0; i < threads_num; ++i) { 139 | consumers.unchecked_emplace_back([&s, &go] { 140 | go.arrive_and_wait(); 141 | 142 | for (size_t j = 0; j < operations_num; ++j) { 143 | CIEL_UNUSED(shared_ptr{s}); 144 | } 145 | }); 146 | } 147 | 148 | for (auto& t : consumers) { 149 | t.join(); 150 | } 151 | 152 | ASSERT_EQ(s.use_count(), 1); 153 | } 154 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: -4 2 | AlignAfterOpenBracket: Align 3 | AlignArrayOfStructures: Right 4 | AlignConsecutiveAssignments: true 5 | AlignConsecutiveBitFields: true 6 | AlignConsecutiveMacros: true 7 | AlignConsecutiveShortCaseStatements: 8 | Enabled: true 9 | AcrossEmptyLines: true 10 | AcrossComments: true 11 | AlignCaseColons: false 12 | AlignEscapedNewlines: Left 13 | AlignOperands: AlignAfterOperator 14 | AlignTrailingComments: 15 | Kind: Always 16 | OverEmptyLines: 1 17 | AllowAllArgumentsOnNextLine: false 18 | AllowAllConstructorInitializersOnNextLine: false 19 | AllowAllParametersOfDeclarationOnNextLine: false 20 | AllowShortBlocksOnASingleLine: Empty 21 | AllowShortCaseLabelsOnASingleLine: false 22 | AllowShortEnumsOnASingleLine: false 23 | AllowShortFunctionsOnASingleLine: Empty 24 | AllowShortIfStatementsOnASingleLine: Never 25 | AllowShortLambdasOnASingleLine: Empty 26 | AllowShortLoopsOnASingleLine: false 27 | AlwaysBreakBeforeMultilineStrings: false 28 | AlwaysBreakTemplateDeclarations: Yes 29 | BinPackArguments: true 30 | BinPackParameters: true 31 | BitFieldColonSpacing: Both 32 | BraceWrapping: 33 | AfterCaseLabel: false 34 | AfterClass: false 35 | AfterControlStatement: Never 36 | AfterEnum: false 37 | AfterFunction: false 38 | AfterNamespace: false 39 | AfterStruct: false 40 | AfterUnion: false 41 | AfterExternBlock: false 42 | BeforeCatch: false 43 | BeforeElse: false 44 | BeforeLambdaBody: false 45 | BeforeWhile: false 46 | IndentBraces: false 47 | SplitEmptyFunction: false 48 | SplitEmptyRecord: false 49 | SplitEmptyNamespace: false 50 | BreakAfterReturnType: Automatic 51 | BreakBeforeBinaryOperators: NonAssignment 52 | BreakBeforeBraces: Custom 53 | BreakBeforeConceptDeclarations: Always 54 | BreakBeforeTernaryOperators: true 55 | BreakConstructorInitializers: BeforeColon 56 | BreakInheritanceList: AfterComma 57 | BreakStringLiterals: false 58 | ColumnLimit: 120 59 | CompactNamespaces: false 60 | ConstructorInitializerIndentWidth: 4 61 | ContinuationIndentWidth: 4 62 | Cpp11BracedListStyle: true 63 | EmptyLineAfterAccessModifier: Never 64 | EmptyLineBeforeAccessModifier: Always 65 | FixNamespaceComments: true 66 | IfMacros: ['CIEL_CATCH'] 67 | IndentAccessModifiers: false 68 | IndentCaseBlocks: false 69 | IndentCaseLabels: true 70 | IndentExternBlock: NoIndent 71 | IndentGotoLabels: true 72 | IndentPPDirectives: AfterHash 73 | IndentRequiresClause: true 74 | IndentWidth: 4 75 | IndentWrappedFunctionNames: false 76 | InsertBraces: true 77 | InsertNewlineAtEOF: true 78 | KeepEmptyLinesAtTheStartOfBlocks: false 79 | LambdaBodyIndentation: Signature 80 | Language: Cpp 81 | LineEnding: LF 82 | MaxEmptyLinesToKeep: 1 83 | NamespaceIndentation: None 84 | PPIndentWidth: 2 85 | PackConstructorInitializers: NextLineOnly 86 | PenaltyReturnTypeOnItsOwnLine: 100 87 | PointerAlignment: Left 88 | QualifierAlignment: Custom 89 | QualifierOrder: ['friend', 'inline', 'static', 'constexpr', 'const', 'volatile', 'type'] 90 | ReferenceAlignment: Left 91 | ReflowComments: true 92 | RemoveSemicolon: true 93 | RequiresClausePosition: OwnLine 94 | RequiresExpressionIndentation: Keyword 95 | SeparateDefinitionBlocks: Always 96 | ShortNamespaceLines : 0 97 | SortIncludes: CaseSensitive 98 | SortUsingDeclarations: Lexicographic 99 | SpaceAfterCStyleCast: false 100 | SpaceAfterLogicalNot: false 101 | SpaceAfterTemplateKeyword: false 102 | SpaceBeforeAssignmentOperators: true 103 | SpaceBeforeCaseColon: true 104 | SpaceBeforeCpp11BracedList: false 105 | SpaceBeforeCtorInitializerColon: true 106 | SpaceBeforeInheritanceColon: true 107 | SpaceBeforeParens: ControlStatements 108 | SpaceBeforeRangeBasedForLoopColon: true 109 | SpaceBeforeSquareBrackets: false 110 | SpaceInEmptyBlock: false 111 | SpaceInEmptyParentheses: false 112 | SpacesBeforeTrailingComments: 1 113 | SpacesInAngles: Never 114 | SpacesInCStyleCastParentheses: false 115 | SpacesInConditionalStatement: false 116 | SpacesInContainerLiterals: false 117 | SpacesInLineCommentPrefix: 118 | Minimum: 1 119 | Maximum: 1 120 | SpacesInParentheses: false 121 | SpacesInSquareBrackets: false 122 | TabWidth: 4 123 | UseTab: Never 124 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(ciellab_test LANGUAGES CXX) 3 | 4 | set(CMAKE_BUILD_TYPE "Debug") 5 | 6 | include(FetchContent) 7 | FetchContent_Declare( 8 | googletest 9 | GIT_REPOSITORY https://github.com/google/googletest.git 10 | GIT_TAG 58d77fa8070e8cec2dc1ed015d66b454c8d78850 # release-1.12.1, last version supporting C++11 11 | ) 12 | FetchContent_MakeAvailable(googletest) 13 | 14 | set(SOURCES 15 | src/main.cpp 16 | src/allocator_traits.cpp 17 | src/atomic_shared_ptr.cpp 18 | src/avl_tree.cpp 19 | src/can_be_destroyed_from_base.cpp 20 | src/compressed_pair.cpp 21 | src/cstring.cpp 22 | src/do_if_noexcept.cpp 23 | src/finally.cpp 24 | src/function.cpp 25 | src/function/constructor.cpp 26 | src/function/overload_resolution.cpp 27 | src/hazard_pointer.cpp 28 | src/is_nullable.cpp 29 | src/list.cpp 30 | src/message.cpp 31 | src/mpsc_queue.cpp 32 | src/observer_ptr.cpp 33 | src/pipe.cpp 34 | src/rb_tree.cpp 35 | src/reference_counter.cpp 36 | src/shared_ptr.cpp 37 | src/singleton.cpp 38 | src/spinlock_ptr.cpp 39 | src/swap.cpp 40 | src/to_chars.cpp 41 | src/treiber_stack.cpp 42 | src/with_lock.cpp 43 | src/worth_move.cpp 44 | src/inplace_vector/assign.cpp 45 | src/inplace_vector/constructor.cpp 46 | src/inplace_vector/emplace.cpp 47 | src/inplace_vector/erase.cpp 48 | src/inplace_vector/insert.cpp 49 | src/inplace_vector/trait.cpp 50 | src/inplace_vector/try_append_range.cpp 51 | src/vector/assign.cpp 52 | src/vector/capacity.cpp 53 | src/vector/compare.cpp 54 | src/vector/constructor.cpp 55 | src/vector/data.cpp 56 | src/vector/emplace_back.cpp 57 | src/vector/emplace.cpp 58 | src/vector/empty.cpp 59 | src/vector/erase.cpp 60 | src/vector/exception_safety.cpp 61 | src/vector/insert.cpp 62 | src/vector/max_size.cpp 63 | src/vector/print.cpp 64 | src/vector/reserve.cpp 65 | src/vector/resize.cpp 66 | src/vector/shrink_to_fit.cpp 67 | src/vector/size.cpp 68 | src/vector/swap.cpp 69 | ) 70 | 71 | set(CMAKE_CXX_STANDARD_OPTIONS 72 | 11 73 | 14 74 | 17 75 | 20 76 | ) 77 | 78 | function (add_ciellab_test CXX_STANDARD ENABLE_EXCEPTIONS ENABLE_RTTI) 79 | 80 | set(SUB_PROJECT_NAME "${PROJECT_NAME}_${CXX_STANDARD}_exceptions_${ENABLE_EXCEPTIONS}_rtti_${ENABLE_RTTI}") 81 | 82 | add_executable(${SUB_PROJECT_NAME} ${SOURCES}) 83 | 84 | if (CXX_STANDARD EQUAL 11) 85 | target_compile_options(${SUB_PROJECT_NAME} PRIVATE -std=c++11) 86 | elseif (CXX_STANDARD EQUAL 14) 87 | target_compile_options(${SUB_PROJECT_NAME} PRIVATE -std=c++14) 88 | elseif (CXX_STANDARD EQUAL 17) 89 | target_compile_options(${SUB_PROJECT_NAME} PRIVATE -std=c++17) 90 | elseif (CXX_STANDARD EQUAL 20) 91 | target_compile_options(${SUB_PROJECT_NAME} PRIVATE -std=c++20) 92 | else () 93 | message(FATAL_ERROR "CXX_STANDARD ${CXX_STANDARD} is not available.") 94 | endif () 95 | 96 | target_link_libraries(${SUB_PROJECT_NAME} PRIVATE ciellab GTest::gtest) 97 | 98 | include(GoogleTest) 99 | gtest_discover_tests(${SUB_PROJECT_NAME}) 100 | 101 | target_compile_options(${SUB_PROJECT_NAME} PRIVATE -Wall -Wextra -Werror) 102 | 103 | if (ENABLE_EXCEPTIONS STREQUAL "on") 104 | target_compile_options(${SUB_PROJECT_NAME} PRIVATE -fexceptions) 105 | else () 106 | target_compile_options(${SUB_PROJECT_NAME} PRIVATE -fno-exceptions) 107 | endif () 108 | 109 | if (ENABLE_RTTI STREQUAL "on") 110 | target_compile_options(${SUB_PROJECT_NAME} PRIVATE -frtti) 111 | else () 112 | target_compile_options(${SUB_PROJECT_NAME} PRIVATE -fno-rtti) 113 | endif () 114 | 115 | endfunction () 116 | 117 | foreach (CXX_STANDARD ${CMAKE_CXX_STANDARD_OPTIONS}) 118 | 119 | add_ciellab_test(${CXX_STANDARD} on on) 120 | add_ciellab_test(${CXX_STANDARD} on off) 121 | add_ciellab_test(${CXX_STANDARD} off on) 122 | add_ciellab_test(${CXX_STANDARD} off off) 123 | 124 | endforeach () 125 | -------------------------------------------------------------------------------- /include/ciel/core/mpsc_queue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_CORE_MPSC_QUEUE_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_CORE_MPSC_QUEUE_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | NAMESPACE_CIEL_BEGIN 12 | 13 | // Inspired by Microsoft snmalloc's implementation. 14 | // https://github.com/microsoft/snmalloc/blob/main/snmalloc.pdf 15 | // See Figure 3 in snmalloc paper for thorough explanations. 16 | 17 | template 18 | class mpsc_queue { 19 | static_assert(std::is_same>::value, ""); 20 | 21 | private: 22 | alignas(cacheline_size) std::atomic front_{nullptr}; 23 | alignas(cacheline_size) std::atomic back_{nullptr}; 24 | aligned_storage stub_; 25 | 26 | public: 27 | mpsc_queue() noexcept { 28 | T* stub_ptr = reinterpret_cast(&stub_); 29 | using AtomicTPtr = std::atomic; 30 | ::new (&(stub_ptr->next)) AtomicTPtr(nullptr); 31 | front_.store(stub_ptr, std::memory_order_relaxed); 32 | } 33 | 34 | mpsc_queue(const mpsc_queue&) = delete; 35 | mpsc_queue& operator=(const mpsc_queue&) = delete; 36 | 37 | ~mpsc_queue() { 38 | T* stub_ptr = reinterpret_cast(&stub_); 39 | using AtomicTPtr = std::atomic; 40 | stub_ptr->next.~AtomicTPtr(); 41 | } 42 | 43 | void push(T* t) noexcept { 44 | push(t, t); 45 | } 46 | 47 | void push(T* first, T* last) noexcept { 48 | CIEL_ASSERT(first != nullptr); 49 | CIEL_ASSERT(last != nullptr); 50 | 51 | last->next.store(nullptr, std::memory_order_relaxed); 52 | // It needs to be release, so nullptr in next is visible, and needs to be acquire, 53 | // so linking into the list does not race with other threads nullptr-initing of the next field. 54 | T* prev = back_.exchange(last, std::memory_order_acq_rel); 55 | 56 | if CIEL_LIKELY (prev != nullptr) { // not at stub state 57 | prev->next.store(first, std::memory_order_relaxed); 58 | return; 59 | } 60 | 61 | // at stub state, remove stub 62 | front_.store(first, std::memory_order_relaxed); 63 | } 64 | 65 | // ProcessEachNode is a monadic predicate callback type, to properly process each node. 66 | // The callback may return false to stop the iteration early, but must have processed the element it was given. 67 | template 68 | void process(ProcessEachNode&& process_each_node) noexcept { 69 | T* cur = front_.load(std::memory_order_relaxed); 70 | T* b = back_.load(std::memory_order_relaxed); 71 | 72 | if CIEL_UNLIKELY (cur == reinterpret_cast(&stub_)) { // at stub state 73 | return; 74 | } 75 | 76 | while (cur != b) { 77 | T* next = cur->next.load(std::memory_order_relaxed); 78 | 79 | // If the push is happening halfway (back_ is updated while prev->next is not updated yet). 80 | if CIEL_UNLIKELY (next == nullptr) { 81 | break; 82 | } 83 | 84 | if CIEL_UNLIKELY (!process_each_node(cur)) { 85 | front_.store(next, std::memory_order_relaxed); 86 | return; 87 | } 88 | 89 | cur = next; 90 | } 91 | 92 | // Probably only one node left. 93 | front_.store(cur, std::memory_order_relaxed); 94 | } 95 | 96 | // ProcessEachNode callback must process all nodes left. 97 | // No other related threads shall exist here. 98 | template 99 | void destructive_process(ProcessEachNode&& process_each_node) noexcept { 100 | T* cur = front_.load(std::memory_order_relaxed); 101 | 102 | if CIEL_UNLIKELY (cur == reinterpret_cast(&stub_)) { // at stub state 103 | return; 104 | } 105 | 106 | while (cur != nullptr) { 107 | T* next = cur->next.load(std::memory_order_relaxed); 108 | process_each_node(cur); 109 | cur = next; 110 | } 111 | } 112 | 113 | }; // class mpsc_queue 114 | 115 | NAMESPACE_CIEL_END 116 | 117 | #endif // CIELLAB_INCLUDE_CIEL_CORE_MPSC_QUEUE_HPP_ 118 | -------------------------------------------------------------------------------- /include/ciel/split_buffer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CIELLAB_INCLUDE_CIEL_SPLIT_BUFFER_HPP_ 2 | #define CIELLAB_INCLUDE_CIEL_SPLIT_BUFFER_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | NAMESPACE_CIEL_BEGIN 14 | 15 | // Inspired by LLVM libc++'s implementation. 16 | 17 | template 18 | class vector; 19 | 20 | template 21 | class split_buffer { 22 | static_assert(std::is_lvalue_reference::value, 23 | "Allocator should be lvalue reference type as being used by vector."); 24 | 25 | public: 26 | using value_type = T; 27 | using allocator_type = remove_reference_t; 28 | using size_type = size_t; 29 | using difference_type = ptrdiff_t; 30 | using reference = value_type&; 31 | using const_reference = const value_type&; 32 | using pointer = typename std::allocator_traits::pointer; 33 | using const_pointer = typename std::allocator_traits::const_pointer; 34 | 35 | private: 36 | using alloc_traits = std::allocator_traits; 37 | 38 | pointer begin_cap_{nullptr}; 39 | pointer begin_{nullptr}; 40 | pointer end_{nullptr}; 41 | pointer end_cap_{nullptr}; 42 | Allocator allocator_; 43 | 44 | friend class vector; 45 | 46 | template 47 | void construct(pointer p, Args&&... args) { 48 | alloc_traits::construct(allocator_, ciel::to_address(p), std::forward(args)...); 49 | } 50 | 51 | void construct_at_end(const size_type n, const value_type& value) { 52 | CIEL_ASSERT(end_ + n <= end_cap_); 53 | 54 | for (size_type i = 0; i < n; ++i) { 55 | unchecked_emplace_back(value); 56 | } 57 | } 58 | 59 | template 60 | void construct_at_end(Iter first, Iter last) { 61 | ciel::uninitialized_copy(allocator_, first, last, end_); 62 | } 63 | 64 | template 65 | void unchecked_emplace_front(Args&&... args) { 66 | CIEL_ASSERT(begin_cap_ < begin_); 67 | 68 | construct(begin_ - 1, std::forward(args)...); 69 | --begin_; 70 | } 71 | 72 | template 73 | void unchecked_emplace_back(Args&&... args) { 74 | CIEL_ASSERT(end_ < end_cap_); 75 | 76 | construct(end_, std::forward(args)...); 77 | ++end_; 78 | } 79 | 80 | public: 81 | explicit split_buffer(Allocator alloc, const size_type cap, const size_type offset) 82 | : allocator_(alloc) { 83 | CIEL_ASSERT(cap != 0); 84 | CIEL_ASSERT(cap >= offset); 85 | 86 | const auto allocation_res = ciel::allocate_at_least(allocator_, cap); 87 | 88 | begin_cap_ = allocation_res.ptr; 89 | end_cap_ = begin_cap_ + allocation_res.count; 90 | begin_ = begin_cap_ + offset; 91 | end_ = begin_; 92 | } 93 | 94 | split_buffer(const split_buffer& other) = delete; 95 | split_buffer& operator=(const split_buffer& other) = delete; 96 | 97 | ~split_buffer() { 98 | if (begin_cap_) { 99 | clear(); 100 | alloc_traits::deallocate(allocator_, begin_cap_, capacity()); 101 | } 102 | } 103 | 104 | CIEL_NODISCARD size_type front_spare() const noexcept { 105 | CIEL_ASSERT(begin_cap_ <= begin_); 106 | 107 | return begin_ - begin_cap_; 108 | } 109 | 110 | CIEL_NODISCARD size_type back_spare() const noexcept { 111 | CIEL_ASSERT(end_ <= end_cap_); 112 | 113 | return end_cap_ - end_; 114 | } 115 | 116 | CIEL_NODISCARD size_type capacity() const noexcept { 117 | CIEL_ASSERT(begin_cap_ < end_cap_); // capacity should not be zero. 118 | 119 | return end_cap_ - begin_cap_; 120 | } 121 | 122 | void clear() noexcept { 123 | CIEL_ASSERT(begin_ <= end_); 124 | 125 | for (; begin_ != end_; ++begin_) { 126 | alloc_traits::destroy(allocator_, ciel::to_address(begin_)); 127 | } 128 | } 129 | 130 | }; // class split_buffer 131 | 132 | NAMESPACE_CIEL_END 133 | 134 | #endif // CIELLAB_INCLUDE_CIEL_SPLIT_BUFFER_HPP_ 135 | -------------------------------------------------------------------------------- /test/src/worth_move.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace ciel; 10 | 11 | namespace { 12 | 13 | struct T1 {}; 14 | 15 | struct T2 { 16 | int i; 17 | }; 18 | 19 | struct T3 { 20 | // user-defined destructor suppress implicitly-declared move constructor and assignments 21 | virtual ~T3() {} 22 | }; 23 | 24 | struct T4 { 25 | T4(const T4&) noexcept {} 26 | 27 | T4& operator=(const T4&) noexcept { 28 | return *this; 29 | } 30 | }; 31 | 32 | struct T5 { 33 | T5(T5&&) noexcept {} 34 | 35 | T5& operator=(T5&&) noexcept { 36 | return *this; 37 | } 38 | }; 39 | 40 | struct T6 { 41 | std::vector v; 42 | }; 43 | 44 | struct T7 { 45 | std::unique_ptr v; 46 | }; 47 | 48 | struct T8 { 49 | virtual void f() noexcept {} 50 | }; 51 | 52 | struct T9 { 53 | T9(const T9&) noexcept {} 54 | 55 | T9(T9&&) noexcept {} 56 | 57 | T9& operator=(const T9&) noexcept { 58 | return *this; 59 | } 60 | 61 | T9& operator=(T9&&) noexcept { 62 | return *this; 63 | } 64 | }; 65 | 66 | struct T10 { 67 | T10(T10&&) noexcept {} 68 | 69 | T10& operator=(T10&&) noexcept { 70 | return *this; 71 | } 72 | 73 | T10(const T10&) = delete; 74 | T10& operator=(const T10&) = delete; 75 | }; 76 | 77 | struct T11 { 78 | T11(const T11&) = delete; 79 | T11(T11&&) = delete; 80 | T11& operator=(const T11&) = delete; 81 | T11& operator=(T11&&) = delete; 82 | }; 83 | 84 | } // namespace 85 | 86 | TEST(worth_move, worth_move_constructing) { 87 | static_assert(not worth_move_constructing::value, ""); 88 | static_assert(not worth_move_constructing::value, ""); 89 | static_assert(not worth_move_constructing::value, ""); 90 | static_assert(not worth_move_constructing::value, ""); 91 | static_assert(not worth_move_constructing::value, ""); 92 | static_assert(not worth_move_constructing>::value, ""); 93 | static_assert(not worth_move_constructing::value, ""); 94 | static_assert(not worth_move_constructing::value, ""); 95 | static_assert(not worth_move_constructing::value, ""); 96 | static_assert(not worth_move_constructing::value, ""); 97 | 98 | static_assert(worth_move_constructing, 5>>::value, ""); 99 | static_assert(not worth_move_constructing[5]>::value, ""); 100 | 101 | static_assert(worth_move_constructing::value, ""); 102 | static_assert(worth_move_constructing::value, ""); 103 | static_assert(worth_move_constructing::value, ""); 104 | static_assert(worth_move_constructing::value, ""); 105 | static_assert(worth_move_constructing::value, ""); 106 | static_assert(worth_move_constructing::value, ""); 107 | 108 | static_assert(not worth_move_constructing::value, ""); 109 | } 110 | 111 | TEST(worth_move, worth_move_assigning) { 112 | static_assert(not worth_move_assigning::value, ""); 113 | static_assert(not worth_move_assigning::value, ""); 114 | static_assert(not worth_move_assigning::value, ""); 115 | static_assert(not worth_move_assigning::value, ""); 116 | static_assert(not worth_move_assigning::value, ""); 117 | static_assert(not worth_move_assigning>::value, ""); 118 | static_assert(not worth_move_assigning::value, ""); 119 | static_assert(not worth_move_assigning::value, ""); 120 | static_assert(not worth_move_assigning::value, ""); 121 | static_assert(not worth_move_assigning::value, ""); 122 | 123 | static_assert(worth_move_assigning, 5>>::value, ""); 124 | static_assert(not worth_move_assigning[5]>::value, ""); 125 | 126 | static_assert(worth_move_assigning::value, ""); 127 | static_assert(worth_move_assigning::value, ""); 128 | static_assert(worth_move_assigning::value, ""); 129 | static_assert(worth_move_assigning::value, ""); 130 | static_assert(worth_move_assigning::value, ""); 131 | static_assert(worth_move_assigning::value, ""); 132 | 133 | static_assert(not worth_move_assigning::value, ""); 134 | } 135 | -------------------------------------------------------------------------------- /test/src/can_be_destroyed_from_base.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace ciel; 6 | 7 | namespace { 8 | 9 | struct B1 {}; 10 | 11 | struct D1 : B1 {}; 12 | 13 | struct B2 { 14 | ~B2() {} 15 | }; 16 | 17 | struct D2 : B2 {}; 18 | 19 | struct B3 {}; 20 | 21 | struct D3 : B3 { 22 | ~D3() {} 23 | }; 24 | 25 | struct B4 { 26 | virtual ~B4() = default; 27 | }; 28 | 29 | struct D4 : B4 {}; 30 | 31 | struct B5 { 32 | virtual ~B5() = default; 33 | }; 34 | 35 | struct D5 : B5 { 36 | ~D5() override {} 37 | }; 38 | 39 | struct B6 { 40 | virtual ~B6() = default; 41 | }; 42 | 43 | struct D6 : private B6 {}; 44 | 45 | } // namespace 46 | 47 | TEST(can_be_destroyed_from_base, similar) { 48 | static_assert(is_similar::value, ""); 49 | static_assert(is_similar::value, ""); 50 | static_assert(is_similar::value, ""); 51 | static_assert(is_similar::value, ""); 52 | static_assert(is_similar::value, ""); 53 | static_assert(is_similar::value, ""); 54 | 55 | static_assert(is_similar::value, ""); 56 | static_assert(is_similar::value, ""); 57 | static_assert(is_similar::value, ""); 58 | static_assert(is_similar::value, ""); 59 | static_assert(is_similar::value, ""); 60 | static_assert(is_similar::value, ""); 61 | static_assert(is_similar::value, ""); 62 | static_assert(is_similar::value, ""); 63 | 64 | static_assert(is_similar::value, ""); 65 | static_assert(is_similar::value, ""); 66 | static_assert(is_similar::value, ""); 67 | static_assert(is_similar::value, ""); 68 | static_assert(is_similar::value, ""); 69 | static_assert(is_similar::value, ""); 70 | static_assert(is_similar::value, ""); 71 | static_assert(is_similar::value, ""); 72 | 73 | static_assert(not is_similar::value, ""); 74 | static_assert(not is_similar::value, ""); 75 | static_assert(not is_similar::value, ""); 76 | static_assert(not is_similar::value, ""); 77 | static_assert(not is_similar::value, ""); 78 | static_assert(not is_similar::value, ""); 79 | 80 | static_assert(is_similar::value, ""); 81 | static_assert(is_similar::value, ""); 82 | static_assert(is_similar::value, ""); 83 | 84 | static_assert(is_similar::value, ""); 85 | static_assert(is_similar::value, ""); 86 | static_assert(is_similar::value, ""); 87 | 88 | static_assert(not is_similar::value, ""); 89 | static_assert(not is_similar::value, ""); 90 | static_assert(not is_similar::value, ""); 91 | static_assert(not is_similar::value, ""); 92 | static_assert(not is_similar::value, ""); 93 | static_assert(not is_similar::value, ""); 94 | } 95 | 96 | TEST(can_be_destroyed_from_base, can_be_destroyed_from_base) { 97 | static_assert(can_be_destroyed_from_base::value, ""); 98 | static_assert(can_be_destroyed_from_base::value, ""); 99 | static_assert(can_be_destroyed_from_base::value, ""); 100 | static_assert(can_be_destroyed_from_base::value, ""); 101 | static_assert(can_be_destroyed_from_base::value, ""); 102 | static_assert(can_be_destroyed_from_base::value, ""); 103 | 104 | static_assert(not can_be_destroyed_from_base::value, ""); 105 | static_assert(not can_be_destroyed_from_base::value, ""); 106 | static_assert(not can_be_destroyed_from_base::value, ""); 107 | static_assert(can_be_destroyed_from_base::value, ""); 108 | static_assert(can_be_destroyed_from_base::value, ""); 109 | static_assert(can_be_destroyed_from_base::value, ""); 110 | } 111 | -------------------------------------------------------------------------------- /test/src/inplace_vector/constructor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace ciel; 16 | 17 | namespace { 18 | 19 | template 20 | void test_default_constructor_impl(::testing::Test*) { 21 | { 22 | C c; 23 | ASSERT_TRUE(c.empty()); 24 | ASSERT_EQ(c.capacity(), 8); 25 | } 26 | { 27 | C c = {}; 28 | ASSERT_TRUE(c.empty()); 29 | ASSERT_EQ(c.capacity(), 8); 30 | } 31 | } 32 | 33 | template 34 | void test_constructor_size_value_impl(::testing::Test*) { 35 | using T = typename C::value_type; 36 | 37 | C v(3, T{1}); 38 | ASSERT_EQ(v, std::initializer_list({1, 1, 1})); 39 | } 40 | 41 | template 42 | void test_constructor_size_impl(::testing::Test*) { 43 | using T = typename C::value_type; 44 | 45 | C v(3); 46 | ASSERT_EQ(v, std::initializer_list({0, 0, 0})); 47 | } 48 | 49 | template 50 | void test_constructor_iterator_range_impl(::testing::Test*) { 51 | using T = typename C::value_type; 52 | 53 | { 54 | std::array arr{0, 1, 2, 3, 4}; 55 | C v(Iter{arr.data()}, Iter{arr.data() + arr.size()}); 56 | ASSERT_EQ(v, std::initializer_list({0, 1, 2, 3, 4})); 57 | } 58 | { 59 | // empty range 60 | C v(Iter{nullptr}, Iter{nullptr}); 61 | ASSERT_TRUE(v.empty()); 62 | } 63 | } 64 | 65 | template 66 | void test_copy_constructor_impl(::testing::Test*) { 67 | using T = typename C::value_type; 68 | 69 | C v1({0, 1, 2, 3, 4}); 70 | C v2(v1); 71 | ASSERT_EQ(v2, std::initializer_list({0, 1, 2, 3, 4})); 72 | } 73 | 74 | template 75 | void test_move_constructor_impl(::testing::Test*) { 76 | using T = typename C::value_type; 77 | 78 | C v1({0, 1, 2, 3, 4}); 79 | C v2(std::move(v1)); 80 | ASSERT_EQ(v2, std::initializer_list({0, 1, 2, 3, 4})); 81 | 82 | if (std::is_trivially_copyable::value) { 83 | ASSERT_EQ(v1, v2); 84 | } else if (is_trivially_relocatable::value) { 85 | ASSERT_TRUE(v1.empty()); 86 | } else { 87 | ASSERT_EQ(v1, std::initializer_list({-1, -1, -1, -1, -1})); 88 | } 89 | } 90 | 91 | template 92 | void test_constructor_initializer_list_impl(::testing::Test*) { 93 | using T = typename C::value_type; 94 | 95 | C v({0, 1, 2, 3, 4}); 96 | ASSERT_EQ(v, std::initializer_list({0, 1, 2, 3, 4})); 97 | } 98 | 99 | } // namespace 100 | 101 | TEST(inplace_vector, default_constructor) { 102 | test_default_constructor_impl>(this); 103 | } 104 | 105 | TEST(inplace_vector, constructor_size_value) { 106 | test_constructor_size_value_impl>(this); 107 | { 108 | // distinguish from iterator range constructor 109 | const inplace_vector v(size_t{5}, size_t{5}); 110 | ASSERT_EQ(v, std::initializer_list({5, 5, 5, 5, 5})); 111 | } 112 | } 113 | 114 | TEST(inplace_vector, constructor_size) { 115 | test_constructor_size_impl>(this); 116 | } 117 | 118 | TEST(inplace_vector, constructor_iterator_range) { 119 | test_constructor_iterator_range_impl, InputIterator>(this); 120 | test_constructor_iterator_range_impl, ForwardIterator>(this); 121 | test_constructor_iterator_range_impl, RandomAccessIterator>(this); 122 | test_constructor_iterator_range_impl, Int*>(this); 123 | } 124 | 125 | TEST(inplace_vector, copy_constructor) { 126 | test_copy_constructor_impl>(this); 127 | } 128 | 129 | TEST(inplace_vector, move_constructor) { 130 | test_move_constructor_impl>(this); 131 | test_move_constructor_impl>(this); 132 | test_move_constructor_impl>(this); 133 | test_move_constructor_impl>(this); 134 | } 135 | 136 | TEST(inplace_vector, constructor_initializer_list) { 137 | test_constructor_initializer_list_impl>(this); 138 | } 139 | --------------------------------------------------------------------------------