diff --git a/include/boost/capy/embed.hpp b/include/boost/capy/embed.hpp new file mode 100644 index 0000000..375e083 --- /dev/null +++ b/include/boost/capy/embed.hpp @@ -0,0 +1,107 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#ifndef BOOST_CAPY_EMBED_HPP +#define BOOST_CAPY_EMBED_HPP + +#include +#include + +#if __cpp_lib_string_view >= 201606L +# include +#endif + +namespace boost { +namespace capy { + +/** Embed a string literal as a string_view + + The embed class template is used to embed a string literal + in source code as a string_view. The first character of + the string literal will be removed, typically this is a + newline, allowing the string to be formatted nicely in code. + + @par Example + @code + embed text(R"( + Hello "world" + This has quotes and ) + )"); + core::string_view sv = text.get(); + @endcode + The resulting string_view `sv` will contain: + ``` + Hello "world" + This has quotes and ) + ``` +*/ +struct embed +{ + /** Constructor + The string literal `s` should be a raw string literal. + The first character (typically a newline) will be + removed from the resulting string_view. + @param s The string literal + */ + template + constexpr + embed( + const char (&s)[N]) noexcept + : s_(s + 1, N - 2) + { + } + + /** Conversion to string_view + */ + operator core::string_view() const noexcept + { + return s_; + } + +#if __cpp_lib_string_view >= 201606L + /** Conversion to std::string_view + */ + operator std::string_view() const noexcept + { + return std::string_view(s_.data(), s_.size()); + } +#endif + + /** Return the string_view + */ + core::string_view + get() const noexcept + { + return s_; + } + + /** Dereference operator + */ + core::string_view + operator*() const noexcept + { + return s_; + } + + /** Member access operator + */ + core::string_view const* + operator->() const noexcept + { + return &s_; + } + +private: + core::string_view s_; +}; + +} // capy +} // boost + +#endif diff --git a/include/boost/capy/neunique_ptr.hpp b/include/boost/capy/neunique_ptr.hpp index bdc9b71..82409f8 100644 --- a/include/boost/capy/neunique_ptr.hpp +++ b/include/boost/capy/neunique_ptr.hpp @@ -11,6 +11,7 @@ #define BOOST_CAPY_NEUNIQUE_PTR_HPP #include +#include #include #include #include @@ -24,6 +25,33 @@ namespace detail { //---------------------------------------------------------- +template +auto try_delete(U* p, int) noexcept + -> decltype(sizeof(U), void()) +{ + delete p; +} + +template +void try_delete(U*, long) noexcept +{ + // Incomplete type - should never reach here + std::terminate(); +} + +template +auto try_delete_array(U* p, int) noexcept + -> decltype(sizeof(U), void()) +{ + delete[] p; +} + +template +void try_delete_array(U*, long) noexcept +{ + std::terminate(); +} + /** Storage wrapper applying empty base optimization. When `T` is empty and non-final, inherits from it to @@ -253,6 +281,12 @@ struct control_block_array final std::size_t size; // Flexible array member follows + explicit control_block_array(A const& a) + : alloc_storage(alloc_type(a)) + , size(0) + { + } + alloc_type& get_alloc() noexcept { return alloc_storage::get(); @@ -489,10 +523,11 @@ class neunique_ptr : ptr_(p) , cb_(other.cb_) { + // aliasing requires control block; use allocate_neunique + BOOST_ASSERT((other.cb_ != nullptr || other.ptr_ == nullptr)); other.ptr_ = nullptr; other.cb_ = nullptr; } - /** Move constructor. Takes ownership from `other`. After construction, @@ -550,7 +585,7 @@ class neunique_ptr if(cb_) cb_->destroy_and_deallocate(); else if(ptr_) - delete ptr_; + detail::try_delete(ptr_, 0); } //------------------------------------------------------ @@ -577,7 +612,7 @@ class neunique_ptr if(cb_) cb_->destroy_and_deallocate(); else if(ptr_) - delete ptr_; + detail::try_delete(ptr_, 0); ptr_ = other.ptr_; cb_ = other.cb_; other.ptr_ = nullptr; @@ -648,7 +683,7 @@ class neunique_ptr if(cb_) cb_->destroy_and_deallocate(); else if(ptr_) - delete ptr_; + detail::try_delete(ptr_, 0); ptr_ = p; cb_ = nullptr; } @@ -903,6 +938,8 @@ class neunique_ptr : ptr_(p) , cb_(other.cb_) { + // aliasing requires control block; use allocate_neunique + BOOST_ASSERT((other.cb_ != nullptr || other.ptr_ == nullptr)); other.ptr_ = nullptr; other.cb_ = nullptr; } @@ -944,7 +981,7 @@ class neunique_ptr if(cb_) cb_->destroy_and_deallocate(); else if(ptr_) - delete[] ptr_; + detail::try_delete_array(ptr_, 0); } //------------------------------------------------------ @@ -971,7 +1008,7 @@ class neunique_ptr if(cb_) cb_->destroy_and_deallocate(); else if(ptr_) - delete[] ptr_; + detail::try_delete_array(ptr_, 0); ptr_ = other.ptr_; cb_ = other.cb_; other.ptr_ = nullptr; @@ -1000,6 +1037,22 @@ class neunique_ptr // //------------------------------------------------------ + /** Replace the owned array. + + Releases the currently owned array. + + @post `get() == nullptr` + */ + void reset() noexcept + { + if(cb_) + cb_->destroy_and_deallocate(); + else if(ptr_) + detail::try_delete_array(ptr_, 0); + ptr_ = nullptr; + cb_ = nullptr; + } + /** Replace the owned array. Releases the currently owned array and takes @@ -1013,7 +1066,7 @@ class neunique_ptr template::value || std::is_same::value>::type> - void reset(U p = nullptr) noexcept + void reset(U p) noexcept { if(cb_) cb_->destroy_and_deallocate(); @@ -1232,9 +1285,7 @@ allocate_neunique(A const& a, std::size_t n) auto guard = detail::make_scope_guard( [&]{ alloc_traits::deallocate(alloc, cb, units); }); - ::new(static_cast(cb)) cb_type(); - cb->get_alloc() = alloc; - cb->size = 0; + ::new(static_cast(cb)) cb_type(a); U* arr = cb->get(); for(std::size_t i = 0; i < n; ++i) diff --git a/test/unit/embed.cpp b/test/unit/embed.cpp new file mode 100644 index 0000000..8cdbc71 --- /dev/null +++ b/test/unit/embed.cpp @@ -0,0 +1,63 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +// Test that header file is self-contained. +#include + +#include "test_suite.hpp" + +#if __cpp_lib_string_view >= 201606L +# include +#endif + +namespace boost { +namespace capy { + +namespace { + +embed text(R"( +Hello "world" +This has quotes and ) +)"); + +} // (anon) + +struct embed_test +{ + core::string_view good = + "Hello \"world\"\nThis has quotes and )\n"; + + void check(core::string_view s) + { + BOOST_TEST_EQ(s, good); + } + +#if __cpp_lib_string_view >= 201606L + void check_std(std::string_view s) + { + BOOST_TEST_EQ(s, good); + } +#endif + + void run() + { + check(text); +#if __cpp_lib_string_view >= 201606L + check_std(text); +#endif + BOOST_TEST_EQ(text.get(), good); + BOOST_TEST_EQ(*text, good); + BOOST_TEST_EQ(text->data(), good); + } +}; + +TEST_SUITE(embed_test, "boost.capy.embed"); + +} // capy +} // boost diff --git a/test/unit/neunique_ptr.cpp b/test/unit/neunique_ptr.cpp index 5655f20..32908a3 100644 --- a/test/unit/neunique_ptr.cpp +++ b/test/unit/neunique_ptr.cpp @@ -4,7 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -// Official repository: https://github.com/cppalliance/beast2 +// Official repository: https://github.com/cppalliance/capy // // Test that header file is self-contained. @@ -12,14 +12,660 @@ #include "test_suite.hpp" +#include +#include +#include +#include +#include + namespace boost { namespace capy { +//---------------------------------------------------------- +// Test types +//---------------------------------------------------------- + +static int destroyed = 0; + +struct Base +{ + virtual ~Base() = default; + virtual int value() const = 0; +}; + +struct Derived : Base +{ + int x; + explicit Derived(int v) : x(v) {} + int value() const override { return x; } +}; + +struct Tracked +{ + int x; + explicit Tracked(int v) : x(v) {} + ~Tracked() { ++destroyed; } +}; + +struct ThrowOnCopy +{ + ThrowOnCopy() = default; + ThrowOnCopy(ThrowOnCopy const&) { throw std::runtime_error("copy"); } + ThrowOnCopy(ThrowOnCopy&&) = default; +}; + +struct CustomDeleter +{ + int* call_count; + + explicit CustomDeleter(int* p) : call_count(p) {} + + void operator()(int* p) const + { + if(call_count) + ++(*call_count); + delete p; + } +}; + +struct CustomArrayDeleter +{ + int* call_count; + + explicit CustomArrayDeleter(int* p) : call_count(p) {} + + void operator()(int* p) const + { + if(call_count) + ++(*call_count); + delete[] p; + } +}; + +template +struct TrackingAllocator +{ + using value_type = T; + + int* alloc_count; + int* dealloc_count; + + TrackingAllocator(int* ac, int* dc) + : alloc_count(ac) + , dealloc_count(dc) + { + } + + template + TrackingAllocator(TrackingAllocator const& other) + : alloc_count(other.alloc_count) + , dealloc_count(other.dealloc_count) + { + } + + T* allocate(std::size_t n) + { + if(alloc_count) + ++(*alloc_count); + return static_cast(::operator new(n * sizeof(T))); + } + + void deallocate(T* p, std::size_t) + { + if(dealloc_count) + ++(*dealloc_count); + ::operator delete(p); + } + + template + bool operator==(TrackingAllocator const& other) const + { + return alloc_count == other.alloc_count && + dealloc_count == other.dealloc_count; + } + + template + bool operator!=(TrackingAllocator const& other) const + { + return !(*this == other); + } +}; + +struct EmptyDeleter +{ + void operator()(int* p) const { delete p; } +}; + +//---------------------------------------------------------- + struct neunique_ptr_test { + void + test_default_construction() + { + neunique_ptr p1; + neunique_ptr p2(nullptr); + + BOOST_TEST(!p1); + BOOST_TEST(!p2); + BOOST_TEST(p1.get() == nullptr); + BOOST_TEST(p2.get() == nullptr); + } + + void + test_raw_pointer_construction() + { + neunique_ptr p1(new int(42)); + BOOST_TEST(p1); + BOOST_TEST(*p1 == 42); + BOOST_TEST(p1.get() != nullptr); + + neunique_ptr p2(nullptr); + BOOST_TEST(!p2); + } + + void + test_custom_deleter() + { + int call_count = 0; + { + neunique_ptr p(new int(10), CustomDeleter(&call_count)); + BOOST_TEST(p); + BOOST_TEST(*p == 10); + } + BOOST_TEST(call_count == 1); + } + + void + test_custom_deleter_and_allocator() + { + int delete_count = 0; + int alloc_count = 0; + int dealloc_count = 0; + + { + TrackingAllocator alloc(&alloc_count, &dealloc_count); + neunique_ptr p( + new int(20), + CustomDeleter(&delete_count), + alloc); + BOOST_TEST(p); + BOOST_TEST(*p == 20); + BOOST_TEST(alloc_count > 0); + } + BOOST_TEST(delete_count == 1); + BOOST_TEST(dealloc_count > 0); + } + + void + test_make_neunique() + { + auto p1 = make_neunique(42); + BOOST_TEST(p1); + BOOST_TEST(*p1 == 42); + + auto p2 = make_neunique(99); + BOOST_TEST(p2); + BOOST_TEST(p2->value() == 99); + } + + void + test_allocate_neunique() + { + int alloc_count = 0; + int dealloc_count = 0; + TrackingAllocator alloc(&alloc_count, &dealloc_count); + + { + auto p = allocate_neunique(alloc, 55); + BOOST_TEST(p); + BOOST_TEST(*p == 55); + BOOST_TEST(alloc_count > 0); + } + BOOST_TEST(dealloc_count > 0); + } + + void + test_move_construction() + { + neunique_ptr p1(new int(42)); + int* raw = p1.get(); + + neunique_ptr p2(std::move(p1)); + BOOST_TEST(!p1); + BOOST_TEST(p2); + BOOST_TEST(p2.get() == raw); + BOOST_TEST(*p2 == 42); + } + + void + test_converting_move_construction() + { + neunique_ptr p1(new Derived(42)); + neunique_ptr p2(std::move(p1)); + + BOOST_TEST(!p1); + BOOST_TEST(p2); + BOOST_TEST(p2->value() == 42); + } + + void + test_aliasing_constructor() + { + struct Pair + { + int first; + int second; + Pair(int a, int b) : first(a), second(b) {} + }; + auto p1 = allocate_neunique(std::allocator{}, 10, 20); + int* raw_second = &p1->second; + neunique_ptr p2(std::move(p1), raw_second); + BOOST_TEST(!p1); + BOOST_TEST(p2); + BOOST_TEST(p2.get() == raw_second); + BOOST_TEST(*p2 == 20); + } + + void + test_move_assignment() + { + neunique_ptr p1(new int(42)); + neunique_ptr p2(new int(99)); + int* raw = p1.get(); + + p2 = std::move(p1); + BOOST_TEST(!p1); + BOOST_TEST(p2); + BOOST_TEST(p2.get() == raw); + BOOST_TEST(*p2 == 42); + } + + void + test_converting_move_assignment() + { + neunique_ptr p1(new Derived(42)); + neunique_ptr p2; + + p2 = std::move(p1); + BOOST_TEST(!p1); + BOOST_TEST(p2); + BOOST_TEST(p2->value() == 42); + } + + void + test_nullptr_assignment() + { + neunique_ptr p(new int(42)); + BOOST_TEST(p); + + p = nullptr; + BOOST_TEST(!p); + BOOST_TEST(p.get() == nullptr); + } + + void + test_reset() + { + destroyed = 0; + { + neunique_ptr p(new Tracked(1)); + BOOST_TEST(destroyed == 0); + + p.reset(); + BOOST_TEST(destroyed == 1); + BOOST_TEST(!p); + + p.reset(new Tracked(2)); + BOOST_TEST(p); + BOOST_TEST(p->x == 2); + } + BOOST_TEST(destroyed == 2); + } + + void + test_reset_with_deleter() + { + int delete_count = 0; + neunique_ptr p(new int(10)); + + p.reset(new int(20), CustomDeleter(&delete_count)); + BOOST_TEST(p); + BOOST_TEST(*p == 20); + BOOST_TEST(delete_count == 0); + + p.reset(); + BOOST_TEST(delete_count == 1); + } + + void + test_reset_with_deleter_and_allocator() + { + int delete_count = 0; + int alloc_count = 0; + int dealloc_count = 0; + TrackingAllocator alloc(&alloc_count, &dealloc_count); + + neunique_ptr p(new int(10)); + p.reset(new int(30), CustomDeleter(&delete_count), alloc); + + BOOST_TEST(p); + BOOST_TEST(*p == 30); + BOOST_TEST(alloc_count > 0); + + p.reset(); + BOOST_TEST(delete_count == 1); + BOOST_TEST(dealloc_count > 0); + } + + void + test_swap() + { + neunique_ptr p1(new int(42)); + neunique_ptr p2(new int(99)); + int* raw1 = p1.get(); + int* raw2 = p2.get(); + + p1.swap(p2); + BOOST_TEST(p1.get() == raw2); + BOOST_TEST(p2.get() == raw1); + BOOST_TEST(*p1 == 99); + BOOST_TEST(*p2 == 42); + + swap(p1, p2); + BOOST_TEST(p1.get() == raw1); + BOOST_TEST(p2.get() == raw2); + } + + void + test_observers() + { + neunique_ptr p1(new int(42)); + BOOST_TEST(p1.get() != nullptr); + BOOST_TEST(static_cast(p1)); + BOOST_TEST(*p1 == 42); + BOOST_TEST(p1.operator->() == p1.get()); + + neunique_ptr p2; + BOOST_TEST(p2.get() == nullptr); + BOOST_TEST(!static_cast(p2)); + } + + void + test_destruction() + { + destroyed = 0; + { + neunique_ptr p(new Tracked(1)); + } + BOOST_TEST(destroyed == 1); + + destroyed = 0; + { + int delete_count = 0; + neunique_ptr p( + new Tracked(2), + [&](Tracked* t) + { + ++delete_count; + delete t; + }); + } + BOOST_TEST(destroyed == 1); + } + + void + test_self_move_assignment() + { + neunique_ptr p(new int(42)); +#if defined(__clang__) || (defined(__GNUC__) && __GNUC__ >= 13) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wself-move" +#endif + p = std::move(p); +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + BOOST_TEST(p); + BOOST_TEST(*p == 42); + } + + void + test_comparison_operators() + { + neunique_ptr p1(new int(42)); + neunique_ptr p2(new int(99)); + neunique_ptr p3; + + BOOST_TEST(p1 == p1); + BOOST_TEST(p1 != p2); + BOOST_TEST(p3 == nullptr); + BOOST_TEST(nullptr == p3); + BOOST_TEST(p1 != nullptr); + BOOST_TEST(nullptr != p1); + + BOOST_TEST((p1 < p2) == (p1.get() < p2.get())); + BOOST_TEST((p1 <= p2) == (p1.get() <= p2.get())); + BOOST_TEST((p1 > p2) == (p1.get() > p2.get())); + BOOST_TEST((p1 >= p2) == (p1.get() >= p2.get())); + } + + void + test_hash() + { + neunique_ptr p1(new int(42)); + neunique_ptr p2(new int(99)); + + std::hash> hasher; + std::size_t h1 = hasher(p1); + std::size_t h2 = hasher(p2); + + BOOST_TEST(h1 == std::hash()(p1.get())); + BOOST_TEST(h2 == std::hash()(p2.get())); + + std::unordered_map, int> map; + map[std::move(p1)] = 42; + BOOST_TEST(map.size() == 1); + } + + void + test_array_default_construction() + { + neunique_ptr p1; + neunique_ptr p2(nullptr); + + BOOST_TEST(!p1); + BOOST_TEST(!p2); + BOOST_TEST(p1.get() == nullptr); + BOOST_TEST(p2.get() == nullptr); + } + + void + test_array_raw_pointer_construction() + { + neunique_ptr p(new int[3]{1, 2, 3}); + BOOST_TEST(p); + BOOST_TEST(p[0] == 1); + BOOST_TEST(p[1] == 2); + BOOST_TEST(p[2] == 3); + } + + void + test_array_custom_deleter() + { + int call_count = 0; + { + neunique_ptr p( + new int[3]{1, 2, 3}, + CustomArrayDeleter(&call_count)); + BOOST_TEST(p); + BOOST_TEST(p[0] == 1); + } + BOOST_TEST(call_count == 1); + } + + void + test_array_make_neunique() + { + auto p = make_neunique(5); + BOOST_TEST(p); + for(int i = 0; i < 5; ++i) + BOOST_TEST(p[i] == 0); + } + + void + test_array_allocate_neunique() + { + int alloc_count = 0; + int dealloc_count = 0; + TrackingAllocator alloc(&alloc_count, &dealloc_count); + + { + auto p = allocate_neunique(alloc, 3); + BOOST_TEST(p); + BOOST_TEST(alloc_count > 0); + p[0] = 10; + p[1] = 20; + p[2] = 30; + BOOST_TEST(p[0] == 10); + BOOST_TEST(p[1] == 20); + BOOST_TEST(p[2] == 30); + } + BOOST_TEST(dealloc_count > 0); + } + + void + test_array_move_construction() + { + neunique_ptr p1(new int[3]{1, 2, 3}); + int* raw = p1.get(); + + neunique_ptr p2(std::move(p1)); + BOOST_TEST(!p1); + BOOST_TEST(p2); + BOOST_TEST(p2.get() == raw); + BOOST_TEST(p2[0] == 1); + } + + void + test_array_move_assignment() + { + neunique_ptr p1(new int[3]{1, 2, 3}); + neunique_ptr p2(new int[2]{4, 5}); + int* raw = p1.get(); + + p2 = std::move(p1); + BOOST_TEST(!p1); + BOOST_TEST(p2); + BOOST_TEST(p2.get() == raw); + BOOST_TEST(p2[0] == 1); + } + + void + test_array_reset() + { + neunique_ptr p(new int[3]{1, 2, 3}); + BOOST_TEST(p); + + p.reset(); + BOOST_TEST(!p); + + p.reset(new int[2]{4, 5}); + BOOST_TEST(p); + BOOST_TEST(p[0] == 4); + BOOST_TEST(p[1] == 5); + } + + void + test_array_swap() + { + neunique_ptr p1(new int[2]{1, 2}); + neunique_ptr p2(new int[2]{3, 4}); + int* raw1 = p1.get(); + int* raw2 = p2.get(); + + p1.swap(p2); + BOOST_TEST(p1.get() == raw2); + BOOST_TEST(p2.get() == raw1); + BOOST_TEST(p1[0] == 3); + BOOST_TEST(p2[0] == 1); + } + + void + test_array_observers() + { + neunique_ptr p1(new int[3]{1, 2, 3}); + BOOST_TEST(p1.get() != nullptr); + BOOST_TEST(static_cast(p1)); + BOOST_TEST(p1[0] == 1); + BOOST_TEST(p1[1] == 2); + BOOST_TEST(p1[2] == 3); + + neunique_ptr p2; + BOOST_TEST(p2.get() == nullptr); + BOOST_TEST(!static_cast(p2)); + } + + void + test_ebo() + { + // Test that empty base optimization is working + // for empty deleters in the control block + neunique_ptr p(new int(42), EmptyDeleter()); + BOOST_TEST(p); + BOOST_TEST(*p == 42); + } + + void + test_incomplete_type() + { + // Test that neunique_ptr can be declared with + // incomplete types (implementation detail test) + struct Incomplete; + neunique_ptr p1; + neunique_ptr p2(nullptr); + BOOST_TEST(!p1); + BOOST_TEST(!p2); + } + void run() { + test_default_construction(); + test_raw_pointer_construction(); + test_custom_deleter(); + test_custom_deleter_and_allocator(); + test_make_neunique(); + test_allocate_neunique(); + test_move_construction(); + test_converting_move_construction(); + test_aliasing_constructor(); + test_move_assignment(); + test_converting_move_assignment(); + test_nullptr_assignment(); + test_reset(); + test_reset_with_deleter(); + test_reset_with_deleter_and_allocator(); + test_swap(); + test_observers(); + test_destruction(); + test_self_move_assignment(); + test_comparison_operators(); + test_hash(); + + test_array_default_construction(); + test_array_raw_pointer_construction(); + test_array_custom_deleter(); + test_array_make_neunique(); + test_array_allocate_neunique(); + test_array_move_construction(); + test_array_move_assignment(); + test_array_reset(); + test_array_swap(); + test_array_observers(); + + test_ebo(); + test_incomplete_type(); } };