diff --git a/.codecov.yml b/.codecov.yml index 2f946c3..4599672 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -14,4 +14,6 @@ comment: ignore: - "**/*-test.cpp" # ignore test harness code - - "**/detail" \ No newline at end of file + - "**/config/*" + - "**/detail/*" + - "**/foundation/utility/*" \ No newline at end of file diff --git a/cmake/project_settings.cmake b/cmake/project_settings.cmake index d28d2cb..6913887 100644 --- a/cmake/project_settings.cmake +++ b/cmake/project_settings.cmake @@ -594,8 +594,6 @@ target_compile_options(flux::project_settings INTERFACE -Wshadow # Warn whenever a class has virtual functions and an accessible non-virtual destructor. -Wnon-virtual-dtor - # Warn if old-style (C-style) cast to a non-void type is used within a C++ program. - -Wold-style-cast # Warn whenever a pointer is cast such that the required alignment of the target is increased. For example, warn if # a char* is cast to an int* on machines where integers can only be accessed at two- or four-byte boundaries. -Wcast-align diff --git a/flux-config/flux/config/features.hpp b/flux-config/flux/config/features.hpp index 5d96d75..15be792 100644 --- a/flux-config/flux/config/features.hpp +++ b/flux-config/flux/config/features.hpp @@ -1,5 +1,21 @@ #pragma once +#if __has_attribute(__no_sanitize__) +# define FLUX_NO_SANITIZE(...) __attribute__((__no_sanitize__(__VA_ARGS__))) +#else +# define FLUX_NO_SANITIZE(...) +#endif + +#if __has_cpp_attribute(__gnu__::__always_inline__) +# define FLUX_ALWAYS_INLINE [[__gnu__::__always_inline__]] +#elif __has_cpp_attribute(clang::always_inline) +# define FLUX_ALWAYS_INLINE [[clang::always_inline]] +#elif __has_cpp_attribute(msvc::forceinline) +# define FLUX_ALWAYS_INLINE [[msvc::forceinline]] +#else +# define FLUX_ALWAYS_INLINE /* nothing */ +#endif + #if __has_cpp_attribute(msvc::no_unique_address) // MSVC implements [[no_unique_address]] as a silent no-op currently. If/when MSVC breaks its C++ // ABI, it will be changed to work as intended. However, MSVC implements [[msvc::no_unique_address]] @@ -9,7 +25,7 @@ // [[msvc::no_unique_address]] though. If/when it does implement [[msvc::no_unique_address]], this // should be preferred though. # define FLUX_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] -#elif __has_cpp_attribute(no_unique_address) +#elif __has_cpp_attribute(no_unique_address) >= 201803 # define FLUX_NO_UNIQUE_ADDRESS [[no_unique_address]] #else // Note that this can be replaced by #error as soon as clang-cl implements msvc::no_unique_address, @@ -19,8 +35,13 @@ # define FLUX_NO_UNIQUE_ADDRESS /* nothing */ #endif -#if __has_attribute(__no_sanitize__) -# define FLUX_NO_SANITIZE(...) __attribute__((__no_sanitize__(__VA_ARGS__))) -#else -# define FLUX_NO_SANITIZE(...) -#endif \ No newline at end of file +namespace flux::config { +namespace detail { +struct empty_class_type {}; +struct test_no_unique_address { + FLUX_NO_UNIQUE_ADDRESS int actual_data_member; + FLUX_NO_UNIQUE_ADDRESS empty_class_type no_space; +}; +} // namespace detail +inline constexpr bool has_no_unique_address = sizeof(detail::test_no_unique_address) == sizeof(int); +} // namespace flux::config \ No newline at end of file diff --git a/flux-foundation/CMakeLists.txt b/flux-foundation/CMakeLists.txt index 456a631..a4d20c4 100644 --- a/flux-foundation/CMakeLists.txt +++ b/flux-foundation/CMakeLists.txt @@ -1,5 +1,11 @@ flux_static_library(foundation COMMON + TEST + "flux/foundation/memory/detail/constexpr_memcpy-test.cpp" + "flux/foundation/memory/construct-test.cpp" + "flux/foundation/memory/relocate-test.cpp" + "flux/foundation/memory/uninitialized_algorithms-test.cpp" + "flux/foundation/memory/uninitialized_storage-test.cpp" SOURCE "flux/foundation/dummy.cpp" LINK diff --git a/flux-foundation/flux/foundation.hpp b/flux-foundation/flux/foundation.hpp index a10dd69..d72e559 100644 --- a/flux-foundation/flux/foundation.hpp +++ b/flux-foundation/flux/foundation.hpp @@ -1,4 +1,10 @@ #pragma once +#include #include +#include -#include \ No newline at end of file +// clang-format off +#include +#include +#include +// clang-format on \ No newline at end of file diff --git a/flux-foundation/flux/foundation/memory.hpp b/flux-foundation/flux/foundation/memory.hpp new file mode 100644 index 0000000..4d9f401 --- /dev/null +++ b/flux-foundation/flux/foundation/memory.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include +#include \ No newline at end of file diff --git a/flux-foundation/flux/foundation/memory/construct-test.cpp b/flux-foundation/flux/foundation/memory/construct-test.cpp new file mode 100644 index 0000000..a7ac8b1 --- /dev/null +++ b/flux-foundation/flux/foundation/memory/construct-test.cpp @@ -0,0 +1,50 @@ +#include + +#include + +using namespace flux; + +struct A { + int i; +}; + +struct B { + A a; + float f; + char c; +}; + +TEST_CASE("fou::construct_in_place", "[flux-memory/construct.hpp]") { + SECTION("construct in place lvalue") { + alignas(A) std::byte buffer[sizeof(A)]; + A* object = (A*)buffer; + + int expected_value = 2024; + fou::construct_in_place(object, expected_value); + CHECK(object->i == expected_value); + + fou::destroy_in_place(object); + } + + SECTION("construct in place rvalue") { + alignas(A) std::byte buffer[sizeof(A)]; + A* object = (A*)buffer; + + fou::construct_in_place(object, 2025); + CHECK(object->i == 2025); + + fou::destroy_in_place(object); + } + + SECTION("construct in place variadic") { + alignas(B) std::byte buffer[sizeof(B)]; + B* object = (B*)buffer; + + fou::construct_in_place(object, A{10}, 3.14f, 'w'); + CHECK(object->a.i == 10); + CHECK(object->f == 3.14f); + CHECK(object->c == 'w'); + + fou::destroy_in_place(object); + } +} \ No newline at end of file diff --git a/flux-foundation/flux/foundation/memory/construct.hpp b/flux-foundation/flux/foundation/memory/construct.hpp new file mode 100644 index 0000000..a5fd690 --- /dev/null +++ b/flux-foundation/flux/foundation/memory/construct.hpp @@ -0,0 +1,36 @@ +#pragma once +#include +#include + +namespace flux::fou { + +template +constexpr void construct_in_place(Iterator iterator, T const& value) noexcept { + detail::construct_at(detail::to_address(iterator), value); +} +template +constexpr void construct_in_place(Iterator iterator, T&& value) noexcept { + detail::construct_at(detail::to_address(iterator), ::std::move(value)); +} +template +constexpr void construct_in_place(Iterator iterator, Args&&... args) noexcept { + detail::construct_at(detail::to_address(iterator), ::std::forward(args)...); +} + +template +constexpr Iterator destroy_range(Iterator first, Iterator last) noexcept { + for (; first != last; ++first) { + detail::destroy_at(detail::to_address(first)); + } + return first; +} + +template +constexpr void destroy_in_place(Iterator iterator) noexcept { + using value_type = meta::iter_value_t; + if constexpr (meta::not_trivially_destructible) { + detail::destroy_at(detail::to_address(iterator)); + } +} + +} // namespace flux::fou \ No newline at end of file diff --git a/flux-foundation/flux/foundation/memory/detail/constexpr_memcpy-test.cpp b/flux-foundation/flux/foundation/memory/detail/constexpr_memcpy-test.cpp new file mode 100644 index 0000000..d78523f --- /dev/null +++ b/flux-foundation/flux/foundation/memory/detail/constexpr_memcpy-test.cpp @@ -0,0 +1,52 @@ +#include + +#include + +constexpr unsigned char Banand[] = "Banand"; +constexpr unsigned char Banane[] = "Banane"; +constexpr unsigned char Bananf[] = "Bananf"; + +constexpr bool true1[] = {true, true, true}; +constexpr bool true2[] = {true, true, true}; +constexpr bool false1[] = {false, false, false}; + +using flux::fou::detail::constexpr_memcmp; +using flux::fou::detail::constexpr_memcmp_equal; +using flux::fou::detail::constexpr_memcpy; + +constexpr bool char_copy() noexcept { + unsigned char dest[4]; + + constexpr_memcpy(dest, Banand + 1, sizeof dest); + unsigned char expected[] = "anan"; + return constexpr_memcmp_equal(expected, dest, 4); +} + +TEST_CASE("fou::detail::constexpr_memcpy", "[flux-memory/constexpr_memcpy.hpp]") { + SECTION("compile time") { + STATIC_REQUIRE(char_copy()); + } + + SECTION("runtime") { + unsigned char source[] = "once upon a midnight dreary..."; + unsigned char dest[4]; + + constexpr_memcpy(dest, source, sizeof dest); + unsigned char expected[] = "once"; + CHECK(constexpr_memcmp_equal(expected, dest, 4)); + } +} + +TEST_CASE("fou::detail::constexpr_memcmp", "[flux-memory/constexpr_memcpy.hpp]") { + STATIC_REQUIRE(constexpr_memcmp(Banane, Banand, 6) == 1); + STATIC_REQUIRE(constexpr_memcmp(Banane, Banane, 6) == 0); + STATIC_REQUIRE(constexpr_memcmp(Banane, Bananf, 6) == -1); +} + +TEST_CASE("fou::detail::constexpr_memcmp_equal", "[flux-memory/constexpr_memcpy.hpp]") { + STATIC_REQUIRE_FALSE(constexpr_memcmp_equal(Banane, Banand, 6)); + STATIC_REQUIRE(constexpr_memcmp_equal(Banane, Banane, 6)); + + STATIC_REQUIRE_FALSE(constexpr_memcmp_equal(true1, false1, 3)); + STATIC_REQUIRE(constexpr_memcmp_equal(true1, true2, 3)); +} \ No newline at end of file diff --git a/flux-foundation/flux/foundation/memory/detail/constexpr_memcpy.hpp b/flux-foundation/flux/foundation/memory/detail/constexpr_memcpy.hpp new file mode 100644 index 0000000..53d8800 --- /dev/null +++ b/flux-foundation/flux/foundation/memory/detail/constexpr_memcpy.hpp @@ -0,0 +1,134 @@ +#pragma once +#include + +namespace flux::fou::detail { + +template + requires meta::trivially_lexicographically_comparable +constexpr int constexpr_memcmp(T const* lhs, U const* rhs, ::std::size_t count = 1) noexcept { + if consteval { + if (1 == sizeof(T) && !meta::same_as) { + return __builtin_memcmp(lhs, rhs, count * sizeof(T)); + } + + // clang-format off + for (; count != 0; --count, ++lhs, ++rhs) { + if (*lhs < *rhs) return -1; + if (*rhs < *lhs) return 1; + } + // clang-format on + return 0; + } + return __builtin_memcmp(lhs, rhs, count * sizeof(T)); +} + +// clang-format off +template + requires meta::trivially_equality_comparable +constexpr bool constexpr_memcmp_equal(T const* lhs, U const* rhs, ::std::size_t count = 1) noexcept { + if consteval { + if (1 == sizeof(T) && meta::integer) { + return __builtin_memcmp(lhs, rhs, count * sizeof(T)) == 0; + } + + for (; count != 0; --count, ++lhs, ++rhs) { + if (!(*lhs == *rhs)) { + return false; + } + } + return true; + } + return __builtin_memcmp(lhs, rhs, count * sizeof(T)) == 0; +} +// clang-format on + +// This function performs an assignment to an existing, already alive TriviallyCopyable object +// from another TriviallyCopyable object. +// +// It basically works around the fact that TriviallyCopyable objects are not required to be +// syntactically copy/move constructible or copy/move assignable. Technically, only one of the +// four operations is required to be syntactically valid -- but at least one definitely has to +// be valid. +// clang-format off +template + requires meta::assignable +constexpr auto assign_trivially_copyable(T& dest, U const& src) noexcept { + dest = src; + return dest; +} + +template + requires (not meta::assignable and meta::assignable) +constexpr auto assign_trivially_copyable(T& dest, U& src) noexcept { + dest = static_cast(src); + return dest; +} + +template + requires (not meta::assignable and + not meta::assignable and + meta::constructible) +constexpr auto assign_trivially_copyable(T& dest, U const& src) noexcept { + construct_at(addressof(dest), src); + return dest; +} + +template + requires (not meta::assignable and + not meta::assignable and + not meta::constructible and + meta::constructible) +constexpr auto assign_trivially_copyable(T& dest, U& src) noexcept { + construct_at(addressof(dest), static_cast(src)); + return dest; +} +// clang-format on + +template + requires meta::trivially_copyable> +constexpr auto constexpr_memcpy(T* dest, U const* src, ::std::size_t count = 1) noexcept { + if consteval { + if constexpr (meta::same_as, meta::remove_cv_t>) { + __builtin_memcpy(dest, src, count * sizeof(T)); + return dest; + } else { + for (::std::size_t i = 0; i != count; ++i) { + assign_trivially_copyable(dest[i], src[i]); + } + } + } else { + if (count > 0) { + __builtin_memcpy(dest, src, (count - 1) * sizeof(T) + meta::data_size_of); + } + } + return dest; +} + +template + requires meta::trivially_copyable> +constexpr auto constexpr_memmove(T* dest, U const* src, ::std::size_t count = 1) noexcept { + if consteval { + if constexpr (meta::same_as, meta::remove_cv_t>) { + __builtin_memmove(dest, src, count * sizeof(T)); + return dest; + } else { + if (is_pointer_in_range(src, src + count, dest)) { + for (; count > 0; --count) { + assign_trivially_copyable(dest[count - 1], src[count - 1]); + } + } else { + for (::std::size_t i = 0; i != count; ++i) { + assign_trivially_copyable(dest[i], src[i]); + } + } + } + } else { + if (count > 0) { + __builtin_memmove(dest, src, (count - 1) * sizeof(T) + meta::data_size_of); + } + } + return dest; +} +// clang-format on + +} // namespace flux::fou::detail \ No newline at end of file diff --git a/flux-foundation/flux/foundation/memory/detail/construct_at.hpp b/flux-foundation/flux/foundation/memory/detail/construct_at.hpp new file mode 100644 index 0000000..34248e5 --- /dev/null +++ b/flux-foundation/flux/foundation/memory/detail/construct_at.hpp @@ -0,0 +1,68 @@ +#pragma once + +// This is a workaround for writing your own constexpr `construct_at` if you don't want to +// pull everything found in the header. Currently, this hack only works with Clang. +// But that's okay, since I'm not going to use the others. +namespace std::detail { + +// clang-format off +template + requires requires(void* ptr, Args&&... args) { ::new (ptr) T(static_cast(args)...); } + // ^^^ allows copy elision since C++17 mandates it +constexpr T* construct_at(T* const location, Args&&... args) noexcept { + FLUX_ASSERT(location != nullptr, "null pointer given to construct_at"); + // @see: https://cplusplus.github.io/LWG/issue3870 + return ::new (static_cast(location)) T(::std::forward(args)...); +} +// clang-format on + +} // namespace std::detail + +namespace flux::fou { + +template +constexpr Iterator destroy_range(Iterator first, Iterator last) noexcept; + +namespace detail { + +using ::std::detail::construct_at; + +// clang-format off +template +#if __has_cpp_attribute(__gnu__::__always_inline__) +[[__gnu__::__always_inline__]] +#endif +constexpr void* voidify(T& from) noexcept { + // Cast away cv-qualifiers to allow modifying elements of a range through const iterators. + return const_cast(static_cast(addressof(from))); +} + +template +constexpr void destroy_at(T* const location) noexcept { + FLUX_ASSERT(location != nullptr, "null pointer given to destroy_at"); + if constexpr (meta::is_array_v) { + destroy_range(::std::begin(*location), ::std::end(*location)); + } else { + location->~T(); + } +} +// clang-format on + +} // namespace detail + +// clang-format off +template +constexpr T* default_construct_at(T* const p) noexcept { + if consteval { + return detail::construct_at(p); + } + return ::new (static_cast(p)) T; +} + +template +constexpr T* value_construct_at(T* const p) noexcept { + return detail::construct_at(p); +} +// clang-format on + +} // namespace flux::fou \ No newline at end of file diff --git a/flux-foundation/flux/foundation/memory/detail/to_address.hpp b/flux-foundation/flux/foundation/memory/detail/to_address.hpp new file mode 100644 index 0000000..a787585 --- /dev/null +++ b/flux-foundation/flux/foundation/memory/detail/to_address.hpp @@ -0,0 +1,31 @@ +#pragma once +#if defined(__cpp_lib_to_address) && __cpp_lib_to_address >= 201711L +# include +#endif + +namespace flux::fou::detail { + +// clang-format off +template +#if __has_cpp_attribute(__gnu__::__always_inline__) +[[__gnu__::__always_inline__]] +#endif +constexpr T* to_address(T* const pointer) noexcept { + static_assert(not meta::function, "T is a function type."); + return pointer; +} + +template +#if __has_cpp_attribute(__gnu__::__always_inline__) +[[__gnu__::__always_inline__]] +#endif +constexpr auto to_address(T const& pointer) noexcept { +#if defined(__cpp_lib_to_address) && __cpp_lib_to_address >= 201711L + return ::std::to_address(pointer); +#else + return addressof(*pointer); +#endif +} +// clang-format on + +} // namespace flux::fou::detail \ No newline at end of file diff --git a/flux-foundation/flux/foundation/memory/relocate-test.cpp b/flux-foundation/flux/foundation/memory/relocate-test.cpp new file mode 100644 index 0000000..453f2bd --- /dev/null +++ b/flux-foundation/flux/foundation/memory/relocate-test.cpp @@ -0,0 +1,80 @@ +#include + +#include +#include + +using namespace flux; + +static_assert(meta::trivially_relocatable); +static_assert(meta::trivially_relocatable); +static_assert(meta::trivially_relocatable); +static_assert(meta::trivially_relocatable); + +static_assert(meta::trivially_relocatable); +static_assert(meta::trivially_relocatable); +static_assert(meta::trivially_relocatable); +static_assert(meta::trivially_relocatable); + +static_assert(not meta::trivially_relocatable); +static_assert(not meta::trivially_relocatable); +static_assert(not meta::trivially_relocatable); + +static_assert(meta::relocatable); +static_assert(meta::relocatable); +static_assert(meta::relocatable); +static_assert(meta::relocatable); + +static_assert(not meta::relocatable); +static_assert(not meta::relocatable); +static_assert(not meta::relocatable); +static_assert(not meta::relocatable); + +static_assert(not meta::relocatable); +static_assert(not meta::relocatable); +static_assert(not meta::relocatable); + +struct A { + int ia; +}; +static_assert(meta::trivially_copyable and meta::trivially_relocatable); + +struct B : A { + int ib; + char cb; +}; +static_assert(meta::trivially_copyable and meta::trivially_relocatable); + +struct C : B { + short sc; +}; +static_assert(meta::trivially_copyable and meta::trivially_relocatable); + +TEST_CASE("fou::relocate_at", "[flux-memory/relocate.hpp]") { + SECTION("trivially copyable/relocatable at run-time (std::memmove)") { + C c1 = {{{1}, 2, 3}, 4}; + B& b1 = c1; + B b2 = {{5}, 6, 7}; + // Before std::memmove + CHECK(4 == c1.sc); + + ::std::memmove(&b1, &b2, sizeof(B)); + // After std::memmove +#if defined(_WIN64) + CHECK(4 == c1.sc); // windows handles this correctly +#else + CHECK(4 != c1.sc); // 64, or 0, or anything but 4 +#endif + } + + SECTION("trivially copyable/relocatable at run-time") { + C c1 = {{{1}, 2, 3}, 4}; + B& b1 = c1; + B b2 = {{5}, 6, 7}; + // Before relocate_at + CHECK(4 == c1.sc); + + fou::relocate_at(&b1, &b2); + // After relocate_at + CHECK(4 == c1.sc); + } +} \ No newline at end of file diff --git a/flux-foundation/flux/foundation/memory/relocate.hpp b/flux-foundation/flux/foundation/memory/relocate.hpp new file mode 100644 index 0000000..7082786 --- /dev/null +++ b/flux-foundation/flux/foundation/memory/relocate.hpp @@ -0,0 +1,28 @@ +#pragma once +#include + +namespace flux::fou { + +// clang-format off +template +struct [[nodiscard]] destroy_guard final { + T* value; + constexpr explicit destroy_guard(T* const other) noexcept : value{other} {} + constexpr ~destroy_guard() noexcept requires meta::trivially_destructible = default; + constexpr ~destroy_guard() noexcept { detail::destroy_at(value); } +}; +// clang-format on + +template + requires meta::relocatable_from +constexpr U* relocate_at(T* const src, U* const dest) noexcept { + if constexpr (meta::same_trivially_relocatable) { + detail::constexpr_memmove(dest, src); + return launder(dest); // required? + } else { + destroy_guard guard{src}; + return detail::construct_at(dest, ::std::move(*src)); + } +} + +} // namespace flux::fou \ No newline at end of file diff --git a/flux-foundation/flux/foundation/memory/uninitialized_algorithms-test.cpp b/flux-foundation/flux/foundation/memory/uninitialized_algorithms-test.cpp new file mode 100644 index 0000000..807fd22 --- /dev/null +++ b/flux-foundation/flux/foundation/memory/uninitialized_algorithms-test.cpp @@ -0,0 +1,967 @@ +#include + +#include // TODO: Replace with own implementation. +#include + +using namespace flux; + +// clang-format off +template +struct value_wrapper { + T value; + + constexpr bool operator==(value_wrapper const&) const noexcept = default; +}; + +namespace type_tag { +struct move_constructible_and_destructible {}; +struct trivially_default_constructible {}; +struct non_trivially_default_constructible {}; +struct non_trivially_copyable {}; +struct non_trivially_movable {}; +} // namespace type_tag + +template +struct test_type; +// clang-format on + +template +struct test_type final : value_wrapper {}; +using trivially_default_constructible_t = test_type; +static_assert(meta::trivially_default_constructible); +using trivially_copyable_t = trivially_default_constructible_t; +static_assert(meta::trivially_copyable); +static_assert(meta::trivially_relocatable); + +template +struct test_type final : value_wrapper { + constexpr test_type() noexcept : value_wrapper() {} +}; +using non_trivially_default_constructible_t = + test_type; +static_assert(not meta::trivially_default_constructible); + +template +struct test_type final : value_wrapper { + constexpr test_type() noexcept : value_wrapper() {} + constexpr test_type(T value) noexcept : value_wrapper(value) {} + constexpr test_type(test_type const& other) noexcept : value_wrapper(other) {} +}; +using non_trivially_copyable_t = test_type; +static_assert(not meta::trivially_copyable); + +template +struct test_type final : value_wrapper { + constexpr test_type() noexcept : value_wrapper() {} + constexpr test_type(T value) noexcept : value_wrapper(value) {} + constexpr test_type(test_type&& other) noexcept : value_wrapper(::std::move(other)) {} +}; +using non_trivially_movable_t = test_type; +static_assert(not meta::trivially_copyable); +static_assert(not meta::trivially_relocatable); +static_assert(meta::relocatable); + +struct non_trivially_movable_counter_t final { + int value; + + explicit non_trivially_movable_counter_t(int&& v) noexcept : value{v} { + v = 0; + ++count; + ++constructed; + } + + non_trivially_movable_counter_t(non_trivially_movable_counter_t const&) noexcept { + assert(false); + } + + ~non_trivially_movable_counter_t() { + assert(count > 0); + --count; + } + + // clang-format off + static int count; + static int constructed; + static void reset() noexcept { count = constructed = 0; } + // clang-format on + + friend void operator&(non_trivially_movable_counter_t) = delete; +}; +int non_trivially_movable_counter_t::count = 0; +int non_trivially_movable_counter_t::constructed = 0; +static_assert(not meta::trivially_copyable); +using relocatable_counter_t = non_trivially_movable_counter_t; + +TEST_CASE("fou::ranges::uninitialized_default_construct", + "[flux-memory/uninitialized_algorithms.hpp]") { + using namespace flux::fou; + + SECTION("trivially default constructible at compile-time") { + using trivially_default_constructible_array_t = + ::std::array; + constexpr auto result_first_last = [] noexcept { + trivially_default_constructible_array_t array; + ranges::uninitialized_default_construct(array.begin(), array.end()); + return array; + }(); + STATIC_REQUIRE(result_first_last == trivially_default_constructible_array_t{}); + + constexpr auto result_range = [] noexcept { + trivially_default_constructible_array_t array; + ranges::uninitialized_default_construct(array); + return array; + }(); + STATIC_REQUIRE(result_range == trivially_default_constructible_array_t{}); + } + + SECTION("trivially default constructible at run-time (skip initialization)") { + using trivially_default_constructible_array_t = + ::std::array; + { + trivially_default_constructible_array_t array; + auto it = ranges::uninitialized_default_construct(array.begin(), array.end()); + CHECK(array.end() == it); // assume that there are garbage values + } + { + trivially_default_constructible_array_t array; + auto it = ranges::uninitialized_default_construct(array); + CHECK(array.end() == it); // assume that there are garbage values + } + } + + SECTION("non trivially default constructible at compile-time") { + using non_trivially_default_constructible_array_t = + ::std::array; + constexpr auto result_first_last = [] noexcept { + non_trivially_default_constructible_array_t array; + ranges::uninitialized_default_construct(array.begin(), array.end()); + return array; + }(); + STATIC_REQUIRE(result_first_last == non_trivially_default_constructible_array_t{}); + + constexpr auto result_range = [] noexcept { + non_trivially_default_constructible_array_t array; + ranges::uninitialized_default_construct(array); + return array; + }(); + STATIC_REQUIRE(result_range == non_trivially_default_constructible_array_t{}); + } + + SECTION("non trivially default constructible at run-time") { + using non_trivially_default_constructible_array_t = + ::std::array; + { + non_trivially_default_constructible_array_t array; + auto it = ranges::uninitialized_default_construct(array.begin(), array.end()); + CHECK(array.end() == it); + CHECK(array == non_trivially_default_constructible_array_t{}); + } + { + non_trivially_default_constructible_array_t array; + auto it = ranges::uninitialized_default_construct(array); + CHECK(array.end() == it); + CHECK(array == non_trivially_default_constructible_array_t{}); + } + } +} + +TEST_CASE("fou::ranges::uninitialized_default_construct_n", + "[flux-memory/uninitialized_algorithms.hpp]") { + using namespace flux::fou; + + SECTION("trivially default constructible at compile-time") { + using trivially_default_constructible_array_t = + ::std::array; + constexpr auto result = [] noexcept { + trivially_default_constructible_array_t array; + ranges::uninitialized_default_construct_n(array.begin(), array.size()); + return array; + }(); + STATIC_REQUIRE(result == trivially_default_constructible_array_t{}); + } + + SECTION("trivially default constructible at run-time (skip initialization)") { + using trivially_default_constructible_array_t = + ::std::array; + trivially_default_constructible_array_t array; + auto it = ranges::uninitialized_default_construct_n(array.begin(), array.size()); + CHECK(array.end() == it); // assume that there are garbage values + } + + SECTION("non trivially default constructible at compile-time") { + using non_trivially_default_constructible_array_t = + ::std::array; + constexpr auto result = [] noexcept { + non_trivially_default_constructible_array_t array; + ranges::uninitialized_default_construct_n(array.begin(), array.size()); + return array; + }(); + STATIC_REQUIRE(result == non_trivially_default_constructible_array_t{}); + } + + SECTION("non trivially default constructible at run-time") { + using non_trivially_default_constructible_array_t = + ::std::array; + non_trivially_default_constructible_array_t array; + auto it = ranges::uninitialized_default_construct_n(array.begin(), array.size()); + CHECK(array.end() == it); + CHECK(array == non_trivially_default_constructible_array_t{}); + } +} + +TEST_CASE("fou::ranges::uninitialized_value_construct", + "[flux-memory/uninitialized_algorithms.hpp]") { + using namespace flux::fou; + + SECTION("trivially copyable at compile-time") { + using trivially_copyable_array_t = ::std::array; + constexpr auto result_first_last = [] noexcept { + trivially_copyable_array_t array; + ranges::uninitialized_value_construct(array.begin(), array.end()); + return array; + }(); + STATIC_REQUIRE(result_first_last == trivially_copyable_array_t{}); + + constexpr auto result_range = [] noexcept { + trivially_copyable_array_t array; + ranges::uninitialized_value_construct(array); + return array; + }(); + STATIC_REQUIRE(result_range == trivially_copyable_array_t{}); + } + + SECTION("trivially copyable at run-time (memset initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::use_memset_value_construct); + { + trivially_copyable_array_t array; + + auto it = ranges::uninitialized_value_construct(array.begin(), array.end()); + CHECK(array.end() == it); + CHECK(array == trivially_copyable_array_t{}); + } + { + trivially_copyable_array_t array; + + auto it = ranges::uninitialized_value_construct(array); + CHECK(array.end() == it); + CHECK(array == trivially_copyable_array_t{}); + } + } + + SECTION("non trivially copyable at compile-time") { + using non_trivially_copyable_array_t = ::std::array; + constexpr auto result_first_last = [] noexcept { + non_trivially_copyable_array_t array; + ranges::uninitialized_value_construct(array.begin(), array.end()); + return array; + }(); + STATIC_REQUIRE(result_first_last == non_trivially_copyable_array_t{}); + + constexpr auto result_range = [] noexcept { + non_trivially_copyable_array_t array; + ranges::uninitialized_value_construct(array); + return array; + }(); + STATIC_REQUIRE(result_range == non_trivially_copyable_array_t{}); + } + + SECTION("non trivially copyable at run-time") { + using non_trivially_copyable_array_t = ::std::array; + static_assert( + not meta::use_memset_value_construct); + { + non_trivially_copyable_array_t array; + + auto it = ranges::uninitialized_value_construct(array.begin(), array.end()); + CHECK(array.end() == it); + CHECK(array == non_trivially_copyable_array_t{}); + } + { + non_trivially_copyable_array_t array; + + auto it = ranges::uninitialized_value_construct(array); + CHECK(array.end() == it); + CHECK(array == non_trivially_copyable_array_t{}); + } + } +} + +TEST_CASE("fou::ranges::uninitialized_value_construct_n", + "[flux-memory/uninitialized_algorithms.hpp]") { + using namespace flux::fou; + + SECTION("trivially copyable at compile-time") { + using trivially_copyable_array_t = ::std::array; + constexpr auto result_first_last = [] noexcept { + trivially_copyable_array_t array; + ranges::uninitialized_value_construct_n(array.begin(), array.size()); + return array; + }(); + STATIC_REQUIRE(result_first_last == trivially_copyable_array_t{}); + } + + SECTION("trivially copyable at run-time (memset initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::use_memset_value_construct); + + trivially_copyable_array_t array; + auto it = ranges::uninitialized_value_construct_n(array.begin(), array.size()); + CHECK(array.end() == it); + CHECK(array == trivially_copyable_array_t{}); + } + + SECTION("non trivially copyable at compile-time") { + using non_trivially_copyable_array_t = ::std::array; + constexpr auto result_first_last = [] noexcept { + non_trivially_copyable_array_t array; + ranges::uninitialized_value_construct_n(array.begin(), array.size()); + return array; + }(); + STATIC_REQUIRE(result_first_last == non_trivially_copyable_array_t{}); + } + + SECTION("non trivially copyable at run-time") { + using non_trivially_copyable_array_t = ::std::array; + non_trivially_copyable_array_t array; + + auto it = ranges::uninitialized_value_construct_n(array.begin(), array.size()); + CHECK(array.end() == it); + CHECK(array == non_trivially_copyable_array_t{}); + } +} + +TEST_CASE("fou::ranges::uninitialized_copy", "[flux-memory/uninitialized_algorithms.hpp]") { + using namespace flux::fou; + + SECTION("trivially copyable at compile-time (memmove initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + constexpr auto result_first_last = [] noexcept { + trivially_copyable_array_t array1 = {{{1}, {2}, {3}}}; + trivially_copyable_array_t array2; + [[maybe_unused]] auto out = ranges::uninitialized_copy(array1.begin(), array1.end(), + array2.begin(), array2.end()); + return array2; + }(); + STATIC_REQUIRE(result_first_last == trivially_copyable_array_t{{{1}, {2}, {3}}}); + + constexpr auto result_first_last_result = [] noexcept { + trivially_copyable_array_t array1 = {{{3}, {2}, {1}}}; + trivially_copyable_array_t array2; + ranges::uninitialized_copy(array1.begin(), array1.end(), array2.begin()); + return array2; + }(); + STATIC_REQUIRE(result_first_last_result == trivially_copyable_array_t{{{3}, {2}, {1}}}); + + constexpr auto result_range = [] noexcept { + trivially_copyable_array_t array1; + trivially_copyable_array_t array2 = {{{4}, {5}, {6}}}; + [[maybe_unused]] auto out = ranges::uninitialized_copy(array2, array1); + return array1; + }(); + STATIC_REQUIRE(result_range == trivially_copyable_array_t{{{4}, {5}, {6}}}); + } + + SECTION("trivially copyable at run-time (memmove initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + { + trivially_copyable_array_t array1 = {{{1}, {2}, {3}}}; + trivially_copyable_array_t array2; + + auto [it1, it2] = ranges::uninitialized_copy(array1.begin(), array1.end(), + array2.begin(), array2.end()); + CHECK(array1.end() == it1); + CHECK(array2.end() == it2); + CHECK(array2 == trivially_copyable_array_t{{{1}, {2}, {3}}}); + } + { + trivially_copyable_array_t array1 = {{{3}, {2}, {1}}}; + trivially_copyable_array_t array2; + + auto it = ranges::uninitialized_copy(array1.begin(), array1.end(), array2.begin()); + CHECK(array2.end() == it); + CHECK(array2 == trivially_copyable_array_t{{{3}, {2}, {1}}}); + } + { + trivially_copyable_array_t array1; + trivially_copyable_array_t array2 = {{{4}, {5}, {6}}}; + + auto [it1, it2] = ranges::uninitialized_copy(array2, array1); + CHECK(array1.end() == it2); + CHECK(array2.end() == it1); + CHECK(array1 == trivially_copyable_array_t{{{4}, {5}, {6}}}); + } + } + + SECTION("non trivially copyable at compile-time") { + using non_trivially_copyable_array_t = ::std::array; + static_assert(not meta::memcpyable); + constexpr auto result_first_last = [] noexcept { + non_trivially_copyable_array_t array1 = {1, 2, 3}; + non_trivially_copyable_array_t array2; + [[maybe_unused]] auto out = ranges::uninitialized_copy(array1.begin(), array1.end(), + array2.begin(), array2.end()); + return array2; + }(); + STATIC_REQUIRE(result_first_last == non_trivially_copyable_array_t{1, 2, 3}); + + constexpr auto result_first_last_result = [] noexcept { + non_trivially_copyable_array_t array1 = {3, 2, 1}; + non_trivially_copyable_array_t array2; + ranges::uninitialized_copy(array1.begin(), array1.end(), array2.begin()); + return array2; + }(); + STATIC_REQUIRE(result_first_last_result == non_trivially_copyable_array_t{3, 2, 1}); + + constexpr auto result_range = [] noexcept { + non_trivially_copyable_array_t array1; + non_trivially_copyable_array_t array2 = {4, 5, 6}; + [[maybe_unused]] auto out = ranges::uninitialized_copy(array2, array1); + return array1; + }(); + STATIC_REQUIRE(result_range == non_trivially_copyable_array_t{4, 5, 6}); + } + + SECTION("non trivially copyable at run-time") { + using non_trivially_copyable_array_t = ::std::array; + static_assert(not meta::memcpyable); + { + non_trivially_copyable_array_t array1 = {1, 2, 3}; + non_trivially_copyable_array_t array2; + + auto [it1, it2] = ranges::uninitialized_copy(array1.begin(), array1.end(), + array2.begin(), array2.end()); + CHECK(array1.end() == it1); + CHECK(array2.end() == it2); + CHECK(array2 == non_trivially_copyable_array_t{1, 2, 3}); + } + { + non_trivially_copyable_array_t array1 = {3, 2, 1}; + non_trivially_copyable_array_t array2; + + auto it = ranges::uninitialized_copy(array1.begin(), array1.end(), array2.begin()); + CHECK(array2.end() == it); + CHECK(array2 == non_trivially_copyable_array_t{3, 2, 1}); + } + { + non_trivially_copyable_array_t array1; + non_trivially_copyable_array_t array2 = {4, 5, 6}; + + auto [it1, it2] = ranges::uninitialized_copy(array2, array1); + CHECK(array1.end() == it2); + CHECK(array2.end() == it1); + CHECK(array1 == non_trivially_copyable_array_t{4, 5, 6}); + } + } +} + +TEST_CASE("fou::ranges::uninitialized_copy_no_overlap", + "[flux-memory/uninitialized_algorithms.hpp]") { + using namespace flux::fou; + + SECTION("trivially copyable at compile-time (memcpy initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + constexpr auto result_first_last = [] noexcept { + trivially_copyable_array_t array1 = {{{1}, {2}, {3}}}; + trivially_copyable_array_t array2; + [[maybe_unused]] auto out = ranges::uninitialized_copy_no_overlap( + array1.begin(), array1.end(), array2.begin(), array2.end()); + return array2; + }(); + STATIC_REQUIRE(result_first_last == trivially_copyable_array_t{{{1}, {2}, {3}}}); + + constexpr auto result_first_last_result = [] noexcept { + trivially_copyable_array_t array1 = {{{3}, {2}, {1}}}; + trivially_copyable_array_t array2; + ranges::uninitialized_copy_no_overlap(array1.begin(), array1.end(), array2.begin()); + return array2; + }(); + STATIC_REQUIRE(result_first_last_result == trivially_copyable_array_t{{{3}, {2}, {1}}}); + + constexpr auto result_range = [] noexcept { + trivially_copyable_array_t array1; + trivially_copyable_array_t array2 = {{{4}, {5}, {6}}}; + [[maybe_unused]] auto out = ranges::uninitialized_copy_no_overlap(array2, array1); + return array1; + }(); + STATIC_REQUIRE(result_range == trivially_copyable_array_t{{{4}, {5}, {6}}}); + } + + SECTION("trivially copyable at run-time (memcpy initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + { + trivially_copyable_array_t array1 = {{{1}, {2}, {3}}}; + trivially_copyable_array_t array2; + + auto [it1, it2] = ranges::uninitialized_copy_no_overlap(array1.begin(), array1.end(), + array2.begin(), array2.end()); + CHECK(array1.end() == it1); + CHECK(array2.end() == it2); + CHECK(array2 == trivially_copyable_array_t{{{1}, {2}, {3}}}); + } + { + trivially_copyable_array_t array1 = {{{3}, {2}, {1}}}; + trivially_copyable_array_t array2; + + auto it = ranges::uninitialized_copy_no_overlap(array1.begin(), array1.end(), + array2.begin()); + CHECK(array2.end() == it); + CHECK(array2 == trivially_copyable_array_t{{{3}, {2}, {1}}}); + } + { + trivially_copyable_array_t array1; + trivially_copyable_array_t array2 = {{{4}, {5}, {6}}}; + + auto [it1, it2] = ranges::uninitialized_copy_no_overlap(array2, array1); + CHECK(array1.end() == it2); + CHECK(array2.end() == it1); + CHECK(array1 == trivially_copyable_array_t{{{4}, {5}, {6}}}); + } + } + + SECTION("non trivially copyable at compile-time") { + using non_trivially_copyable_array_t = ::std::array; + static_assert(not meta::memcpyable); + constexpr auto result_first_last = [] noexcept { + non_trivially_copyable_array_t array1 = {1, 2, 3}; + non_trivially_copyable_array_t array2; + [[maybe_unused]] auto out = ranges::uninitialized_copy_no_overlap( + array1.begin(), array1.end(), array2.begin(), array2.end()); + return array2; + }(); + STATIC_REQUIRE(result_first_last == non_trivially_copyable_array_t{1, 2, 3}); + + constexpr auto result_first_last_result = [] noexcept { + non_trivially_copyable_array_t array1 = {3, 2, 1}; + non_trivially_copyable_array_t array2; + ranges::uninitialized_copy_no_overlap(array1.begin(), array1.end(), array2.begin()); + return array2; + }(); + STATIC_REQUIRE(result_first_last_result == non_trivially_copyable_array_t{3, 2, 1}); + + constexpr auto result_range = [] noexcept { + non_trivially_copyable_array_t array1; + non_trivially_copyable_array_t array2 = {4, 5, 6}; + [[maybe_unused]] auto out = ranges::uninitialized_copy_no_overlap(array2, array1); + return array1; + }(); + STATIC_REQUIRE(result_range == non_trivially_copyable_array_t{4, 5, 6}); + } + + SECTION("non trivially copyable at run-time") { + using non_trivially_copyable_array_t = ::std::array; + static_assert(not meta::memcpyable); + { + non_trivially_copyable_array_t array1 = {1, 2, 3}; + non_trivially_copyable_array_t array2; + + auto [it1, it2] = ranges::uninitialized_copy_no_overlap(array1.begin(), array1.end(), + array2.begin(), array2.end()); + CHECK(array1.end() == it1); + CHECK(array2.end() == it2); + CHECK(array2 == non_trivially_copyable_array_t{1, 2, 3}); + } + { + non_trivially_copyable_array_t array1 = {3, 2, 1}; + non_trivially_copyable_array_t array2; + + auto it = ranges::uninitialized_copy_no_overlap(array1.begin(), array1.end(), + array2.begin()); + CHECK(array2.end() == it); + CHECK(array2 == non_trivially_copyable_array_t{3, 2, 1}); + } + { + non_trivially_copyable_array_t array1; + non_trivially_copyable_array_t array2 = {4, 5, 6}; + + auto [it1, it2] = ranges::uninitialized_copy_no_overlap(array2, array1); + CHECK(array1.end() == it2); + CHECK(array2.end() == it1); + CHECK(array1 == non_trivially_copyable_array_t{4, 5, 6}); + } + } +} + +TEST_CASE("fou::ranges::uninitialized_copy_n", "[flux-memory/uninitialized_algorithms.hpp]") { + using namespace flux::fou; + + SECTION("trivially copyable at compile-time (memmove initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + constexpr auto result_first_last = [] noexcept { + trivially_copyable_array_t array1 = {{{1}, {2}, {3}}}; + trivially_copyable_array_t array2; + ranges::uninitialized_copy_n(array1.begin(), array2.size(), array2.begin()); + return array2; + }(); + STATIC_REQUIRE(result_first_last == trivially_copyable_array_t{{{1}, {2}, {3}}}); + } + + SECTION("trivially copyable at run-time (memmove initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + trivially_copyable_array_t array1 = {{{3}, {2}, {1}}}; + trivially_copyable_array_t array2; + + auto it = ranges::uninitialized_copy_n(array1.begin(), array2.size(), array2.begin()); + CHECK(array2.end() == it); + CHECK(array2 == trivially_copyable_array_t{{{3}, {2}, {1}}}); + } + + SECTION("non trivially copyable at compile-time") { + using non_trivially_copyable_array_t = ::std::array; + static_assert(not meta::memcpyable); + constexpr auto result_first_last = [] noexcept { + non_trivially_copyable_array_t array1 = {1, 2, 3}; + non_trivially_copyable_array_t array2; + ranges::uninitialized_copy_n(array1.begin(), array2.size(), array2.begin()); + return array2; + }(); + STATIC_REQUIRE(result_first_last == non_trivially_copyable_array_t{1, 2, 3}); + } + + SECTION("non trivially copyable at run-time") { + using non_trivially_copyable_array_t = ::std::array; + static_assert(not meta::memcpyable); + non_trivially_copyable_array_t array1 = {3, 2, 1}; + non_trivially_copyable_array_t array2; + + auto it = ranges::uninitialized_copy_n(array1.begin(), array2.size(), array2.begin()); + CHECK(array2.end() == it); + CHECK(array2 == non_trivially_copyable_array_t{3, 2, 1}); + } +} + +TEST_CASE("fou::ranges::uninitialized_move", "[flux-memory/uninitialized_algorithms.hpp]") { + using namespace flux::fou; + + SECTION("trivially movable at compile-time (memmove initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + constexpr auto result_range = [] noexcept { + trivially_copyable_array_t array1; + trivially_copyable_array_t array2 = {{{4}, {5}, {6}}}; + [[maybe_unused]] auto out = ranges::uninitialized_move(array2, array1); + return array1; + }(); + STATIC_REQUIRE(result_range == trivially_copyable_array_t{{{4}, {5}, {6}}}); + } + + SECTION("trivially movable at run-time (memmove initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + { + trivially_copyable_array_t array1 = {{{3}, {2}, {1}}}; + trivially_copyable_array_t array2; + + auto it = ranges::uninitialized_move(array1.begin(), array1.end(), array2.begin()); + CHECK(array2.end() == it); + CHECK(array2 == trivially_copyable_array_t{{{3}, {2}, {1}}}); + } + { + trivially_copyable_array_t array1; + trivially_copyable_array_t array2 = {{{4}, {5}, {6}}}; + + auto [it1, it2] = ranges::uninitialized_move(array2, array1); + CHECK(array1.end() == it2); + CHECK(array2.end() == it1); + CHECK(array1 == trivially_copyable_array_t{{{4}, {5}, {6}}}); + } + } + + SECTION("non trivially movable at compile-time") { + using non_trivially_movable_array_t = ::std::array; + static_assert(not meta::memcpyable); + constexpr auto result_range = [] noexcept { + non_trivially_movable_array_t array1; + non_trivially_movable_array_t array2 = {4, 5, 6}; + [[maybe_unused]] auto out = ranges::uninitialized_move(array2, array1); + return array1; + }(); + STATIC_REQUIRE(result_range == non_trivially_movable_array_t{4, 5, 6}); + } + + SECTION("non trivially movable at run-time") { + using non_trivially_movable_array_t = ::std::array; + static_assert(not meta::memcpyable); + { + non_trivially_movable_array_t array1 = {3, 2, 1}; + non_trivially_movable_array_t array2; + + auto it = ranges::uninitialized_move(array1.begin(), array1.end(), array2.begin()); + CHECK(array2.end() == it); + CHECK(array2 == non_trivially_movable_array_t{3, 2, 1}); + } + { + non_trivially_movable_array_t array1; + non_trivially_movable_array_t array2 = {4, 5, 6}; + + auto [it1, it2] = ranges::uninitialized_move(array2, array1); + CHECK(array1.end() == it2); + CHECK(array2.end() == it1); + CHECK(array1 == non_trivially_movable_array_t{4, 5, 6}); + } + } +} + +TEST_CASE("fou::ranges::uninitialized_move_n", "[flux-memory/uninitialized_algorithms.hpp]") { + using namespace flux::fou; + + SECTION("trivially movable at compile-time (memmove initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + constexpr auto result_first_last = [] noexcept { + trivially_copyable_array_t array1 = {{{1}, {2}, {3}}}; + trivially_copyable_array_t array2; + ranges::uninitialized_move_n(array1.begin(), array2.size(), array2.begin()); + return array2; + }(); + STATIC_REQUIRE(result_first_last == trivially_copyable_array_t{{{1}, {2}, {3}}}); + } + + SECTION("trivially movable at run-time (memmove initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + trivially_copyable_array_t array1 = {{{3}, {2}, {1}}}; + trivially_copyable_array_t array2; + + auto it = ranges::uninitialized_move_n(array1.begin(), array2.size(), array2.begin()); + CHECK(array2.end() == it); + CHECK(array2 == trivially_copyable_array_t{{{3}, {2}, {1}}}); + } + + SECTION("non trivially movable at compile-time") { + using non_trivially_movable_array_t = ::std::array; + static_assert(not meta::memcpyable); + constexpr auto result_first_last = [] noexcept { + non_trivially_movable_array_t array1 = {1, 2, 3}; + non_trivially_movable_array_t array2; + ranges::uninitialized_move_n(array1.begin(), array2.size(), array2.begin()); + return array2; + }(); + STATIC_REQUIRE(result_first_last == non_trivially_movable_array_t{1, 2, 3}); + } + + SECTION("non trivially movable at run-time") { + using non_trivially_movable_array_t = ::std::array; + static_assert(not meta::memcpyable); + non_trivially_movable_array_t array1 = {3, 2, 1}; + non_trivially_movable_array_t array2; + + auto it = ranges::uninitialized_move_n(array1.begin(), array2.size(), array2.begin()); + CHECK(array2.end() == it); + CHECK(array2 == non_trivially_movable_array_t{3, 2, 1}); + } +} + +TEST_CASE("fou::ranges::uninitialized_relocate", "[flux-memory/uninitialized_algorithms.hpp]") { + using namespace flux::fou; + + SECTION("trivially relocatable at compile-time (memmove initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + constexpr auto result_range = [] noexcept { + trivially_copyable_array_t array1; + trivially_copyable_array_t array2 = {{{4}, {5}, {6}}}; + [[maybe_unused]] auto out = ranges::uninitialized_relocate(array2, array1); + return array1; + }(); + STATIC_REQUIRE(result_range == trivially_copyable_array_t{{{4}, {5}, {6}}}); + } + + SECTION("trivially relocatable at run-time (memmove initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + { + trivially_copyable_array_t array1 = {{{3}, {2}, {1}}}; + trivially_copyable_array_t array2; + + auto it = ranges::uninitialized_relocate(array1.begin(), array1.end(), array2.begin()); + CHECK(array2.end() == it); + CHECK(array2 == trivially_copyable_array_t{{{3}, {2}, {1}}}); + } + { + trivially_copyable_array_t array1; + trivially_copyable_array_t array2 = {{{4}, {5}, {6}}}; + + auto [it1, it2] = ranges::uninitialized_relocate(array2, array1); + CHECK(array1.end() == it2); + CHECK(array2.end() == it1); + CHECK(array1 == trivially_copyable_array_t{{{4}, {5}, {6}}}); + } + } + + SECTION("non trivially relocatable at compile-time") { + using non_trivially_movable_array_t = ::std::array; + static_assert(not meta::memcpyable); + constexpr auto result_range = [] noexcept { + non_trivially_movable_array_t array1; + non_trivially_movable_array_t array2 = {4, 5, 6}; + [[maybe_unused]] auto out = ranges::uninitialized_relocate(array2, array1); + return array1; + }(); + STATIC_REQUIRE(result_range == non_trivially_movable_array_t{4, 5, 6}); + } + + SECTION("non trivially relocatable at run-time") { + relocatable_counter_t::reset(); + + using InputIterator = int*; + using OutputIterator = relocatable_counter_t*; + + int const N = 5; + int values[N] = {1, 2, 3, 4, 5}; + + alignas(relocatable_counter_t) std::byte pool[sizeof(relocatable_counter_t) * N]; + relocatable_counter_t* counted = (relocatable_counter_t*)pool; + + // clang-format off + auto result = ranges::uninitialized_relocate(InputIterator(values), InputIterator(values + 1), + OutputIterator(counted)); + CHECK(result == OutputIterator(counted + 1)); + CHECK(relocatable_counter_t::constructed == 1); + CHECK(relocatable_counter_t::count == 1); + CHECK(counted[0].value == 1); + CHECK(values[0] == 0); + + result = ranges::uninitialized_relocate( InputIterator(values + 1), InputIterator(values + N), + OutputIterator(counted + 1), OutputIterator(counted + N)).out; + CHECK(result == OutputIterator(counted + N)); + CHECK(relocatable_counter_t::count == 5); + CHECK(relocatable_counter_t::constructed == 5); + CHECK(counted[1].value == 2); + CHECK(counted[2].value == 3); + CHECK(counted[3].value == 4); + CHECK(counted[4].value == 5); + CHECK(values[1] == 0); + CHECK(values[2] == 0); + CHECK(values[3] == 0); + CHECK(values[4] == 0); + // clang-format on + + destroy_range(counted, counted + N); + CHECK(relocatable_counter_t::count == 0); + } +} + +TEST_CASE("fou::ranges::uninitialized_relocate_no_overlap", + "[flux-memory/uninitialized_algorithms.hpp]") { + using namespace flux::fou; + + SECTION("trivially relocatable at compile-time (memmove initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + constexpr auto result_range = [] noexcept { + trivially_copyable_array_t array1; + trivially_copyable_array_t array2 = {{{4}, {5}, {6}}}; + [[maybe_unused]] auto out = ranges::uninitialized_relocate_no_overlap(array2, array1); + return array1; + }(); + STATIC_REQUIRE(result_range == trivially_copyable_array_t{{{4}, {5}, {6}}}); + } + + SECTION("trivially relocatable at run-time (memmove initialization)") { + using trivially_copyable_array_t = ::std::array; + static_assert(meta::memcpyable); + { + trivially_copyable_array_t array1 = {{{3}, {2}, {1}}}; + trivially_copyable_array_t array2; + + auto it = ranges::uninitialized_relocate_no_overlap(array1.begin(), array1.end(), + array2.begin()); + CHECK(array2.end() == it); + CHECK(array2 == trivially_copyable_array_t{{{3}, {2}, {1}}}); + } + { + trivially_copyable_array_t array1; + trivially_copyable_array_t array2 = {{{4}, {5}, {6}}}; + + auto [it1, it2] = ranges::uninitialized_relocate_no_overlap(array2, array1); + CHECK(array1.end() == it2); + CHECK(array2.end() == it1); + CHECK(array1 == trivially_copyable_array_t{{{4}, {5}, {6}}}); + } + } + + SECTION("non trivially relocatable at compile-time") { + using non_trivially_movable_array_t = ::std::array; + static_assert(not meta::memcpyable); + constexpr auto result_range = [] noexcept { + non_trivially_movable_array_t array1; + non_trivially_movable_array_t array2 = {4, 5, 6}; + [[maybe_unused]] auto out = ranges::uninitialized_relocate_no_overlap(array2, array1); + return array1; + }(); + STATIC_REQUIRE(result_range == non_trivially_movable_array_t{4, 5, 6}); + } + + SECTION("non trivially relocatable at run-time") { + relocatable_counter_t::reset(); + + using InputIterator = int*; + using OutputIterator = relocatable_counter_t*; + + int const N = 5; + int values[N] = {1, 2, 3, 4, 5}; + + alignas(relocatable_counter_t) std::byte pool[sizeof(relocatable_counter_t) * N]; + relocatable_counter_t* counted = (relocatable_counter_t*)pool; + + auto result = ranges::uninitialized_relocate_no_overlap( + InputIterator(values), InputIterator(values + 1), OutputIterator(counted)); + CHECK(result == OutputIterator(counted + 1)); + CHECK(relocatable_counter_t::constructed == 1); + CHECK(relocatable_counter_t::count == 1); + CHECK(counted[0].value == 1); + CHECK(values[0] == 0); + + result = ranges::uninitialized_relocate_no_overlap( + InputIterator(values + 1), InputIterator(values + N), + OutputIterator(counted + 1), OutputIterator(counted + N)) + .out; + CHECK(result == OutputIterator(counted + N)); + CHECK(relocatable_counter_t::count == 5); + CHECK(relocatable_counter_t::constructed == 5); + CHECK(counted[1].value == 2); + CHECK(counted[2].value == 3); + CHECK(counted[3].value == 4); + CHECK(counted[4].value == 5); + CHECK(values[1] == 0); + CHECK(values[2] == 0); + CHECK(values[3] == 0); + CHECK(values[4] == 0); + + destroy_range(counted, counted + N); + CHECK(relocatable_counter_t::count == 0); + } +} \ No newline at end of file diff --git a/flux-foundation/flux/foundation/memory/uninitialized_algorithms.hpp b/flux-foundation/flux/foundation/memory/uninitialized_algorithms.hpp new file mode 100644 index 0000000..4526b2a --- /dev/null +++ b/flux-foundation/flux/foundation/memory/uninitialized_algorithms.hpp @@ -0,0 +1,588 @@ +#pragma once +#include +#include + +namespace flux::fou { + +namespace detail { + +struct [[nodiscard]] uninitialized_default_construct_fn final { + template Sentinel> + requires meta::default_initializable> + static constexpr Iterator operator()(Iterator first, Sentinel last) noexcept { + using ValueType = meta::remove_ref_t>; + if consteval { + for (; first != last; ++first) { + detail::construct_at(detail::to_address(first)); + } + return first; + } else { + if constexpr (meta::trivially_default_constructible) { + return ranges::next(first, last); + } else { + for (; first != last; ++first) { + ::new (static_cast(detail::to_address(first))) ValueType; + } + return first; + } + } + } + + template + requires meta::default_initializable> + static constexpr meta::borrowed_iter_t operator()(Range&& range) noexcept { + return operator()(::std::ranges::begin(range), ::std::ranges::end(range)); + } +}; + +struct [[nodiscard]] uninitialized_default_construct_n_fn final { + template + requires meta::default_initializable> + static constexpr Iterator operator()(Iterator first, meta::iter_diff_t n) noexcept { + using ValueType = meta::remove_ref_t>; + if consteval { + for (; n > 0; ++first, (void)--n) { + detail::construct_at(detail::to_address(first)); + } + return first; + } else { + if constexpr (meta::trivially_default_constructible) { + return ranges::next(first, n); + } else { + for (; n > 0; ++first, (void)--n) { + ::new (static_cast(detail::to_address(first))) ValueType; + } + return first; + } + } + } +}; + +struct [[nodiscard]] uninitialized_value_construct_fn final { + template Sentinel> + requires meta::default_initializable> + static constexpr Iterator operator()(Iterator first, Sentinel last) noexcept { + using ValueType = meta::remove_ref_t>; + if constexpr (meta::use_memset_value_construct) { + if not consteval { + char* const first_byte = (char*)detail::to_address(first); + auto const n_bytes = (char*)detail::to_address(last) - first_byte; + __builtin_memset((void*)first_byte, 0, (std::size_t)n_bytes); + return last; + } + } + + for (; first != last; ++first) { + detail::construct_at(detail::to_address(first)); + } + return first; + } + + template + requires meta::default_initializable> + static constexpr meta::borrowed_iter_t operator()(Range&& range) noexcept { + return operator()(::std::ranges::begin(range), ::std::ranges::end(range)); + } +}; + +struct [[nodiscard]] uninitialized_value_construct_n_fn final { + template + requires meta::default_initializable> + static constexpr Iterator operator()(Iterator first, meta::iter_diff_t n) noexcept { + using ValueType = meta::remove_ref_t>; + if constexpr (meta::use_memset_value_construct) { + if not consteval { + auto* const first_byte = (char*)detail::to_address(first); + auto const n_bytes = sizeof(ValueType) * (std::size_t)n; + __builtin_memset((void*)first_byte, 0, n_bytes); + return first + n; + } + } + + for (; n > 0; ++first, (void)--n) { + detail::construct_at(detail::to_address(first)); + } + return first; + } +}; + +// clang-format off +template +struct [[nodiscard]] uninitialized_memcpy_or_memmove_fn final { + template + using result = in_out_result; + + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator> + requires(IsMove ? meta::iter_move_constructible + : meta::iter_copy_constructible) + FLUX_ALWAYS_INLINE + static constexpr OutputIterator operator()(InputIterator first, + InputSentinel last, + OutputIterator result) noexcept { + using OutType = meta::remove_ref_t>; + static_assert(IsMove ? meta::nothrow_move_assignable + : meta::nothrow_copy_assignable); + if constexpr (IsMove) { + auto const count = last - first; + detail::constexpr_memmove(detail::to_address(result), detail::to_address(first), + (std::size_t)count); + return result + count; + } else { + auto const count = last - first; + detail::constexpr_memcpy(detail::to_address(result), detail::to_address(first), + (std::size_t)count); + return result + count; + } + } + + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator, + meta::nothrow_sentinel_for OutputSentinel> + requires(IsMove ? meta::iter_move_constructible + : meta::iter_copy_constructible) + FLUX_ALWAYS_INLINE + static constexpr auto operator()(InputIterator ifirst, InputSentinel ilast, + OutputIterator ofirst, OutputSentinel olast) noexcept + -> result { + using OutType = meta::remove_ref_t>; + static_assert(IsMove ? meta::nothrow_move_constructible + : meta::nothrow_copy_constructible); + constexpr bool sized_input = meta::sized_sentinel_for< InputSentinel, InputIterator>; + constexpr bool sized_output = meta::sized_sentinel_for; + if constexpr (IsMove) { + if constexpr (sized_input && sized_output) { + auto const count = ::std::min(ilast - ifirst, olast - ofirst); + detail::constexpr_memmove(detail::to_address(ofirst), detail::to_address(ifirst), + (std::size_t)count); + return {ifirst + count, ofirst + count}; + } else if constexpr (sized_input) { + auto const count = ilast - ifirst; + detail::constexpr_memmove(detail::to_address(ofirst), detail::to_address(ifirst), + (std::size_t)count); + return {ifirst + count, ofirst + count}; + } else if constexpr (sized_output) { + auto const count = olast - ofirst; + detail::constexpr_memmove(detail::to_address(ofirst), detail::to_address(ifirst), + (std::size_t)count); + return {ifirst + count, ofirst + count}; + } else { + static_assert(false, "two ranges with unreachable sentinels"); + } + } else { + if constexpr (sized_input && sized_output) { + auto const count = ::std::min(ilast - ifirst, olast - ofirst); + detail::constexpr_memcpy(detail::to_address(ofirst), detail::to_address(ifirst), + (std::size_t)count); + return {ifirst + count, ofirst + count}; + } else if constexpr (sized_input) { + auto const count = ilast - ifirst; + detail::constexpr_memcpy(detail::to_address(ofirst), detail::to_address(ifirst), + (std::size_t)count); + return {ifirst + count, ofirst + count}; + } else if constexpr (sized_output) { + auto const count = olast - ofirst; + detail::constexpr_memcpy(detail::to_address(ofirst), detail::to_address(ifirst), + (std::size_t)count); + return {ifirst + count, ofirst + count}; + } else { + static_assert(false, "two ranges with unreachable sentinels"); + } + } + } +}; +inline constexpr auto uninitialized_memcpy__ = uninitialized_memcpy_or_memmove_fn{}; +inline constexpr auto uninitialized_memmove__ = uninitialized_memcpy_or_memmove_fn{}; + +template +struct [[nodiscard]] uninitialized_copy_or_move_fn final { + template + using result = in_out_result; + + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator> + requires(IsMove ? meta::iter_move_constructible + : meta::iter_copy_constructible) + FLUX_ALWAYS_INLINE + static constexpr OutputIterator operator()(InputIterator first, + InputSentinel last, + OutputIterator result) noexcept { + using OutType = meta::remove_ref_t>; + auto current = result; + for (; first != last; ++current, (void)++first) { + if constexpr (IsMove) { + detail::construct_at(detail::to_address(current), ::std::move(*first)); + } else { + detail::construct_at(detail::to_address(current), *first); + } + } + return current; + } + + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator, + meta::nothrow_sentinel_for OutputSentinel> + requires(IsMove ? meta::iter_move_constructible + : meta::iter_copy_constructible) + FLUX_ALWAYS_INLINE + static constexpr auto operator()(InputIterator ifirst, InputSentinel ilast, + OutputIterator ofirst, OutputSentinel olast) noexcept + -> result { + using OutType = meta::remove_ref_t>; + for (; ifirst != ilast && ofirst != olast; ++ofirst, (void)++ifirst) { + if constexpr (IsMove) { + detail::construct_at(detail::to_address(ofirst), ::std::move(*ifirst)); + } else { + detail::construct_at(detail::to_address(ofirst), *ifirst); + } + } + return {::std::move(ifirst), ::std::move(ofirst)}; + } +}; +inline constexpr auto uninitialized_copy__ = uninitialized_copy_or_move_fn{}; +inline constexpr auto uninitialized_move__ = uninitialized_copy_or_move_fn{}; + +template +struct [[nodiscard]] uninitialized_copy_or_move_n_fn final { + template + requires(IsMove ? meta::iter_move_constructible + : meta::iter_copy_constructible) + FLUX_ALWAYS_INLINE + static constexpr OutputIterator operator()(InputIterator first, + meta::iter_diff_t count, + OutputIterator result) noexcept { + using OutType = meta::remove_ref_t>; + if constexpr (meta::memcpyable) { + static_assert(IsMove ? meta::move_assignable + : meta::copy_assignable); + detail::constexpr_memmove(detail::to_address(result), detail::to_address(first), + (std::size_t)count); + return result + count; + } else { + auto current = result; + for (; count > 0; ++current, (void)++first, (void)--count) { + if constexpr (IsMove) { + detail::construct_at(detail::to_address(current), ::std::move(*first)); + } else { + detail::construct_at(detail::to_address(current), *first); + } + } + return current; + } + } +}; +inline constexpr auto uninitialized_copy_n__ = uninitialized_copy_or_move_n_fn{}; +inline constexpr auto uninitialized_move_n__ = uninitialized_copy_or_move_n_fn{}; +// clang-format on + +struct [[nodiscard]] uninitialized_copy_fn final { + template + using result = in_out_result; + + // clang-format off + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator> + requires meta::constructible_from, + meta::iter_ref_t > + static constexpr OutputIterator operator()(InputIterator first, + InputSentinel last, + OutputIterator result) noexcept { + if constexpr (meta::memcpyable) { + return uninitialized_memmove__(::std::move(first), ::std::move(last), + ::std::move(result)); + } else { + return uninitialized_copy__(::std::move(first), ::std::move(last), + ::std::move(result)); + } + } + + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator, + meta::nothrow_sentinel_for OutputSentinel> + requires meta::constructible_from, + meta::iter_ref_t > + static constexpr auto operator()(InputIterator ifirst, InputSentinel ilast, + OutputIterator ofirst, OutputSentinel olast) noexcept { + if constexpr (meta::memcpyable) { + return uninitialized_memmove__(::std::move(ifirst), ::std::move(ilast), + ::std::move(ofirst), ::std::move(olast)); + } else { + return uninitialized_copy__(::std::move(ifirst), ::std::move(ilast), + ::std::move(ofirst), ::std::move(olast)); + } + } + + template + requires meta::constructible_from, + meta::range_ref_t > + static constexpr auto operator()(InputRange&& in, OutputRange&& out) noexcept + -> result, meta::borrowed_iter_t> { + return operator()(::std::ranges::begin(in ), ::std::ranges::end(in ), + ::std::ranges::begin(out), ::std::ranges::end(out)); + } + // clang-format on +}; + +struct [[nodiscard]] uninitialized_copy_n_fn final { + // clang-format off + template + requires meta::constructible_from, + meta::iter_ref_t > + static constexpr OutputIterator operator()(InputIterator first, + meta::iter_diff_t count, + OutputIterator result) noexcept { + return uninitialized_copy_n__(::std::move(first), count, ::std::move(result)); + } + // clang-format on +}; + +struct [[nodiscard]] uninitialized_move_fn final { + template + using result = in_out_result; + + // clang-format off + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator> + requires meta::constructible_from, + meta::iter_rvref_t> + static constexpr OutputIterator operator()(InputIterator first, + InputSentinel last, + OutputIterator result) noexcept { + if constexpr (meta::memcpyable) { + return uninitialized_memmove__(::std::move(first), ::std::move(last), + ::std::move(result)); + } else { + return uninitialized_move__(::std::move(first), ::std::move(last), + ::std::move(result)); + } + } + + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator, + meta::nothrow_sentinel_for OutputSentinel> + requires meta::constructible_from, + meta::iter_rvref_t> + static constexpr auto operator()(InputIterator ifirst, InputSentinel ilast, + OutputIterator ofirst, OutputSentinel olast) noexcept { + if constexpr (meta::memcpyable) { + return uninitialized_memmove__(::std::move(ifirst), ::std::move(ilast), + ::std::move(ofirst), ::std::move(olast)); + } else { + return uninitialized_move__(::std::move(ifirst), ::std::move(ilast), + ::std::move(ofirst), ::std::move(olast)); + } + } + + template + requires meta::constructible_from, + meta::range_rvref_t> + static constexpr auto operator()(InputRange&& in, OutputRange&& out) noexcept + -> result, meta::borrowed_iter_t> { + return operator()(::std::ranges::begin(in ), ::std::ranges::end(in ), + ::std::ranges::begin(out), ::std::ranges::end(out)); + } + // clang-format on +}; + +struct [[nodiscard]] uninitialized_move_n_fn final { + template + requires meta::constructible_from, + meta::iter_rvref_t> + static constexpr OutputIterator operator()(InputIterator first, + meta::iter_diff_t count, + OutputIterator result) noexcept { + return uninitialized_move_n__(::std::move(first), count, ::std::move(result)); + } +}; + +struct [[nodiscard]] uninitialized_copy_no_overlap_fn final { + template + using result = in_out_result; + + // clang-format off + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator> + requires meta::constructible_from, + meta::iter_ref_t > + static constexpr OutputIterator operator()(InputIterator first, + InputSentinel last, + OutputIterator result) noexcept { + if constexpr (meta::memcpyable) { + return uninitialized_memcpy__(::std::move(first), ::std::move(last), + ::std::move(result)); + } else { + return uninitialized_copy__(::std::move(first), ::std::move(last), + ::std::move(result)); + } + } + + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator, + meta::nothrow_sentinel_for OutputSentinel> + requires meta::constructible_from, + meta::iter_ref_t > + static constexpr auto operator()(InputIterator ifirst, InputSentinel ilast, + OutputIterator ofirst, OutputSentinel olast) noexcept { + if constexpr (meta::memcpyable) { + return uninitialized_memcpy__(::std::move(ifirst), ::std::move(ilast), + ::std::move(ofirst), ::std::move(olast)); + } else { + return uninitialized_copy__(::std::move(ifirst), ::std::move(ilast), + ::std::move(ofirst), ::std::move(olast)); + } + } + + template + requires meta::constructible_from, + meta::range_ref_t > + static constexpr auto operator()(InputRange&& in, OutputRange&& out) noexcept + -> result, meta::borrowed_iter_t> { + return operator()(::std::ranges::begin(in ), ::std::ranges::end(in ), + ::std::ranges::begin(out), ::std::ranges::end(out)); + } + // clang-format on +}; + +struct [[nodiscard]] uninitialized_relocate_fn final { + template + using result = in_out_result; + + // clang-format off + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator> + requires meta::constructible_from, + meta::iter_rvref_t> + static constexpr OutputIterator operator()(InputIterator first, + InputSentinel last, + OutputIterator result) noexcept { + if constexpr (meta::memcpyable) { + return uninitialized_memmove__(::std::move(first), ::std::move(last), + ::std::move(result)); + } else { + auto current = result; + for (; first != last; ++current, (void)++first) { + relocate_at(detail::to_address(first), detail::to_address(current)); + } + return current; + } + } + + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator, + meta::nothrow_sentinel_for OutputSentinel> + requires meta::constructible_from, + meta::iter_rvref_t> + static constexpr auto operator()(InputIterator ifirst, InputSentinel ilast, + OutputIterator ofirst, OutputSentinel olast) noexcept { + using ReturnType = result; + if constexpr (meta::memcpyable) { + return uninitialized_memmove__(::std::move(ifirst), ::std::move(ilast), + ::std::move(ofirst), ::std::move(olast)); + } else { + for (; ifirst != ilast && ofirst != olast; ++ofirst, (void)++ifirst) { + relocate_at(detail::to_address(ifirst), detail::to_address(ofirst)); + } + return ReturnType{::std::move(ifirst), ::std::move(ofirst)}; + } + } + + template + requires meta::constructible_from, + meta::range_rvref_t> + static constexpr auto operator()(InputRange&& in, OutputRange&& out) noexcept + -> result, meta::borrowed_iter_t> { + return operator()(::std::ranges::begin(in ), ::std::ranges::end(in ), + ::std::ranges::begin(out), ::std::ranges::end(out)); + } + // clang-format on +}; + +struct [[nodiscard]] uninitialized_relocate_no_overlap_fn final { + template + using result = in_out_result; + + // clang-format off + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator> + requires meta::constructible_from, + meta::iter_rvref_t> + static constexpr OutputIterator operator()(InputIterator first, + InputSentinel last, + OutputIterator result) noexcept { + if constexpr (meta::memcpyable) { + return uninitialized_memcpy__(::std::move(first), ::std::move(last), + ::std::move(result)); + } else { + auto current = result; + for (; first != last; ++current, (void)++first) { + relocate_at(detail::to_address(first), detail::to_address(current)); + } + return current; + } + } + + template InputSentinel, + meta::nothrow_forward_iterator OutputIterator, + meta::nothrow_sentinel_for OutputSentinel> + requires meta::constructible_from, + meta::iter_rvref_t> + static constexpr auto operator()(InputIterator ifirst, InputSentinel ilast, + OutputIterator ofirst, OutputSentinel olast) noexcept { + using ReturnType = result; + if constexpr (meta::memcpyable) { + return uninitialized_memcpy__(::std::move(ifirst), ::std::move(ilast), + ::std::move(ofirst), ::std::move(olast)); + } else { + for (; ifirst != ilast && ofirst != olast; ++ofirst, (void)++ifirst) { + relocate_at(detail::to_address(ifirst), detail::to_address(ofirst)); + } + return ReturnType{::std::move(ifirst), ::std::move(ofirst)}; + } + } + + template + requires meta::constructible_from, + meta::range_rvref_t> + static constexpr auto operator()(InputRange&& in, OutputRange&& out) noexcept + -> result, meta::borrowed_iter_t> { + return operator()(::std::ranges::begin(in ), ::std::ranges::end(in ), + ::std::ranges::begin(out), ::std::ranges::end(out)); + } + // clang-format on +}; + +} // namespace detail + +namespace ranges { +// clang-format off +inline constexpr auto uninitialized_default_construct = detail::uninitialized_default_construct_fn{}; +inline constexpr auto uninitialized_default_construct_n = detail::uninitialized_default_construct_n_fn{}; +inline constexpr auto uninitialized_value_construct = detail::uninitialized_value_construct_fn{}; +inline constexpr auto uninitialized_value_construct_n = detail::uninitialized_value_construct_n_fn{}; +inline constexpr auto uninitialized_copy = detail::uninitialized_copy_fn{}; +inline constexpr auto uninitialized_copy_n = detail::uninitialized_copy_n_fn{}; +inline constexpr auto uninitialized_move = detail::uninitialized_move_fn{}; +inline constexpr auto uninitialized_move_n = detail::uninitialized_move_n_fn{}; +inline constexpr auto uninitialized_copy_no_overlap = detail::uninitialized_copy_no_overlap_fn{}; +inline constexpr auto uninitialized_relocate = detail::uninitialized_relocate_fn{}; +inline constexpr auto uninitialized_relocate_no_overlap = detail::uninitialized_relocate_no_overlap_fn{}; +// clang-format on +} // namespace ranges + +} // namespace flux::fou \ No newline at end of file diff --git a/flux-foundation/flux/foundation/memory/uninitialized_storage-test.cpp b/flux-foundation/flux/foundation/memory/uninitialized_storage-test.cpp new file mode 100644 index 0000000..dd5cbf6 --- /dev/null +++ b/flux-foundation/flux/foundation/memory/uninitialized_storage-test.cpp @@ -0,0 +1,89 @@ +#include + +#include + +struct dummy { + int value{}; + + constexpr dummy() noexcept {} + constexpr dummy(int v) noexcept : value{v} {} + + constexpr dummy(dummy const&) noexcept {} + constexpr dummy(dummy&&) noexcept {} + + constexpr dummy& operator=(dummy const&) noexcept { + return *this; + } + constexpr dummy& operator=(dummy&&) noexcept { + return *this; + } + + constexpr ~dummy() {} +}; + +using namespace flux; +static_assert(not meta::trivial>); +static_assert(meta::standard_layout>); +static_assert(not meta::trivially_default_constructible>); +static_assert(meta::trivially_copyable>); +static_assert(meta::trivially_copy_constructible>); +static_assert(meta::trivially_move_constructible>); +static_assert(meta::trivially_copy_assignable>); +static_assert(meta::trivially_move_assignable>); +static_assert(meta::trivially_destructible>); + +static_assert(not meta::trivial>); +static_assert(meta::standard_layout>); +static_assert(not meta::trivially_default_constructible>); +static_assert(not meta::trivially_copyable>); +static_assert(not meta::trivially_copy_constructible>); +static_assert(not meta::trivially_move_constructible>); +static_assert(not meta::trivially_copy_assignable>); +static_assert(not meta::trivially_move_assignable>); +static_assert(not meta::trivially_destructible>); + +consteval int storage_construct() noexcept { + fou::uninitialized_storage storage; + static_assert(sizeof(storage) == sizeof(dummy)); + + fou::detail::construct_at(storage.data(), 10); + int value = storage.data()->value; + fou::detail::destroy_at(storage.data()); + return value; +} + +consteval int storage_accessors() noexcept { + fou::uninitialized_storage storage; + static_assert(sizeof(storage) == sizeof(dummy)); + + fou::detail::construct_at(get(storage), 20); + int value = get(storage).value; + fou::detail::destroy_at(get(storage)); + return value; +} + +TEST_CASE("fou::uninitialized_storage", "[flux-memory/uninitialized_storage.hpp]") { + SECTION("evaluate in constant expression") { + STATIC_REQUIRE(10 == storage_construct()); + STATIC_REQUIRE(20 == storage_accessors()); + } + + SECTION("construct from fundamental type") { + fou::uninitialized_storage storage; + + int* value = fou::detail::construct_at(storage.data(), 5); + CHECK(5 == *value); + CHECK(get(*value) == get(storage)); + CHECK(*get(*value) == *get(storage)); + fou::detail::destroy_at(storage.data()); + } + + SECTION("construct from user-defined type") { + fou::uninitialized_storage storage; + + fou::detail::construct_at(storage.data(), 8); + CHECK(8 == get(storage).value); + CHECK(8 == get(storage)->value); + fou::detail::destroy_at(storage.data()); + } +} \ No newline at end of file diff --git a/flux-foundation/flux/foundation/memory/uninitialized_storage.hpp b/flux-foundation/flux/foundation/memory/uninitialized_storage.hpp new file mode 100644 index 0000000..a5eecc6 --- /dev/null +++ b/flux-foundation/flux/foundation/memory/uninitialized_storage.hpp @@ -0,0 +1,98 @@ +#pragma once + +namespace flux::fou { + +struct nontrivial_type { + // This default constructor is user-provided to avoid + // zero-initialization when objects are value-initialized. + constexpr nontrivial_type() noexcept {} +}; + +// clang-format off +template +union [[nodiscard, clang::trivial_abi]] uninitialized_storage final { + using value_type = T; + + value_type value; + nontrivial_type _; + + constexpr uninitialized_storage() : _{} {} + + constexpr uninitialized_storage(uninitialized_storage const& other) noexcept + requires meta::trivially_copy_constructible + = default; + constexpr uninitialized_storage(uninitialized_storage const& other) noexcept + : value{other.value} {} + + constexpr uninitialized_storage(uninitialized_storage&& other) noexcept + requires meta::trivially_move_constructible + = default; + constexpr uninitialized_storage(uninitialized_storage&& other) noexcept + : value{std::move(other.value)} {} + + constexpr uninitialized_storage& operator=(uninitialized_storage const&) noexcept + requires meta::trivially_copy_assignable + = default; + constexpr uninitialized_storage& operator=(uninitialized_storage const&) noexcept = delete; + + constexpr uninitialized_storage& operator=(uninitialized_storage&&) noexcept + requires meta::trivially_move_assignable + = default; + constexpr uninitialized_storage& operator=(uninitialized_storage&&) noexcept = delete; + + constexpr ~uninitialized_storage() requires meta::trivially_destructible = default; + constexpr ~uninitialized_storage() {} + + [[nodiscard]] constexpr value_type* data() noexcept { + return addressof(value); + } + [[nodiscard]] constexpr value_type const* data() const noexcept { + return addressof(value); + } +}; +// clang-format on + +template +constexpr bool operator==(uninitialized_storage const& lhs, + uninitialized_storage const& rhs) noexcept { + return lhs.value == rhs.value; +} + +} // namespace flux::fou + +namespace flux { + +// clang-format off +template +constexpr Reference get(fou::uninitialized_storage& storage) noexcept { + return *storage.data(); +} +template +constexpr Reference get(fou::uninitialized_storage const& storage) noexcept { + return *storage.data(); +} +template +constexpr T&& get(T&& value) noexcept { + return value; +} + +template +constexpr Pointer get(fou::uninitialized_storage& storage) noexcept { + return storage.data(); +} +template +constexpr Pointer get(fou::uninitialized_storage const& storage) noexcept { + return storage.data(); +} +template +constexpr T* get(T& value) noexcept { + return fou::addressof(value); +} +// clang-format on + +// Optional, since types with trivial lifetimes will not be wrapped by an uninitialized storage. +template +using optional_uninitialized_storage = + meta::condition, T, fou::uninitialized_storage>; + +} // namespace flux \ No newline at end of file diff --git a/flux-foundation/flux/foundation/utility.hpp b/flux-foundation/flux/foundation/utility.hpp index 27d8c87..cc814b0 100644 --- a/flux-foundation/flux/foundation/utility.hpp +++ b/flux-foundation/flux/foundation/utility.hpp @@ -1,6 +1,10 @@ #pragma once #include +#include +#include +#include #include +#include #include #include \ No newline at end of file diff --git a/flux-foundation/flux/foundation/utility/advance.hpp b/flux-foundation/flux/foundation/utility/advance.hpp new file mode 100644 index 0000000..2242790 --- /dev/null +++ b/flux-foundation/flux/foundation/utility/advance.hpp @@ -0,0 +1,118 @@ +#pragma once + +namespace flux::fou { + +namespace detail { + +struct [[nodiscard]] advance_fn final { + // Preconditions: If `I` does not model `bidirectional_iterator`, `n` is not negative. + template + constexpr void operator()(I& i, meta::iter_diff_t n) const noexcept { + // If `I` models `random_access_iterator`, equivalent to `i += n`. + if constexpr (meta::random_access_iterator) { + i += n; + } else if constexpr (meta::bidirectional_iterator) { + // Otherwise, if `n` is non-negative, increments `i` by `n`. + advance_forward(i, n); + // Otherwise, decrements `i` by `-n`. + advance_backward(i, n); + } else { + FLUX_ASSERT(n >= 0, "negative advance of non-bidirectional iterator"); + + // Otherwise, if `n` is non-negative, increments `i` by `n`. + advance_forward(i, n); + } + } + + // Preconditions: Either `assignable_from || sized_sentinel_for` is modeled, or [i, + // bound) denotes a range. + template Sentinel> + constexpr void operator()(I& i, Sentinel bound) const noexcept { + // If `I` and `S` model `assignable_from`, equivalent to `i = std::move(bound)`. + if constexpr (meta::assignable_from) { + i = static_cast(bound); + } else if constexpr (meta::sized_sentinel_for) { + // Otherwise, if `S` and `I` model `sized_sentinel_for`, equivalent to + // `ranges::advance(i, bound - i)`. + operator()(i, bound - i); + } else { + // Otherwise, while `bool(i != bound)` is true, increments `i`. + while (i != bound) { + ++i; + } + } + } + + // Preconditions: + // * If `n > 0`, [i, bound) denotes a range. + // * If `n == 0`, [i, bound) or [bound, i) denotes a range. + // * If `n < 0`, [bound, i) denotes a range, `I` models `bidirectional_iterator`, and `I` and + // `S` model `same_as`. + // Returns: `n - M`, where `M` is the difference between the ending and starting position. + template Sentinel> + constexpr auto operator()(I& i, meta::iter_diff_t n, Sentinel bound) const noexcept { + // If `S` and `I` model `sized_sentinel_for`: + if constexpr (meta::sized_sentinel_for) { + // If |n| >= |bound - i|, equivalent to `ranges::advance(i, bound)`. + const meta::iter_diff_t delta = bound - i; + if ((n < 0 && n <= delta) || (n > 0 && n >= delta)) { + operator()(i, bound); + return n - delta; + } + + // Otherwise, equivalent to `ranges::advance(i, n)`. + operator()(i, n); + return meta::iter_diff_t(0); + } else { + // Otherwise, while `bool(i != bound_sentinel)` is true, decrements `i` but at most `-n` + // times. + if constexpr (meta::bidirectional_iterator && meta::same_as) { + for (; n < 0 && i != bound; ++n) { + --i; + } + } else { + FLUX_ASSERT(n >= 0, "negative advance of non-bidirectional iterator"); + } + + // Otherwise, if `n` is non-negative, while `bool(i != bound)` is true, increments `i` + // but at most `n` times. + for (; n > 0 && i != bound; --n) { + ++i; + } + return n; + } + } + +private: + // clang-format off + template +#if __has_cpp_attribute(__gnu__::__always_inline__) + [[__gnu__::__always_inline__]] +#endif + static constexpr void advance_forward(I& i, meta::iter_diff_t n) noexcept { + while (n > 0) { + --n; + ++i; + } + } + + template +#if __has_cpp_attribute(__gnu__::__always_inline__) + [[__gnu__::__always_inline__]] +#endif + static constexpr void advance_backward(I& i, meta::iter_diff_t n) noexcept { + while (n < 0) { + ++n; + --i; + } + } + // clang-format on +}; + +} // namespace detail + +namespace ranges { +inline constexpr auto advance = detail::advance_fn{}; +} // namespace ranges + +} // namespace flux::fou \ No newline at end of file diff --git a/flux-foundation/flux/foundation/utility/in_out_result.hpp b/flux-foundation/flux/foundation/utility/in_out_result.hpp new file mode 100644 index 0000000..7f291c0 --- /dev/null +++ b/flux-foundation/flux/foundation/utility/in_out_result.hpp @@ -0,0 +1,27 @@ +#pragma once + +namespace flux::fou { + +// clang-format off +template +struct [[nodiscard]] in_out_result final { + FLUX_NO_UNIQUE_ADDRESS In in; + FLUX_NO_UNIQUE_ADDRESS Out out; + + template + requires meta::convertible_to and + meta::convertible_to + constexpr operator in_out_result() const& noexcept { + return {in, out}; + } + + template + requires meta::convertible_to and + meta::convertible_to + constexpr operator in_out_result() && noexcept { + return {::std::move(in), ::std::move(out)}; + } +}; +// clang-format on + +} // namespace flux::fou \ No newline at end of file diff --git a/flux-foundation/flux/foundation/utility/is_pointer_in_range.hpp b/flux-foundation/flux/foundation/utility/is_pointer_in_range.hpp new file mode 100644 index 0000000..8639329 --- /dev/null +++ b/flux-foundation/flux/foundation/utility/is_pointer_in_range.hpp @@ -0,0 +1,51 @@ +#pragma once +#include + +namespace flux::fou { + +template +concept is_less_comparable = requires(T a, U b) { + { a < b }; +}; + +template +constexpr FLUX_NO_SANITIZE("address") bool is_valid_range(T const* first, T const* last) noexcept { + if consteval { + // If this is not a constant during constant evaluation, that is because `first` and `last` + // are not part of the same allocation. If they are part of the same allocation, we must + // still make sure they are ordered properly. + return __builtin_constant_p(first <= last) && first <= last; + } + return !::std::less<>()(last, first); +} + +template + requires is_less_comparable +constexpr FLUX_NO_SANITIZE("address") bool is_pointer_in_range(T const* begin, T const* end, + U const* ptr) noexcept { + FLUX_ASSERT(is_valid_range(begin, end), "[__begin, __end) is not a valid range"); + + if consteval { + // If this is not a constant during constant evaluation we know that `ptr` is not + // part of the allocation where `[begin, end)` is. + if (!__builtin_constant_p(begin <= ptr && ptr < end)) { + return false; + } + } + return !::std::less<>()(ptr, begin) && ::std::less<>()(ptr, end); +} + +template + requires(not is_less_comparable) +constexpr FLUX_NO_SANITIZE("address") bool is_pointer_in_range(T const* begin, T const* end, + U const* ptr) noexcept { + if consteval { + return false; + } + // clang-format off + return reinterpret_cast(begin) <= reinterpret_cast(ptr) && + reinterpret_cast(ptr) < reinterpret_cast(end); + // clang-format on +} + +} // namespace flux::fou \ No newline at end of file diff --git a/flux-foundation/flux/foundation/utility/next.hpp b/flux-foundation/flux/foundation/utility/next.hpp new file mode 100644 index 0000000..2f35d07 --- /dev/null +++ b/flux-foundation/flux/foundation/utility/next.hpp @@ -0,0 +1,41 @@ +#pragma once + +namespace flux::fou { + +namespace detail { + +struct [[nodiscard]] next_fn final { + // clang-format off + template + constexpr I operator()(I& i) const noexcept { + ++i; + return i; + } + // clang-format on + + template + constexpr I operator()(I& i, meta::iter_diff_t n) const noexcept { + ranges::advance(i, n); + return i; + } + + template Sentinel> + constexpr I operator()(I& i, Sentinel bound) const noexcept { + ranges::advance(i, bound); + return i; + } + + template Sentinel> + constexpr I operator()(I& i, meta::iter_diff_t n, Sentinel bound) const noexcept { + ranges::advance(i, n, bound); + return i; + } +}; + +} // namespace detail + +namespace ranges { +inline constexpr auto next = detail::next_fn{}; +} // namespace ranges + +} // namespace flux::fou \ No newline at end of file diff --git a/flux-foundation/flux/foundation/utility/terminate.hpp b/flux-foundation/flux/foundation/utility/terminate.hpp index 331013f..7b6735a 100644 --- a/flux-foundation/flux/foundation/utility/terminate.hpp +++ b/flux-foundation/flux/foundation/utility/terminate.hpp @@ -12,7 +12,7 @@ namespace flux::fou { #elif __has_builtin(__builtin_abort) __builtin_abort(); #else - std::abort(); + ::std::abort(); #endif } diff --git a/flux-meta/flux/meta.hpp b/flux-meta/flux/meta.hpp index 6d3df02..64c4529 100644 --- a/flux-meta/flux/meta.hpp +++ b/flux-meta/flux/meta.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include diff --git a/flux-meta/flux/meta/concepts.hpp b/flux-meta/flux/meta/concepts.hpp index 6aa1103..8cfcdb5 100644 --- a/flux-meta/flux/meta/concepts.hpp +++ b/flux-meta/flux/meta/concepts.hpp @@ -2,23 +2,33 @@ namespace flux::meta { -using std::common_reference_with; -using std::constructible_from; -using std::convertible_to; -using std::default_initializable; -using std::derived_from; -using std::forward_iterator; -using std::input_iterator; -using std::integral; -using std::movable; -using std::predicate; -using std::random_access_iterator; -using std::same_as; - -template +using ::std::assignable_from; +using ::std::bidirectional_iterator; +using ::std::common_reference_with; +using ::std::constructible_from; +using ::std::contiguous_iterator; +using ::std::convertible_to; +using ::std::default_initializable; +using ::std::derived_from; +using ::std::forward_iterator; +using ::std::indirectly_copyable; +using ::std::indirectly_movable; +using ::std::input_iterator; +using ::std::input_or_output_iterator; +using ::std::integral; +using ::std::movable; +using ::std::predicate; +using ::std::random_access_iterator; +using ::std::same_as; +using ::std::sentinel_for; +using ::std::sized_sentinel_for; +using ::std::ranges::input_range; +using ::std::ranges::range; + +template concept same_size = requires { requires sizeof(T) == Size; }; -template +template concept same_align = requires { requires alignof(T) == Alignment; }; template @@ -27,62 +37,78 @@ concept distinct = are_distinct_v; template concept contains = is_contains_v; +template +concept assignable = ::std::is_assignable_v; + +template +concept constructible = ::std::is_constructible_v; + +template +concept nothrow_constructible = ::std::is_nothrow_constructible_v; template -concept trivial = std::is_trivial_v; +concept nothrow_destructible = ::std::is_nothrow_destructible_v; template -concept standard_layout = std::is_standard_layout_v; +concept trivial = ::std::is_trivial_v; template -concept default_constructible = std::is_default_constructible_v; +concept standard_layout = ::std::is_standard_layout_v; template -concept trivially_constructible = std::is_trivially_constructible_v; +concept default_constructible = ::std::is_default_constructible_v; template -concept trivially_default_constructible = std::is_trivially_default_constructible_v; +concept trivially_constructible = ::std::is_trivially_constructible_v; -template -concept trivially_copyable = std::is_trivially_copyable_v; +template +concept trivially_default_constructible = ::std::is_trivially_default_constructible_v; template -concept trivially_copy_constructible = std::is_trivially_copy_constructible_v; +concept trivially_copyable = ::std::is_trivially_copyable_v; + template -concept trivially_move_constructible = std::is_trivially_move_constructible_v; +concept trivially_copy_constructible = ::std::is_trivially_copy_constructible_v; +template +concept trivially_move_constructible = ::std::is_trivially_move_constructible_v; template -concept copy_assignable = std::is_copy_assignable_v; +concept copy_assignable = ::std::is_copy_assignable_v; template -concept move_assignable = std::is_move_assignable_v; +concept move_assignable = ::std::is_move_assignable_v; template -concept trivially_copy_assignable = std::is_trivially_copy_assignable_v; +concept trivially_copy_assignable = ::std::is_trivially_copy_assignable_v; template -concept trivially_move_assignable = std::is_trivially_move_assignable_v; +concept trivially_move_assignable = ::std::is_trivially_move_assignable_v; template -concept trivially_destructible = std::is_trivially_destructible_v; +concept trivially_destructible = ::std::is_trivially_destructible_v; template concept not_trivially_destructible = not trivially_destructible; template -concept destructible = std::is_destructible_v; +concept destructible = ::std::is_destructible_v; template concept has_trivial_lifetime = trivially_default_constructible and trivially_destructible; template -concept nothrow_copy_constructible = std::is_nothrow_copy_constructible_v; +concept nothrow_copy_constructible = ::std::is_nothrow_copy_constructible_v; template -concept nothrow_move_constructible = std::is_nothrow_move_constructible_v; +concept nothrow_move_constructible = ::std::is_nothrow_move_constructible_v; template -concept copy_constructible = std::copy_constructible; +concept nothrow_copy_assignable = ::std::is_nothrow_copy_assignable_v; template -concept move_constructible = std::move_constructible; +concept nothrow_move_assignable = ::std::is_nothrow_move_assignable_v; + +template +concept copy_constructible = ::std::copy_constructible; +template +concept move_constructible = ::std::move_constructible; template concept trivially_lexicographically_comparable = - same_as && sizeof(T) == 1 && std::is_unsigned_v; + same_as, remove_cv_t> and sizeof(T) == 1 and ::std::is_unsigned_v; template concept trivially_equality_comparable = is_trivially_equality_comparable_v; @@ -98,43 +124,86 @@ concept boolean_testable = detail::boolean_testable_impl and requires(T&& t) }; template -concept abstract = std::is_abstract_v; +concept abstract = ::std::is_abstract_v; template concept not_abstract = not abstract; template concept empty = meta::is_empty_v; template -concept object = std::is_object_v; +concept object = ::std::is_object_v; template -concept function = std::is_function_v; +concept function = ::std::is_function_v; template -concept member_function = std::is_member_function_pointer_v; +concept member_function = ::std::is_member_function_pointer_v; template -concept reference = std::is_reference_v; +concept reference = ::std::is_reference_v; template concept not_reference = not reference; template -concept pointer = std::is_pointer_v; +concept pointer = ::std::is_pointer_v; template concept not_pointer = not pointer; template -concept non_cv = same_as, T>; +concept non_cv = same_as<::std::remove_cv_t, T>; template -concept integer = std::integral and not same_as, bool>; +concept integer = ::std::integral and not same_as, bool>; template -concept unsigned_integer = std::unsigned_integral and not same_as, bool>; +concept unsigned_integer = ::std::unsigned_integral and not same_as, bool>; -template -concept not_volatile = std::is_volatile_v and std::is_volatile_v; +template +concept not_volatile = (not ::std::is_volatile_v and ...); template -concept underlying_constructible = std::conjunction_v...>; +concept underlying_constructible = ::std::conjunction_v...>; template -concept only_constructible = requires(Args&&... args) { new T{std::forward(args)...}; }; +concept only_constructible = requires(Args&&... args) { new T{::std::forward(args)...}; }; + +// This concept ensures that uninitialized algorithms can construct an object +// at the address pointed-to by the iterator, which requires an lvalue. +template +concept nothrow_input_iterator = + input_iterator and is_lvalue_reference_v> and + same_as>, remove_ref_t>> and + same_as>, iter_value_t>; + +template +concept nothrow_sentinel_for = sentinel_for; + +template +concept nothrow_forward_iterator = + nothrow_input_iterator and forward_iterator and + nothrow_sentinel_for; + +template +concept nothrow_input_range = range and nothrow_input_iterator> and + nothrow_sentinel_for, iterator_t>; + +template +concept nothrow_forward_range = + nothrow_input_range and nothrow_forward_iterator>; + +template +concept use_memset_value_construct = contiguous_iterator and + trivially_copyable> and + not_volatile>>; + +template +concept has_to_address = requires(T const p) { p.to_address(); } or + requires(T const p) { ::std::pointer_traits::to_address(p); }; +template +concept has_arrow_operator = requires(T const p) { p.operator->(); }; + +template +concept iter_move_constructible = + constructible_from, iter_rvref_t>; + +template +concept iter_copy_constructible = + constructible_from, iter_ref_t>; } // namespace flux::meta \ No newline at end of file diff --git a/flux-meta/flux/meta/datasizeof.hpp b/flux-meta/flux/meta/datasizeof.hpp new file mode 100644 index 0000000..b43e910 --- /dev/null +++ b/flux-meta/flux/meta/datasizeof.hpp @@ -0,0 +1,55 @@ +#pragma once +#include + +#include + +// This trait provides the size of a type excluding any tail padding. +// +// It is useful in contexts where performing an operation using the full size of the class +// (including padding) may have unintended side effects, such as overwriting a derived class' member +// when writing the tail padding of a class through a pointer-to-base. + +namespace flux::meta { + +// clang-format off +#if __has_extension(datasizeof) +template +inline constexpr auto data_size_of = __datasizeof(T); +#else +namespace detail { +// `tail_padding` is sometimes non-standard layout. Using `offsetof` is UB in that case, but GCC and Clang allow +// the use as an extension. +FLUX_DISABLE_WARNING_BLOCK(-Winvalid-offsetof, +template +inline constexpr auto data_size_impl = [] constexpr -> ::std::size_t { + if constexpr (is_empty_v) { + return 1; + } + + if constexpr (config::has_no_unique_address) { + struct tail_padding { + FLUX_NO_UNIQUE_ADDRESS T value; + char first_padding_byte; + }; + return offsetof(tail_padding, first_padding_byte); + } else { + if constexpr (is_class_v and !is_final_v) { + struct tail_padding : T { + char first_padding_byte; + }; + return offsetof(tail_padding, first_padding_byte); + } else { + struct tail_padding { + T value; + char first_padding_byte; + }; + return offsetof(tail_padding, first_padding_byte); + } + } +}();) +} // namespace detail +template +inline constexpr auto data_size_of = detail::data_size_impl; +// clang-format on +#endif +} // namespace flux::meta \ No newline at end of file diff --git a/flux-meta/flux/meta/declval.hpp b/flux-meta/flux/meta/declval.hpp index 227b33a..4492687 100644 --- a/flux-meta/flux/meta/declval.hpp +++ b/flux-meta/flux/meta/declval.hpp @@ -10,7 +10,7 @@ template constexpr auto declval() noexcept -> T; template - requires(std::is_function_v> or std::is_array_v>) + requires(::std::is_function_v<::std::remove_cv_t> or ::std::is_array_v<::std::remove_cv_t>) constexpr auto declval() noexcept -> T&&; // clang-format on diff --git a/flux-meta/flux/meta/memcpyable.hpp b/flux-meta/flux/meta/memcpyable.hpp index a332240..45b13ae 100644 --- a/flux-meta/flux/meta/memcpyable.hpp +++ b/flux-meta/flux/meta/memcpyable.hpp @@ -5,8 +5,8 @@ namespace flux::meta { // clang-format off template struct [[nodiscard]] is_memcpyable final { - using T = std::iter_value_t; - using U = decltype(std::ranges::iter_move(declval())); + using T = iter_value_t; + using U = decltype(::std::ranges::iter_move(declval())); static constexpr bool value = same_as> and trivially_copyable; }; @@ -15,16 +15,17 @@ template inline constexpr bool is_memcpyable_v = is_memcpyable::value; template -concept addressable = pointer or requires(T const x) { x.operator->(); }; +concept addressable = pointer or has_to_address or has_arrow_operator; template -concept contiguous = std::contiguous_iterator - and std::contiguous_iterator; +concept contiguous = contiguous_iterator + and contiguous_iterator; template -concept memcpyable = std::contiguous_iterator - and addressable - and is_memcpyable_v ; +concept memcpyable = contiguous + and addressable + and is_memcpyable_v + and not_volatile ; // clang-format on } // namespace flux::meta \ No newline at end of file diff --git a/flux-meta/flux/meta/relocatable.hpp b/flux-meta/flux/meta/relocatable.hpp index 3dec90e..dca866b 100644 --- a/flux-meta/flux/meta/relocatable.hpp +++ b/flux-meta/flux/meta/relocatable.hpp @@ -8,11 +8,13 @@ concept relocatable = move_constructible and destructible; template concept trivially_relocatable = trivially_copyable; +// clang-format on template -concept same_trivially_relocatable = is_same_uncvref_v - and trivially_relocatable> - and not_volatile; -// clang-format on +concept same_trivially_relocatable = + not_volatile and is_same_uncvref_v and trivially_relocatable>; + +template +concept relocatable_from = nothrow_constructible and nothrow_destructible; } // namespace flux::meta \ No newline at end of file diff --git a/flux-meta/flux/meta/type_traits.hpp b/flux-meta/flux/meta/type_traits.hpp index f190acc..87b5e1e 100644 --- a/flux-meta/flux/meta/type_traits.hpp +++ b/flux-meta/flux/meta/type_traits.hpp @@ -2,40 +2,61 @@ namespace flux::meta { +using ::std::add_pointer_t; +using ::std::apply; +using ::std::bool_constant; +using ::std::contiguous_iterator_tag; +using ::std::decay_t; +using ::std::false_type; +using ::std::iter_value_t; +using ::std::random_access_iterator_tag; +using ::std::remove_all_extents_t; +using ::std::remove_cv_t; +using ::std::remove_cvref_t; +using ::std::true_type; +using ::std::tuple; +using ::std::void_t; +using ::std::ranges::borrowed_iterator_t; +using ::std::ranges::iterator_t; +using ::std::ranges::range_value_t; +using ::std::ranges::sentinel_t; + // clang-format off template -using remove_ref_t = std::remove_reference_t; +using remove_ref_t = ::std::remove_reference_t; template -using common_type = std::common_type_t; +using common_type = ::std::common_type_t; -using std::bool_constant; -using std::true_type; -using std::false_type; -using std::decay_t; -using std::remove_cv_t; -using std::remove_cvref_t; -using std::remove_all_extents_t; -using std::add_pointer_t; -using std::void_t; -using std::tuple; -using std::apply; -using std::random_access_iterator_tag; -using std::contiguous_iterator_tag; +template +using iter_diff_t = ::std::iter_difference_t; +template +using iter_ref_t = ::std::iter_reference_t; +template +using iter_rvref_t = ::std::iter_rvalue_reference_t; template -using iter_diff_t = std::iter_difference_t; +using range_ref_t = ::std::ranges::range_reference_t; +template +using range_rvref_t = ::std::ranges::range_rvalue_reference_t; template -inline constexpr bool is_class_v = std::is_class_v; +using borrowed_iter_t = ::std::ranges::borrowed_iterator_t; + +template +inline constexpr bool is_array_v = ::std::is_array_v; +template +inline constexpr bool is_class_v = ::std::is_class_v; +template +inline constexpr bool is_empty_v = ::std::is_empty_v; template -inline constexpr bool is_empty_v = std::is_empty_v; +inline constexpr bool is_final_v = ::std::is_final_v; template -inline constexpr bool is_lvalue_reference_v = std::is_lvalue_reference_v; +inline constexpr bool is_lvalue_reference_v = ::std::is_lvalue_reference_v; template struct [[nodiscard]] are_distinct - : std::conjunction>..., are_distinct> {}; + : ::std::conjunction<::std::negation<::std::is_same>..., are_distinct> {}; template struct [[nodiscard]] are_distinct : true_type {}; @@ -43,7 +64,7 @@ template inline constexpr bool are_distinct_v = are_distinct::value; template -struct [[nodiscard]] is_contains : std::disjunction...> {}; +struct [[nodiscard]] is_contains : ::std::disjunction<::std::is_same...> {}; template inline constexpr bool is_contains_v = is_contains::value; @@ -81,8 +102,8 @@ inline constexpr bool is_specialization_v = is_specialization::valu template struct [[nodiscard]] remove_all_pointers - : condition, remove_all_pointers>, - std::type_identity> {}; + : condition<::std::is_pointer_v, remove_all_pointers<::std::remove_pointer_t>, + ::std::type_identity> {}; template using remove_all_pointers_t = typename remove_all_pointers::type; @@ -92,7 +113,7 @@ struct [[nodiscard]] is_equality_comparable : false_type {}; template struct [[nodiscard]] is_equality_comparable() == declval())>> + ::std::void_t() == declval())>> : true_type {}; template @@ -104,10 +125,10 @@ struct [[nodiscard]] is_trivially_equality_comparable_impl : false_type {}; template struct [[nodiscard]] is_trivially_equality_comparable_impl #if __has_builtin(__is_trivially_equality_comparable) - : std::bool_constant<__is_trivially_equality_comparable(T) and + : ::std::bool_constant<__is_trivially_equality_comparable(T) and is_equality_comparable_v> #else - : std::is_integral + : ::std::is_integral #endif { }; @@ -117,9 +138,9 @@ struct [[nodiscard]] is_trivially_equality_comparable_impl : true_type { template struct [[nodiscard]] is_trivially_equality_comparable_impl - : std::bool_constant and - (std::is_same_v, remove_cv_t> or - std::is_void_v or std::is_void_v)> {}; + : ::std::bool_constant and + (::std::is_same_v, remove_cv_t> or + ::std::is_void_v or ::std::is_void_v)> {}; template using is_trivially_equality_comparable = @@ -130,7 +151,7 @@ inline constexpr bool is_trivially_equality_comparable_v = is_trivially_equality_comparable::value; template - requires std::conjunction_v...> + requires ::std::conjunction_v<::std::is_same...> struct [[nodiscard]] enforce_same { using type = First; }; @@ -143,7 +164,7 @@ struct [[nodiscard]] deduce; template struct [[nodiscard]] deduce { - using type = std::invoke_result_t; + using type = ::std::invoke_result_t; }; template using deduce_t = typename deduce::type; @@ -156,7 +177,7 @@ template using deref_t = typename deref::type; template -using is_same_as = std::bool_constant<__is_same(T, U)>; +using is_same_as = ::std::bool_constant<__is_same(T, U)>; template struct [[nodiscard]] is_same_uncvref : is_same_as, remove_cvref_t> {}; @@ -165,7 +186,7 @@ inline constexpr bool is_same_uncvref_v = is_same_uncvref::value; template struct [[nodiscard]] is_constructible_from - : std::bool_constant && std::is_constructible_v> {}; + : ::std::bool_constant<::std::is_nothrow_destructible_v && ::std::is_constructible_v> {}; template struct [[nodiscard]] template_parameter;