diff --git a/Doxyfile b/Doxyfile index e6f0ac5f..ad5e1e1b 100644 --- a/Doxyfile +++ b/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = OE-Lib # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2.4 +PROJECT_NUMBER = 3.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/README.md b/README.md index de4c6504..6f64232d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Obscure Efficient Library (v2) +# Obscure Efficient Library A cross-platform, very fast substitute for C++ std::vector (and std::copy) with a range-based interface. @@ -6,10 +6,10 @@ Features relocation optimizations similar to [Folly fbvector](https://github.com The library is distributed under the Boost Software License, and is header only, just include and go. -C++14 is required. Supported compilers: -* Visual Studio 2017 and later -* GCC 5 and later -* Clang (tested regularly, but minimum version is unknown) +C++17 is required. Oldest supported compilers: +* Visual Studio 2017 (15.8) +* GCC 7 +* Clang 5 ### Append diff --git a/allocator.h b/allocator.h index 27ae7d65..0db5beea 100644 --- a/allocator.h +++ b/allocator.h @@ -58,7 +58,7 @@ struct allocator namespace _detail { - constexpr size_t defaultAlign = + inline constexpr size_t defaultAlign = #if defined __STDCPP_DEFAULT_NEW_ALIGNMENT__ __STDCPP_DEFAULT_NEW_ALIGNMENT__; #elif _WIN64 or defined __x86_64__ // then assuming 16 byte aligned from malloc @@ -67,7 +67,7 @@ namespace _detail alignof(std::max_align_t); #endif - constexpr auto * allocFailMsg = "No memory oel::allocator"; + inline constexpr auto allocFailMsg = "No memory oel::allocator"; struct BadAlloc { @@ -99,7 +99,7 @@ namespace _detail { void * operator()(size_t const nBytes) const { - OEL_CONST_COND if (Align > defaultAlign) + if constexpr (Align > defaultAlign) { void * p = std::malloc(nBytes + Align); return AlignAndStore(p); @@ -117,7 +117,7 @@ namespace _detail void * operator()(size_t const nBytes) const { - OEL_CONST_COND if (Align > defaultAlign) + if constexpr (Align > defaultAlign) { void * p = old ? static_cast(old)[-1] : diff --git a/auxi/allocate_with_header.h b/auxi/allocate_with_header.h index 6b9c867f..f8cc838a 100644 --- a/auxi/allocate_with_header.h +++ b/auxi/allocate_with_header.h @@ -10,9 +10,8 @@ #include // for uintptr_t -namespace oel -{ -namespace _detail + +namespace oel::_detail { struct DebugAllocationHeader { @@ -20,7 +19,7 @@ namespace _detail size_t nObjects; }; - constexpr DebugAllocationHeader headerNoAllocation{0, 0}; + inline constexpr DebugAllocationHeader headerNoAllocation{}; #define OEL_DEBUG_HEADER_OF(ptr) ( (DebugAllocationHeader *)static_cast(ptr) - 1) #define OEL_DEBUG_HEADER_OF_C(ptr) ((const DebugAllocationHeader *)static_cast(ptr) - 1) @@ -119,6 +118,4 @@ namespace _detail Ptr end; Ptr reservEnd; }; -} - -} // namespace oel +} \ No newline at end of file diff --git a/auxi/contiguous_iterator_to_ptr.h b/auxi/contiguous_iterator_to_ptr.h index 923bcb85..6ed8019f 100644 --- a/auxi/contiguous_iterator_to_ptr.h +++ b/auxi/contiguous_iterator_to_ptr.h @@ -16,15 +16,11 @@ namespace oel { -//! If an IteratorSource range can be copied to an IteratorDest range with memmove, is-a true_type, else false_type -template< typename IteratorDest, typename IteratorSource > -struct can_memmove_with; - - #if __cpp_lib_concepts >= 201907 - template< std::contiguous_iterator T > - constexpr auto to_pointer_contiguous(T it) noexcept { return std::to_address(it); } - + constexpr auto to_pointer_contiguous(std::contiguous_iterator auto it) noexcept + { + return std::to_address(it); + } #else namespace _detail { @@ -55,24 +51,16 @@ struct can_memmove_with; constexpr T * to_pointer_contiguous(std::__wrap_iter it) noexcept { return it.base(); } #elif _CPPLIB_VER - #if _MSVC_STL_UPDATE < 201805 - #define OEL_UNWRAP(iter) _Unchecked(iter) - #else - #define OEL_UNWRAP(iter) iter._Unwrapped() - #endif - template< typename ContiguousIterator, - enable_if - < std::is_same< - decltype( OEL_UNWRAP(ContiguousIterator{}) ), - typename ContiguousIterator::pointer >::value + enable_if< + std::is_same_v< decltype(ContiguousIterator{}._Unwrapped()), + typename ContiguousIterator::pointer > > = 0 > constexpr auto to_pointer_contiguous(const ContiguousIterator & it) noexcept { - return _detail::ToAddress(OEL_UNWRAP(it)); + return _detail::ToAddress(it._Unwrapped()); } - #undef OEL_UNWRAP #endif #endif @@ -81,12 +69,8 @@ constexpr auto to_pointer_contiguous(std::move_iterator it) noexcept -> decltype( to_pointer_contiguous(it.base()) ) { return to_pointer_contiguous(it.base()); } - - //////////////////////////////////////////////////////////////////////////////// - - namespace _detail { template< typename Range > @@ -99,7 +83,7 @@ namespace _detail > = 0 > constexpr auto Size(Range && r, None...) -> decltype(end(r) - begin(r)) - { return end(r) - begin(r); } + { return end(r) - begin(r); } @@ -114,12 +98,13 @@ namespace _detail false_type CanMemmoveWith(...); } -} // namespace oel - -//! @cond FALSE +//! Is true if an IteratorSource range can be copied to an IteratorDest range with memmove template< typename IteratorDest, typename IteratorSource > -struct oel::can_memmove_with : - decltype( _detail::CanMemmoveWith(std::declval(), - std::declval()) ) {}; -//! @endcond +inline constexpr bool can_memmove_with = + decltype( + _detail::CanMemmoveWith(std::declval(), + std::declval()) + )::value; + +} // namespace oel diff --git a/auxi/detail_forward.h b/auxi/detail_forward.h index 148860d1..daeec47f 100644 --- a/auxi/detail_forward.h +++ b/auxi/detail_forward.h @@ -8,9 +8,8 @@ #include "type_traits.h" -namespace oel -{ -namespace _detail + +namespace oel::_detail { // Note: false for arrays, they aren't copy/move constructible template< typename T, typename SansRef, bool IsConst > @@ -21,28 +20,24 @@ namespace _detail (std::is_move_constructible_v and !IsConst) ) // !IsConst implies rvalue due to check in ForwardT and sizeof(SansRef) <= 2 * sizeof(int) #else - std::is_trivially_copy_constructible::value and std::is_trivially_destructible::value + std::is_trivially_copy_constructible_v and std::is_trivially_destructible_v and sizeof(SansRef) <= 2 * sizeof(void *) #endif > {}; template< typename T, typename SansRef = std::remove_reference_t, - bool IsConst = std::is_const::value + bool IsConst = std::is_const_v > using ForwardT = std::conditional_t< - conjunctionV< + std::conjunction_v< // Forwarding a function or mutable lvalue reference by value would break - bool_constant< - (!std::is_lvalue_reference::value or IsConst) - and !std::is_function::value - >, + bool_constant< !std::is_lvalue_reference_v or IsConst >, + std::negation< std::is_function >, PassByValueIsBetter >, std::remove_cv_t, T && >; -} - } \ No newline at end of file diff --git a/auxi/dynarray_iterator.h b/auxi/dynarray_iterator.h index b54e4231..2f70ec90 100644 --- a/auxi/dynarray_iterator.h +++ b/auxi/dynarray_iterator.h @@ -194,11 +194,8 @@ Ptr to_pointer_contiguous(const dynarray_iterator & it) noexcept { return } // namespace oel -namespace std -{ - template< typename Ptr > -struct pointer_traits< oel::dynarray_iterator > +struct std::pointer_traits< oel::dynarray_iterator > { using pointer = oel::dynarray_iterator; using difference_type = typename pointer::difference_type; @@ -207,17 +204,13 @@ struct pointer_traits< oel::dynarray_iterator > static element_type * to_address(const pointer & it) noexcept { return it._pElem; } }; -} - //////////////////////////////////////////////////////////////////////////////// -namespace oel -{ -namespace _detail +namespace oel::_detail { template< typename Ptr > dynarray_iterator MakeDynarrayIter(Ptr const pos, Ptr const begin, const void * parent) noexcept @@ -231,6 +224,4 @@ namespace _detail { return {pos, &_detail::headerNoAllocation, reinterpret_cast(parent)}; } } -} - } \ No newline at end of file diff --git a/auxi/impl_algo.h b/auxi/impl_algo.h index 6b020b7b..8cbab82b 100644 --- a/auxi/impl_algo.h +++ b/auxi/impl_algo.h @@ -1,6 +1,6 @@ #pragma once -// Copyright 2014, 2015 Ole Erik Peistorpet +// Copyright 2015 Ole Erik Peistorpet // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -12,9 +12,7 @@ #include -namespace oel -{ -namespace _detail +namespace oel::_detail { struct Throw { // Exception throwing has been split out from templates to avoid bloat @@ -32,9 +30,9 @@ namespace _detail //////////////////////////////////////////////////////////////////////////////// template< typename T > - void Destroy(T * first, const T * last) noexcept + void Destroy([[maybe_unused]] T * first, [[maybe_unused]] const T * last) noexcept { // first > last is OK, does nothing - if (!std::is_trivially_destructible::value) // for speed with non-optimized builds + if constexpr (!std::is_trivially_destructible_v) // for speed with non-optimized builds { for (; first < last; ++first) first-> ~T(); @@ -62,70 +60,65 @@ namespace _detail } - template< typename T, - enable_if< is_trivially_relocatable::value > = 0 - > - inline T * Relocate(T *__restrict src, size_t const n, T *__restrict dest) + template< typename T > + T * Relocate(T *__restrict src, size_t const n, T *__restrict dest) { - T *const dLast = dest + n; - #if OEL_CHECK_NULL_MEMCPY - if (src) - #endif - { std::memcpy( - static_cast(dest), - static_cast(src), - sizeof(T) * n ); + if constexpr (is_trivially_relocatable::value) + { + T *const dLast = dest + n; + #if OEL_CHECK_NULL_MEMCPY + if (src) + #endif + { std::memcpy( + static_cast(dest), + static_cast(src), + sizeof(T) * n ); + } + return dLast; } - return dLast; - } - - template< typename T, typename... None > - T * Relocate(T *__restrict src, size_t const n, T *__restrict dest, None...) - { - #if OEL_HAS_EXCEPTIONS - static_assert( std::is_nothrow_move_constructible::value, - "dynarray requires that T is noexcept move constructible or trivially relocatable" ); - #endif - for (size_t i{}; i < n; ++i) + else { - ::new(static_cast(dest + i)) T( std::move(src[i]) ); - src[i].~T(); + #if OEL_HAS_EXCEPTIONS + static_assert( std::is_nothrow_move_constructible_v, + "dynarray requires that T is noexcept move constructible or trivially relocatable" ); + #endif + for (size_t i{}; i < n; ++i) + { + ::new(static_cast(dest + i)) T( std::move(src[i]) ); + src[i].~T(); + } + return dest + n; } - return dest + n; } #undef OEL_CHECK_NULL_MEMCPY - template< typename Alloc, typename ContiguousIter, typename T, - enable_if< can_memmove_with::value > = 0 - > - inline ContiguousIter UninitCopy(ContiguousIter const src, T *__restrict dFirst, T *const dLast, Alloc &) + template< typename Alloc, typename InputIter, typename T > + InputIter UninitCopy(InputIter src, T *__restrict dest, T *const dLast, [[maybe_unused]] Alloc & allo) { - auto const n = dLast - dFirst; - _detail::MemcpyCheck(src, n, dFirst); - return src + n; - } - - template< typename Alloc, typename InputIter, typename T, - enable_if< ! can_memmove_with::value > = 0 - > - InputIter UninitCopy(InputIter src, T *__restrict dest, T *const dLast, Alloc & allo) - { - T *const dFirst = dest; - OEL_TRY_ + if constexpr (can_memmove_with) { - while (dest != dLast) + auto const n = dLast - dest; + _detail::MemcpyCheck(src, n, dest); + return src + n; + } + else + { T *const dFirst = dest; + OEL_TRY_ { - std::allocator_traits::construct(allo, dest, *src); - ++src; ++dest; + while (dest != dLast) + { + std::allocator_traits::construct(allo, dest, *src); + ++src; ++dest; + } } + OEL_CATCH_ALL + { + _detail::Destroy(dFirst, dest); + OEL_RETHROW; + } + return src; } - OEL_CATCH_ALL - { - _detail::Destroy(dFirst, dest); - OEL_RETHROW; - } - return src; } @@ -133,11 +126,11 @@ namespace _detail struct UninitFill { template< typename T > - using IsByte = bool_constant< sizeof(T) == 1 and - (std::is_integral::value or std::is_enum::value) >; + static constexpr auto isByte = + sizeof(T) == 1 and (std::is_integral_v or std::is_enum_v); template< typename T, typename... Args, - enable_if< !IsByte::value > = 0 + enable_if< !isByte > = 0 > static void call(T *__restrict first, T *const last, Alloc & allo, const Args &... args) { @@ -155,7 +148,7 @@ namespace _detail } template< typename T, - enable_if< IsByte::value > = 0 + enable_if< isByte > = 0 > static void call(T *const first, T * last, Alloc &, T val) { @@ -163,7 +156,7 @@ namespace _detail } template< typename T, - enable_if< std::is_trivial::value > = 0 + enable_if< std::is_trivial_v > = 0 > static void call(T *const first, T * last, Alloc &) { @@ -176,41 +169,38 @@ namespace _detail template< typename Alloc, typename T > static void call(T *__restrict first, T *const last, Alloc & a) { - if (!std::is_trivially_default_constructible::value) + if constexpr (!std::is_trivially_default_constructible_v) + { UninitFill::call(first, last, a); + } + else + { (void) first; (void) last; (void) a; // avoid VC++ 2017 warning C4100 + } } }; - template< typename Range, - enable_if< - iter_is_forward< iterator_t > - > = 0 > - size_t CountOrEndNoSize(Range & r, int) - { - size_t n = 0; - auto it = begin(r); - auto const last = end(r); - while (it != last) { ++it; ++n; } - - return n; - } - - template< typename Range > - auto CountOrEndNoSize(Range & r, long) { return end(r); } - // If r is sized or multi-pass, returns element count as size_t, else end(r) template< typename Range, typename... None > inline auto CountOrEnd(Range & r, None...) { - return _detail::CountOrEndNoSize(r, 0); + if constexpr (iter_is_forward< iterator_t >) + { + size_t n{}; + auto it = begin(r); + auto const l = end(r); + while (it != l) { ++it; ++n; } + + return n; + } + else + { return end(r); + } } template< typename Range > auto CountOrEnd(Range & r) -> decltype( static_cast(_detail::Size(r)) ) { return static_cast(_detail::Size(r)); } -} - -} // namespace oel +} \ No newline at end of file diff --git a/auxi/range_algo_detail.h b/auxi/range_algo_detail.h index ae9f7377..6cdf52da 100644 --- a/auxi/range_algo_detail.h +++ b/auxi/range_algo_detail.h @@ -11,66 +11,67 @@ #include -namespace oel -{ -namespace _detail + +namespace oel::_detail { - template< typename Container > - constexpr auto EraseEnd(Container & c, typename Container::iterator f) + template< typename Container, typename Iterator > + constexpr auto EraseEnd(Container & c, Iterator f) -> decltype(c.erase_to_end(f)) - { return c.erase_to_end(f); } + { return c.erase_to_end(f); } - template< typename Container, typename ContainerIter, typename... None > - constexpr void EraseEnd(Container & c, ContainerIter f, None...) { c.erase(f, c.end()); } + template< typename Container, typename Iterator, typename... None > + constexpr void EraseEnd(Container & c, Iterator f, None...) + { + c.erase(f, c.end()); + } template< typename Container, typename UnaryPred > - constexpr auto RemoveIf(Container & c, UnaryPred p, int) - -> decltype(c.remove_if(p)) { return c.remove_if(p); } + constexpr auto RemoveIf(Container & c, UnaryPred p) + -> decltype(c.remove_if(p)) + { return c.remove_if(p); } - template< typename Container, typename UnaryPred > - constexpr void RemoveIf(Container & c, UnaryPred p, long) + template< typename Container, typename UnaryPred, typename... None > + constexpr void RemoveIf(Container & c, UnaryPred p, None...) { _detail::EraseEnd( c, std::remove_if(begin(c), end(c), p) ); } template< typename Container > - constexpr auto Unique(Container & c, int) // pass dummy int to prefer this overload + constexpr auto Unique(Container & c) -> decltype(c.unique()) { return c.unique(); } - template< typename Container > - constexpr void Unique(Container & c, long) + template< typename Container, typename... None > + constexpr void Unique(Container & c, None...) { _detail::EraseEnd( c, std::unique(begin(c), end(c)) ); } //////////////////////////////////////////////////////////////////////////////// - template< typename ContiguousIter, typename ContiguousIter2, - enable_if< can_memmove_with::value > = 0 - > - ContiguousIter CopyUnsf(ContiguousIter const src, size_t const n, ContiguousIter2 const dest) - { - #if OEL_MEM_BOUND_DEBUG_LVL - if (n != 0) - { // Dereference to detect out of range errors if the iterator has internal check - (void) *dest; - (void) *(dest + (n - 1)); - } - #endif - _detail::MemcpyCheck(src, n, to_pointer_contiguous(dest)); - return src + n; - } - - template< typename InputIter, typename RandomAccessIter, typename... None > - InputIter CopyUnsf(InputIter src, size_t const n, RandomAccessIter const dest, None...) + template< typename InputIter, typename RandomAccessIter > + InputIter CopyUnsf(InputIter src, size_t const n, RandomAccessIter const dest) { - for (size_t i{}; i < n; ++i) + if constexpr (can_memmove_with) { - dest[i] = *src; - ++src; + #if OEL_MEM_BOUND_DEBUG_LVL + if (n != 0) + { // Dereference to detect out of range errors if the iterator has internal check + (void) *dest; + (void) *(dest + (n - 1)); + } + #endif + _detail::MemcpyCheck(src, n, to_pointer_contiguous(dest)); + return src + n; + } + else + { for (size_t i{}; i < n; ++i) + { + dest[i] = *src; + ++src; + } + return src; } - return src; } @@ -106,6 +107,4 @@ namespace _detail _detail::CopyUnsf(begin(src), n, begin(dest)); return success; } -} - } \ No newline at end of file diff --git a/auxi/type_traits.h b/auxi/type_traits.h index 738c0013..9c042961 100644 --- a/auxi/type_traits.h +++ b/auxi/type_traits.h @@ -22,26 +22,17 @@ #include #endif -#if !__has_include() - #define OEL_NO_BOOST 1 -#endif - namespace oel { namespace _detail { template< typename Alloc > // pass dummy int to prefer this overload - bool_constant CanRealloc(int); + constexpr auto CanRealloc(int) + -> decltype(Alloc::can_reallocate()) + { return Alloc::can_reallocate(); } template< typename > - false_type CanRealloc(long); - - - template< typename T > - typename T::is_always_equal IsAlwaysEqual(int); - - template< typename T > - std::is_empty IsAlwaysEqual(long); + constexpr bool CanRealloc(long) { return false; } @@ -60,34 +51,17 @@ namespace _detail template< typename Iter, typename Tag > constexpr bool IterIs() { - if (!std::is_copy_constructible::value) + if constexpr (!std::is_copy_constructible_v) return false; - return std::is_base_of< Tag, decltype(_detail::IterCat(0)) >::value - or std::is_base_of< Tag, decltype(_detail::IterConcept(0)) >::value; + return std::is_base_of_v< Tag, decltype(_detail::IterCat(0)) > + or std::is_base_of_v< Tag, decltype(_detail::IterConcept(0)) >; } } -template< typename Alloc > -struct allocator_can_realloc : decltype( _detail::CanRealloc(0) ) {}; - -//! Part of std::allocator_traits for C++17 -template< typename T > -using is_always_equal = decltype( _detail::IsAlwaysEqual(0) ); - -template< bool... > struct bool_pack_t; - -/** @brief Similar to std::conjunction, but is not short-circuiting -* -* Example: @code -template< typename... Ts > -struct Numbers { - static_assert(oel::all_< std::is_arithmetic... >::value, "Only arithmetic types, please"); -@endcode */ -template< typename... BoolConstants > -struct all_ : std::is_same< bool_pack_t, - bool_pack_t > {}; +template< typename Alloc > +inline constexpr bool allocator_can_realloc = _detail::CanRealloc(0); using std::ptrdiff_t; @@ -132,20 +106,20 @@ using borrowed_iterator_t = #endif template< typename Iterator > -constexpr bool iter_is_forward = _detail::IterIs(); +inline constexpr bool iter_is_forward = _detail::IterIs(); template< typename Iterator > -constexpr bool iter_is_bidirectional = _detail::IterIs(); +inline constexpr bool iter_is_bidirectional = _detail::IterIs(); template< typename Iterator > -constexpr bool iter_is_random_access = _detail::IterIs(); +inline constexpr bool iter_is_random_access = _detail::IterIs(); /** @brief Partial emulation of std::sized_sentinel_for (C++20) * * Let i be an Iterator and s a Sentinel. If `s - i` is well-formed, then this value specifies whether * that subtraction is invalid or not O(1). Must be specialized for some iterator, sentinel pairs. */ template< typename Sentinel, typename Iterator > -constexpr bool disable_sized_sentinel_for = +inline constexpr bool disable_sized_sentinel_for = #if __cpp_lib_concepts >= 201907 std::disable_sized_sentinel_for; #else @@ -165,14 +139,11 @@ using enable_if = typename std::enable_if::type; namespace _detail { - template< typename B0, typename B1 > - constexpr bool conjunctionV = std::conditional_t::value; - - - template< typename > constexpr bool isUnboundedArray = false; + template< typename > + inline constexpr bool isUnboundedArray = false; template< typename T > - constexpr bool isUnboundedArray = true; + inline constexpr bool isUnboundedArray = true; } } // namespace oel @@ -184,10 +155,6 @@ struct oel::is_trivially_relocatable : decltype( specify_trivial_relocate(std::d //! @cond INTERNAL -#if __cpp_deduction_guides or (_MSC_VER >= 1914 and _HAS_CXX17) - #define OEL_HAS_DEDUCTION_GUIDES 1 -#endif - #if __cpp_lib_concepts >= 201907 #define OEL_REQUIRES(...) requires(__VA_ARGS__) #else diff --git a/dynarray.h b/dynarray.h index e5ea4e73..813419d6 100644 --- a/dynarray.h +++ b/dynarray.h @@ -25,7 +25,7 @@ namespace oel template< typename T, typename Alloc > is_trivially_relocatable specify_trivial_relocate(dynarray); -//! Overloads generic erase_unstable(RandomAccessContainer &, RandomAccessContainer::size_type) (in range_algo.h) +//! Overloads generic erase_unstable(RandomAccessContainer &, Integral) (in range_algo.h) template< typename T, typename A > inline void erase_unstable(dynarray & d, size_t index) { d.erase_unstable(d.begin() + index); } @@ -70,14 +70,14 @@ inline namespace debug template< typename T, typename Alloc/* = oel::allocator*/ > class dynarray { - using _allocTrait = std::allocator_traits; + using _alloTrait = std::allocator_traits; + using pointer = typename _alloTrait::pointer; public: using value_type = T; using allocator_type = Alloc; using reference = T &; using const_reference = const T &; - using pointer = typename _allocTrait::pointer; using difference_type = ptrdiff_t; using size_type = size_t; @@ -113,7 +113,6 @@ class dynarray * Example, construct from a standard istream with formatting (using Boost): @code #include - // Template argument for dynarray can be omitted with C++17 as shown (there exists a deduction guide) auto result = dynarray(boost::range::istream_range(someStream)); @endcode */ template< typename InputRange, @@ -125,17 +124,17 @@ class dynarray dynarray(dynarray && other) noexcept : _m(std::move(other._m)) {} dynarray(dynarray && other, const Alloc & a); dynarray(const dynarray & other) : dynarray(other, - _allocTrait::select_on_container_copy_construction(other._m)) {} + _alloTrait::select_on_container_copy_construction(other._m)) {} dynarray(const dynarray & other, const Alloc & a) : _m(a) { append(other); } ~dynarray() noexcept; dynarray & operator =(dynarray && other) & - noexcept(_allocTrait::propagate_on_container_move_assignment::value or is_always_equal::value); + noexcept(_alloTrait::propagate_on_container_move_assignment::value or _alloTrait::is_always_equal::value); //! Requires that allocator_type is always equal or does not have propagate_on_container_copy_assignment dynarray & operator =(const dynarray & other) & { - static_assert(!_allocTrait::propagate_on_container_copy_assignment::value or is_always_equal::value, + static_assert(!_alloTrait::propagate_on_container_copy_assignment::value or _alloTrait::is_always_equal::value, "Alloc propagate_on_container_copy_assignment unsupported"); assign(other); return *this; } @@ -153,7 +152,7 @@ class dynarray * Any elements held before the call are either assigned to or destroyed. */ template< typename InputRange > auto assign(InputRange && source) - -> borrowed_iterator_t { return _doAssign(oel::adl_begin(source), _detail::CountOrEnd(source)); } + -> borrowed_iterator_t { return _doAssign(adl_begin(source), _detail::CountOrEnd(source)); } void assign(size_type count, const T & val) { clear(); append(count, val); } @@ -167,7 +166,7 @@ class dynarray * where `end(source)` is not needed if `source.size()` exists. */ template< typename InputRange > auto append(InputRange && source) - -> borrowed_iterator_t { return _append(oel::adl_begin(source), _detail::CountOrEnd(source)); } + -> borrowed_iterator_t { return _append(adl_begin(source), _detail::CountOrEnd(source)); } //! Equivalent to `std::vector::insert(end(), il)` void append(std::initializer_list il) { append<>(il); } /** @@ -213,7 +212,7 @@ class dynarray * * Constant complexity (compared to linear in the distance between pos and end() for normal erase). * @return iterator corresponding to the same index in the sequence as pos, same as for std containers. */ - iterator erase_unstable(iterator pos) & { _eraseUnorder(pos); return pos; } + iterator erase_unstable(iterator pos) &; iterator erase(iterator pos) &; @@ -227,14 +226,17 @@ class dynarray size_type size() const noexcept { return _m.end - _m.data; } - void reserve(size_type minCap); - + void reserve(size_type minCap) + { + if (capacity() < minCap) + _realloc(_calcCapChecked(minCap), size()); + } //! It's probably a good idea to check that size < capacity before calling, maybe add some treshold to size void shrink_to_fit(); size_type capacity() const noexcept { return _m.reservEnd - _m.data; } - constexpr size_type max_size() const noexcept { return _allocTrait::max_size(_m) - _allocateWrap::sizeForHeader; } + constexpr size_type max_size() const noexcept { return _alloTrait::max_size(_m) - _allocateWrap::sizeForHeader; } //! How much smaller capacity is than the number passed to allocator_type::allocate static constexpr size_type allocate_size_overhead() noexcept { return _allocateWrap::sizeForHeader; } @@ -351,8 +353,8 @@ class dynarray _m.data = newData; } - void _swapBuf(_scopedPtr & s) - { + void _swapBuf(_scopedPtr & s) noexcept + { // Missing _debugSizeUpdater here, but only used in _insertRealloc, which is guarded using std::swap; swap(_m.data, s.data); swap(_m.reservEnd, s.bufEnd); @@ -376,30 +378,24 @@ class dynarray } - void _moveInternBase(_internBase & src) + void _moveInternBase(_internBase & src) noexcept { static_cast<_internBase &>(_m) = src; src.reservEnd = src.end = src.data = nullptr; } - void _moveAssignAlloc(std::true_type, Alloc & src) + void _swapAlloc([[maybe_unused]] Alloc & other) noexcept { - Alloc & a = _m; - a = std::move(src); - } - - OEL_ALWAYS_INLINE void _moveAssignAlloc(std::false_type, Alloc &) {} - - void _swapAlloc(std::true_type, Alloc & a) noexcept - { - using std::swap; - swap(static_cast(_m), a); - } - - void _swapAlloc(std::false_type, Alloc & a) noexcept - { // propagate_on_container_swap false, standard says this is undefined if allocators compare unequal - OEL_ASSERT(static_cast(_m) == a); - (void) a; + [[maybe_unused]] Alloc & a = _m; + if constexpr (_alloTrait::propagate_on_container_swap::value) + { + using std::swap; + swap(a, other); + } + else + { // Standard says this is undefined if allocators compare unequal + OEL_ASSERT(a == other); + } } @@ -448,25 +444,24 @@ class dynarray } - template< typename A = Alloc, enable_if< allocator_can_realloc::value > = 0 > void _realloc(size_type const newCap, size_type const oldSize) { - pointer const p = _allocateWrap::realloc(_m, _m.data, newCap); - _m.data = p; - _m.end = p + oldSize; - _m.reservEnd = p + newCap; - } - - template< typename... None > - void _realloc(size_type const newCap, size_type const oldSize, None...) - { - pointer const newData = _allocateWrap::allocate(_m, newCap); - _m.end = _detail::Relocate(_m.data, oldSize, newData); - _resetData(newData); - _m.reservEnd = newData + newCap; + if constexpr (allocator_can_realloc) + { + pointer const p = _allocateWrap::realloc(_m, _m.data, newCap); + _m.data = p; + _m.end = p + oldSize; + _m.reservEnd = p + newCap; + } + else + { pointer const newData = _allocateWrap::allocate(_m, newCap); + _m.end = _detail::Relocate(_m.data, oldSize, newData); + _resetData(newData); + _m.reservEnd = newData + newCap; + } + (void) _debugSizeUpdater{_m}; } - #ifdef _MSC_VER __declspec(noinline) // to get the compiler to inline calling function #endif @@ -499,96 +494,70 @@ class dynarray } - template< typename... None > - void _eraseUnorder(iterator pos, None...) - { - *pos = std::move(back()); - pop_back(); - } - - template< typename T_ = T, - enable_if< is_trivially_relocatable::value > = 0 - > - void _eraseUnorder(iterator const pos) - { - _debugSizeUpdater guard{_m}; - - T *const ptr = std::addressof(*pos); - ptr-> ~T(); - --_m.end; - auto mem = reinterpret_cast *>(ptr); - *mem = *reinterpret_cast *>(_m.end); // relocate last element to pos - } - - - template< typename ContiguousIter, - enable_if< can_memmove_with::value > = 0 - > - ContiguousIter _doAssign(ContiguousIter const first, size_type const count) + template< typename InputIter > + InputIter _doAssign(InputIter src, size_type const count) { _debugSizeUpdater guard{_m}; - if (capacity() < count) + if constexpr (can_memmove_with) { - // Deallocating first might be better, but then the _m pointers would have to be nulled in case allocate throws - _resetData(_allocateChecked(count)); - _m.end = _m.reservEnd = _m.data + count; + if (capacity() < count) + { // Deallocating first might be better, + // but then the _m pointers would have to be nulled in case allocate throws + _resetData(_allocateChecked(count)); + _m.end = _m.reservEnd = _m.data + count; + } + else + { _m.end = _m.data + count; + } + // UB for self assign, but found to work. Add check in operator = or use memmove? + _detail::MemcpyCheck(src, count, _m.data); + + return src + count; } else - { _m.end = _m.data + count; - } - // UB for self assign, but found to work. Add check in operator = or use memmove? - _detail::MemcpyCheck(first, count, _m.data); - - return first + count; - } - - template< typename InputIter, typename... None > - InputIter _doAssign(InputIter src, size_type const count, None...) - { - _debugSizeUpdater guard{_m}; - - auto copy = [](InputIter src_, T * dest, T * dLast) - { - while (dest != dLast) + { auto copy = [](InputIter src_, T * dest, T * dLast) { - *dest = *src_; - ++src_; ++dest; + while (dest != dLast) + { + *dest = *src_; + ++src_; ++dest; + } + return src_; + }; + T * newEnd; + if (capacity() < count) + { + T *const newData = _allocateChecked(count); + // Old elements might hold some limited resource, probably good to destroy them before constructing new + _detail::Destroy(_m.data, _m.end); + _resetData(newData); + _m.end = newData; + _m.reservEnd = newData + count; + newEnd = _m.reservEnd; } - return src_; - }; - T * newEnd; - if (capacity() < count) - { - T *const newData = _allocateChecked(count); - // Old elements might hold some limited resource, destroying them before constructing new is probably good - _detail::Destroy(_m.data, _m.end); - _resetData(newData); - _m.end = newData; - _m.reservEnd = newData + count; - newEnd = _m.reservEnd; - } - else - { newEnd = _m.data + count; - if (newEnd < _m.end) - { // downsizing, assign new and destroy rest - src = copy(std::move(src), _m.data, newEnd); - erase_to_end(_makeIter(newEnd)); + else + { newEnd = _m.data + count; + if (newEnd < _m.end) + { // downsizing, assign new and destroy rest + src = copy(std::move(src), _m.data, newEnd); + erase_to_end(_makeIter(newEnd)); + } + else // assign to old elements as far as we can + { src = copy(std::move(src), _m.data, _m.end); + } } - else // assign to old elements as far as we can - { src = copy(std::move(src), _m.data, _m.end); + while (_m.end < newEnd) + { // each iteration updates _m.end for exception safety + _alloTrait::construct(_m, _m.end, *src); + ++src; ++_m.end; } + return src; } - while (_m.end < newEnd) - { // each iteration updates _m.end for exception safety - _allocTrait::construct(_m, _m.end, *src); - ++src; ++_m.end; - } - return src; } - template< typename InputIter, typename Sentinel, typename... None > - InputIter _doAssign(InputIter first, Sentinel const last, None...) + template< typename InputIter, typename Sentinel > + InputIter _doAssign(InputIter first, Sentinel const last) { // single-pass iterator and unknown count clear(); for (; first != last; ++first) @@ -668,7 +637,7 @@ class dynarray template< typename... Args > static T * construct(decltype(_m) & alloc, T *const newPos, Args... args) { - _allocTrait::construct(alloc, newPos, static_cast(args)...); + _alloTrait::construct(alloc, newPos, static_cast(args)...); return newPos + 1; } }; @@ -710,7 +679,7 @@ typename dynarray::iterator { // Temporary in case constructor throws or source is an element of this dynarray at pos or after storage_for tmp; - _allocTrait::construct(_m, reinterpret_cast(&tmp), static_cast(args)...); + _alloTrait::construct(_m, reinterpret_cast(&tmp), static_cast(args)...); // Relocate [pos, end) to [pos + 1, end + 1), leaving memory at pos uninitialized (conceptually) size_type const bytesAfterPos = sizeof(T) * (_m.end - pPos); std::memmove( @@ -732,10 +701,10 @@ template< typename ForwardRange > typename dynarray::iterator dynarray::insert_range(const_iterator pos, ForwardRange && src) & { - auto first = oel::adl_begin(src); + auto first = adl_begin(src); auto const count = _detail::CountOrEnd(src); - static_assert( std::is_same::value, + static_assert( std::is_same_v, "insert_range requires that source models std::ranges::forward_range or that source.size() is valid" ); OEL_DYNARR_INSERT_STEP1 @@ -752,9 +721,9 @@ typename dynarray::iterator bytesAfterPos ); _m.end += count; // Construct new - if (can_memmove_with::value) + if constexpr (can_memmove_with) { - _detail::UninitCopy(first, pPos, dLast, _m); + _detail::MemcpyCheck(first, count, pPos); } else { T * dest = pPos; @@ -762,7 +731,7 @@ typename dynarray::iterator { while (dest != dLast) { - _allocTrait::construct(_m, dest, *first); + _alloTrait::construct(_m, dest, *first); ++first; ++dest; } } @@ -792,7 +761,7 @@ inline T & dynarray::emplace_back(Args &&... args) & if (_m.end == _m.reservEnd) _growByOne(); - _allocTrait::construct(_m, _m.end, static_cast(args)...); + _alloTrait::construct(_m, _m.end, static_cast(args)...); return *(_m.end++); } @@ -802,20 +771,20 @@ template< typename T, typename Alloc > dynarray::dynarray(dynarray && other, const Alloc & a) : _m(a) { - OEL_CONST_COND if (!is_always_equal::value and a != other._m) - append(view::move(other)); + OEL_CONST_COND if (!_alloTrait::is_always_equal::value and a != other._m) + append(other | view::move); else _moveInternBase(other._m); } template< typename T, typename Alloc > dynarray & dynarray::operator =(dynarray && other) & - noexcept(_allocTrait::propagate_on_container_move_assignment::value or is_always_equal::value) + noexcept(_alloTrait::propagate_on_container_move_assignment::value or _alloTrait::is_always_equal::value) { - OEL_CONST_COND if (!_allocTrait::propagate_on_container_move_assignment::value - and static_cast(_m) != other._m) + Alloc & myA = _m; + OEL_CONST_COND if (!_alloTrait::propagate_on_container_move_assignment::value and myA != other._m) { - assign(view::move(other)); + assign(other | view::move); } else // take allocated memory from other { @@ -825,7 +794,8 @@ dynarray & dynarray::operator =(dynarray && other) & _allocateWrap::dealloc(_m, _m.data, capacity()); } _moveInternBase(other._m); - _moveAssignAlloc(typename _allocTrait::propagate_on_container_move_assignment{}, other._m); + if constexpr (_alloTrait::propagate_on_container_move_assignment::value) + myA = static_cast(other._m); } return *this; } @@ -864,26 +834,13 @@ void dynarray::swap(dynarray & other) noexcept _internBase & a = _m; _internBase & b = other._m; std::swap(a, b); - _swapAlloc(typename _allocTrait::propagate_on_container_swap{}, other._m); + _swapAlloc(other._m); } -template< typename T, typename Alloc > -void dynarray::reserve(size_type n) -{ - if (capacity() < n) - { - _debugSizeUpdater guard{_m}; - - _realloc(_calcCapChecked(n), size()); - } -} - template< typename T, typename Alloc > void dynarray::shrink_to_fit() { - _debugSizeUpdater guard{_m}; - size_type const used = size(); if (0 < used) { @@ -892,6 +849,8 @@ void dynarray::shrink_to_fit() else { _resetData(nullptr); _m.reservEnd = _m.end = nullptr; + + (void) _debugSizeUpdater{_m}; } } @@ -934,6 +893,27 @@ inline void dynarray::erase_to_end(iterator first) noexcept _m.end = newEnd; } +template< typename T, typename Alloc > +inline typename dynarray::iterator dynarray::erase_unstable(iterator pos) & +{ + if constexpr (is_trivially_relocatable::value) + { + T & elem = *pos; + elem.~T(); + + --_m.end; + _debugSizeUpdater guard{_m}; + + auto & mem = reinterpret_cast &>(elem); + mem = *reinterpret_cast *>(_m.end); // relocate last element to pos + } + else + { *pos = std::move(back()); + pop_back(); + } + return pos; +} + template< typename T, typename Alloc > typename dynarray::iterator dynarray::erase(iterator pos) & { @@ -941,7 +921,7 @@ typename dynarray::iterator dynarray::erase(iterator pos) & T *const ptr = to_pointer_contiguous(pos); OEL_ASSERT(_m.data <= ptr and ptr < _m.end); - if (is_trivially_relocatable::value) + if constexpr (is_trivially_relocatable::value) { ptr-> ~T(); T *const next = ptr + 1; @@ -966,7 +946,8 @@ typename dynarray::iterator dynarray::erase(iterator first, T * dest = to_pointer_contiguous(first); const T *const pLast = to_pointer_contiguous(last); OEL_ASSERT(_m.data <= dest and dest <= pLast and pLast <= _m.end); - if (is_trivially_relocatable::value) + + if constexpr (is_trivially_relocatable::value) { _detail::Destroy(dest, pLast); size_type const nAfterLast = _m.end - pLast; @@ -1002,17 +983,15 @@ const T & dynarray::at(size_type i) const } -#ifdef OEL_HAS_DEDUCTION_GUIDES template< typename InputRange, typename Alloc = allocator< - iter_value_t< iterator_t > + iter_value_t< iterator_t > > > explicit dynarray(InputRange &&, Alloc = {}) -> dynarray< iter_value_t< iterator_t >, Alloc >; -#endif #ifdef OEL_DYNARRAY_IN_DEBUG } diff --git a/fwd.h b/fwd.h index 49d3e301..5ac5b1f5 100644 --- a/fwd.h +++ b/fwd.h @@ -77,11 +77,9 @@ class dynarray; -template< bool Val > -using bool_constant = std::integral_constant; - -using std::true_type; // equals bool_constant +using std::bool_constant; using std::false_type; +using std::true_type; /** @@ -112,7 +110,7 @@ class Outer { }; @endcode */ template< typename T > -bool_constant< std::is_trivially_move_constructible::value and std::is_trivially_destructible::value > +bool_constant< std::is_trivially_move_constructible_v and std::is_trivially_destructible_v > specify_trivial_relocate(T &&); /** @brief Trait that tells if T can be trivially relocated. See specify_trivial_relocate(T &&) diff --git a/optimize_ext/boost_variant2.h b/optimize_ext/boost_variant2.h index 2f535271..723e33e0 100644 --- a/optimize_ext/boost_variant2.h +++ b/optimize_ext/boost_variant2.h @@ -17,6 +17,6 @@ namespace oel template< typename... Ts > struct is_trivially_relocatable< boost::variant2::variant > - : all_< is_trivially_relocatable... > {}; + : std::conjunction< is_trivially_relocatable... > {}; } \ No newline at end of file diff --git a/optimize_ext/default.h b/optimize_ext/default.h index 11a3b4bd..41caaf0c 100644 --- a/optimize_ext/default.h +++ b/optimize_ext/default.h @@ -16,7 +16,11 @@ #include #include -#ifndef OEL_NO_BOOST +#if __has_include() + #define OEL_HAS_BOOST 1 +#endif + +#ifdef OEL_HAS_BOOST #include #include @@ -62,7 +66,7 @@ struct is_trivially_relocatable< std::shared_ptr > : true_type {}; template< typename T > struct is_trivially_relocatable< std::weak_ptr > : true_type {}; -#ifndef OEL_NO_BOOST +#ifdef OEL_HAS_BOOST template< typename T > struct is_trivially_relocatable< boost::container::pmr::polymorphic_allocator > : true_type {}; @@ -81,15 +85,15 @@ struct is_trivially_relocatable< std::weak_ptr > : true_type {}; template< typename... Ts > struct is_trivially_relocatable< boost::variant > - : all_< is_trivially_relocatable... > {}; + : std::conjunction< is_trivially_relocatable... > {}; #endif template< typename T, typename U > struct is_trivially_relocatable< std::pair > - : all_< is_trivially_relocatable, is_trivially_relocatable > {}; + : std::conjunction< is_trivially_relocatable, is_trivially_relocatable > {}; template< typename... Ts > struct is_trivially_relocatable< std::tuple > - : all_< is_trivially_relocatable... > {}; + : std::conjunction< is_trivially_relocatable... > {}; } \ No newline at end of file diff --git a/optimize_ext/std_variant.h b/optimize_ext/std_variant.h index 78e37bb5..a8ffbc1e 100644 --- a/optimize_ext/std_variant.h +++ b/optimize_ext/std_variant.h @@ -17,6 +17,6 @@ namespace oel template< typename... Ts > struct is_trivially_relocatable< std::variant > - : all_< is_trivially_relocatable... > {}; + : std::conjunction< is_trivially_relocatable... > {}; } \ No newline at end of file diff --git a/range_algo.h b/range_algo.h index 7de64950..3272b6f2 100644 --- a/range_algo.h +++ b/range_algo.h @@ -23,8 +23,8 @@ namespace oel * * Constant complexity (compared to linear in the distance between position and last for standard erase). * The end iterator and any iterator, pointer and reference referring to the last element may become invalid. */ -template< typename RandomAccessContainer > -constexpr void erase_unstable(RandomAccessContainer & c, typename RandomAccessContainer::size_type index) +template< typename RandomAccessContainer, typename Integral > +constexpr void erase_unstable(RandomAccessContainer & c, Integral index) { c[index] = std::move(c.back()); c.pop_back(); @@ -35,14 +35,14 @@ constexpr void erase_unstable(RandomAccessContainer & c, typename RandomAccessCo * * This mimics `std::erase_if` (C++20) for sequence containers */ template< typename Container, typename UnaryPredicate > -constexpr void erase_if(Container & c, UnaryPredicate p) { _detail::RemoveIf(c, p, int{}); } +constexpr void erase_if(Container & c, UnaryPredicate p) { _detail::RemoveIf(c, p); } /** * @brief Erase consecutive duplicate elements in container * * Calls Container::unique if available (with fallback std::unique). * To erase duplicates anywhere, sort container contents first. (Or just use std::set or unordered_set) */ template< typename Container > -constexpr void erase_adjacent_dup(Container & c) { _detail::Unique(c, int{}); } +constexpr void erase_adjacent_dup(Container & c) { _detail::Unique(c); } @@ -57,7 +57,7 @@ struct copy_return * @pre If the ranges overlap, behavior is undefined (uses memcpy when possible) * * Requires that `source.size()` or `end(source) - begin(source)` is valid, and that dest models random_access_iterator. -* To move instead of copy, pass `view::move(source)`. To mimic std::copy_n, use view::counted. +* To move instead of copy, wrap source with view::move. To mimic std::copy_n, use view::counted. * (Views can be used for all functions taking a range as source) */ template< typename SizedInputRange, typename RandomAccessIter > inline auto copy_unsafe(SizedInputRange && source, RandomAccessIter dest) diff --git a/unit_test/CMakeLists.txt b/unit_test/CMakeLists.txt index 72a52a7f..0df1a6f8 100644 --- a/unit_test/CMakeLists.txt +++ b/unit_test/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.1) project(oel-test CXX) -set(CMAKE_CXX_STANDARD 14 CACHE STRING "C++ standard to use, tested up to 20") +set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard to use, minimum 17") option(MEM_BOUND_DEBUG "Set OEL_MEM_BOUND_DEBUG_LVL=2" off) diff --git a/unit_test/dynarray_construct_assignop_swap_gtest.cpp b/unit_test/dynarray_construct_assignop_swap_gtest.cpp index 9c9990b3..b24c27f5 100644 --- a/unit_test/dynarray_construct_assignop_swap_gtest.cpp +++ b/unit_test/dynarray_construct_assignop_swap_gtest.cpp @@ -247,7 +247,6 @@ TEST_F(dynarrayConstructTest, constructInitList) ASSERT_EQ(AllocCounter::nAllocations, AllocCounter::nDeallocations); } -#if OEL_HAS_DEDUCTION_GUIDES TEST_F(dynarrayConstructTest, deductionGuides) { using Base = std::array; @@ -271,17 +270,12 @@ TEST_F(dynarrayConstructTest, deductionGuides) static_assert(std::is_same()); EXPECT_TRUE(sizeAndVal.at(1) == 1.f); } -#endif TEST_F(dynarrayConstructTest, constructContiguousRange) { std::string str = "AbCd"; -#if OEL_HAS_DEDUCTION_GUIDES dynarray test(str); static_assert(std::is_same()); -#else - dynarray test(str); -#endif EXPECT_TRUE( 0 == str.compare(0, 4, test.data(), test.size()) ); } @@ -364,7 +358,8 @@ TEST_F(dynarrayConstructTest, moveConstructWithAlloc) TEST_F(dynarrayConstructTest, moveConstructWithStatefulAlloc) { - testMoveConstruct< StatefulAllocator >({0}, {0}); + using Al = StatefulAllocator; + testMoveConstruct(Al(0), Al(0)); } struct NonAssignable @@ -469,7 +464,9 @@ TEST_F(dynarrayConstructTest, moveAssign) TEST_F(dynarrayConstructTest, moveAssignStatefulAlloc) { - testMoveAssign< StatefulAllocator >({0}, {1}); + using PropagateAlloc = StatefulAllocator; + static_assert(std::is_nothrow_move_assignable< dynarray >::value); + testMoveAssign(PropagateAlloc(0), PropagateAlloc(1)); } template @@ -540,6 +537,8 @@ using PmrDynarray = dynarray< T, std::pmr::polymorphic_allocator >; TEST_F(dynarrayConstructTest, moveAssignPolymorphicAlloc) { + static_assert(!std::is_nothrow_move_assignable_v< PmrDynarray >); + using Nested = PmrDynarray< PmrDynarray >; std::pmr::monotonic_buffer_resource bufRes{}; auto a = Nested(1); diff --git a/unit_test/dynarray_mutate_gtest.cpp b/unit_test/dynarray_mutate_gtest.cpp index 3e6e954a..f7aa8c15 100644 --- a/unit_test/dynarray_mutate_gtest.cpp +++ b/unit_test/dynarray_mutate_gtest.cpp @@ -31,7 +31,6 @@ struct throwingAlloc : public oel::allocator return oel::allocator::allocate(nObjs); } }; -static_assert( !oel::is_always_equal>::value, "?" ); // The fixture for testing dynarray. class dynarrayTest : public ::testing::Test @@ -185,7 +184,7 @@ struct ConstructFromRef template< typename T > ConstructFromRef(UPtr &, T &&, int(&)()) { - static_assert(std::is_const(), "?"); + static_assert(std::is_const()); } }; @@ -212,7 +211,7 @@ TEST_F(dynarrayTest, assign) EXPECT_EQ(VALUES[0], *test[0]); EXPECT_EQ(VALUES[1], *test[1]); - test.assign(view::subrange(src, src) | view::move()); + test.assign(view::subrange(src, src) | view::move); EXPECT_EQ(0U, test.size()); } EXPECT_EQ(MoveOnly::nConstructions, MoveOnly::nDestruct); @@ -565,20 +564,34 @@ TEST_F(dynarrayTest, moveOnlyIterator) { dynarray dest; { - std::istringstream ss{"1 2 3"}; - dest.append(std::views::istream(ss)); + std::istringstream ss{"1 2 3 4"}; + auto v = std::views::istream(ss); + + auto it = dest.append(view::counted(v.begin(), 3)); EXPECT_EQ(3u, dest.size()); EXPECT_EQ(1, dest[0]); EXPECT_EQ(2, dest[1]); EXPECT_EQ(3, dest[2]); + + it = dest.assign( view::subrange(std::move(it), v.end()) ); + EXPECT_EQ(v.end(), it); + EXPECT_EQ(1u, dest.size()); + EXPECT_EQ(4, dest[0]); } - { - std::istringstream ss{"2 1"}; - dest.assign(std::views::istream(ss)); - EXPECT_EQ(2u, dest.size()); - EXPECT_EQ(2, dest[0]); - EXPECT_EQ(1, dest[1]); - } + std::istringstream ss{"5 6 7 8"}; + auto v = std::views::istream(ss); + + auto it = dest.assign(view::counted(v.begin(), 2)); + EXPECT_EQ(2u, dest.size()); + EXPECT_EQ(5, dest[0]); + EXPECT_EQ(6, dest[1]); + + dest.insert_range(dest.begin() + 1, view::counted(std::move(it), 2)); + EXPECT_EQ(4u, dest.size()); + EXPECT_EQ(5, dest[0]); + EXPECT_EQ(7, dest[1]); + EXPECT_EQ(8, dest[2]); + EXPECT_EQ(6, dest[3]); } #endif diff --git a/unit_test/dynarray_other_gtest.cpp b/unit_test/dynarray_other_gtest.cpp index cf4ee4ae..3a3f64ec 100644 --- a/unit_test/dynarray_other_gtest.cpp +++ b/unit_test/dynarray_other_gtest.cpp @@ -3,6 +3,7 @@ #include "test_classes.h" #include "dynarray.h" +#include "optimize_ext/std_variant.h" #include "view/move.h" #include "gtest/gtest.h" @@ -12,7 +13,7 @@ #include -static_assert(oel::is_trivially_relocatable< std::pmr::polymorphic_allocator >::value, "?"); +static_assert(oel::is_trivially_relocatable< std::pmr::polymorphic_allocator >::value); #endif @@ -33,22 +34,22 @@ namespace static_assert(std::sized_sentinel_for); #endif - static_assert(std::is_same::value_type, float>(), "?"); + static_assert(std::is_same::value_type, float>()); - static_assert(oel::can_memmove_with::value, "?"); - static_assert(oel::can_memmove_with::value, "?"); - static_assert(oel::can_memmove_with< float *, std::move_iterator >::value, "?"); - static_assert( !oel::can_memmove_with::value, "?" ); + static_assert(oel::can_memmove_with); + static_assert(oel::can_memmove_with); + static_assert(oel::can_memmove_with< float *, std::move_iterator >); + static_assert( !oel::can_memmove_with ); - static_assert(std::is_trivially_copyable::value, "?"); - static_assert(std::is_convertible::value, "?"); - static_assert( !std::is_convertible::value, "?" ); + static_assert(std::is_trivially_copyable::value); + static_assert(std::is_convertible::value); + static_assert( !std::is_convertible::value ); static_assert(sizeof(dynarray) == 3 * sizeof(float *), "Not critical, this assert can be removed"); - static_assert(oel::allocator_can_realloc< TrackingAllocator >::value, "?"); - static_assert(!oel::allocator_can_realloc< oel::allocator >::value, "?"); + static_assert(oel::allocator_can_realloc< TrackingAllocator >); + static_assert(!oel::allocator_can_realloc< oel::allocator >); } TEST(dynarrayOtherTest, zeroBitRepresentation) @@ -92,7 +93,7 @@ TEST(dynarrayOtherTest, allocAndIterEquality) } using MyAllocStr = oel::allocator; -static_assert(std::is_trivially_copyable::value, "?"); +static_assert(std::is_trivially_copyable::value); TEST(dynarrayOtherTest, stdDequeWithOelAlloc) { @@ -131,10 +132,6 @@ TEST(dynarrayOtherTest, oelDynarrWithStdAlloc) EXPECT_EQ(MoveOnly::nConstructions, MoveOnly::nDestruct); } -#if __has_include() and (__cplusplus > 201500 or _HAS_CXX17) - -#include "optimize_ext/std_variant.h" - TEST(dynarrayOtherTest, stdVariant) { using Inner = std::conditional_t< oel::is_trivially_relocatable{}, std::string, dynarray >; @@ -150,7 +147,6 @@ TEST(dynarrayOtherTest, stdVariant) EXPECT_TRUE(std::strcmp( "abc", std::get(a[0]).data() ) == 0); EXPECT_EQ( 3.3, *std::get<0>(a[1]) ); } -#endif TEST(dynarrayOtherTest, withReferenceWrapper) { diff --git a/unit_test/forward_decl_test.cpp b/unit_test/forward_decl_test.cpp index 627dc8b8..5d747be6 100644 --- a/unit_test/forward_decl_test.cpp +++ b/unit_test/forward_decl_test.cpp @@ -12,7 +12,7 @@ oel::is_trivially_relocatable specify_trivial_relocate(Outer &&); #include "dynarray.h" -static_assert( !oel::is_trivially_relocatable::value, "?" ); +static_assert( !oel::is_trivially_relocatable::value ); void Outer::Foo(oel::dynarray & d) { d.max_size(); } diff --git a/unit_test/test_classes.h b/unit_test/test_classes.h index 5dbcc718..36fd7347 100644 --- a/unit_test/test_classes.h +++ b/unit_test/test_classes.h @@ -11,7 +11,7 @@ #ifndef HAS_STD_PMR - #if __has_include() and (__cplusplus > 201500 or _HAS_CXX17) + #if __has_include() #define HAS_STD_PMR 1 #else #define HAS_STD_PMR 0 @@ -168,8 +168,8 @@ struct TrivialDefaultConstruct TrivialDefaultConstruct() = default; TrivialDefaultConstruct(const TrivialDefaultConstruct &) {} }; -static_assert(std::is_trivially_default_constructible::value, "?"); -static_assert( !std::is_trivially_copyable::value, "?" ); +static_assert(std::is_trivially_default_constructible::value); +static_assert( !std::is_trivially_copyable::value ); struct NontrivialConstruct : MyCounter { @@ -183,7 +183,7 @@ struct NontrivialConstruct : MyCounter ~NontrivialConstruct() { ++nDestruct; } }; -static_assert( !std::is_trivially_default_constructible::value, "?" ); +static_assert( !std::is_trivially_default_constructible::value ); struct AllocCounter @@ -267,7 +267,7 @@ struct StatefulAllocator : std::conditional_t< UseConstruct, TrackingAllocator friend bool operator==(StatefulAllocator a, StatefulAllocator b) diff --git a/unit_test/util_gtest.cpp b/unit_test/util_gtest.cpp index 239847b1..ddecf2c7 100644 --- a/unit_test/util_gtest.cpp +++ b/unit_test/util_gtest.cpp @@ -15,99 +15,99 @@ namespace { - static_assert(oel::is_trivially_relocatable< std::pair> >(), "?"); - static_assert(oel::is_trivially_relocatable< std::tuple> >(), "?"); - static_assert(oel::is_trivially_relocatable< std::tuple<> >::value, "?"); + static_assert(oel::is_trivially_relocatable< std::pair> >()); + static_assert(oel::is_trivially_relocatable< std::tuple> >()); + static_assert(oel::is_trivially_relocatable< std::tuple<> >::value); struct NonTrivialDestruct { ~NonTrivialDestruct() { ; } }; - static_assert( !oel::is_trivially_relocatable< std::tuple >(), "?" ); + static_assert( !oel::is_trivially_relocatable< std::tuple >() ); #if (defined _CPPLIB_VER or defined _LIBCPP_VERSION or defined __GLIBCXX__) and !_GLIBCXX_USE_CXX11_ABI - static_assert(oel::is_trivially_relocatable< std::string >::value, "?"); + static_assert(oel::is_trivially_relocatable< std::string >::value); #endif -#ifndef OEL_NO_BOOST - static_assert(oel::is_trivially_relocatable< boost::circular_buffer> >(), "?"); +#ifdef OEL_HAS_BOOST + static_assert(oel::is_trivially_relocatable< boost::circular_buffer> >()); #endif struct alignas(32) Foo { int a[24]; }; - static_assert(alignof(oel::storage_for) == 32, "?"); - static_assert(sizeof(oel::storage_for) == sizeof(Foo), "?"); + static_assert(alignof(oel::storage_for) == 32); + static_assert(sizeof(oel::storage_for) == sizeof(Foo)); using ListI = std::list::iterator; - static_assert(!oel::can_memmove_with< int *, float * >::value, "?"); - static_assert(!oel::can_memmove_with< int *, std::set::iterator >(), "?"); - static_assert(!oel::can_memmove_with< int *, std::move_iterator >(), "?"); - static_assert(oel::can_memmove_with< std::array::iterator, std::move_iterator >(), "?"); + static_assert(!oel::can_memmove_with< int *, float * >); + static_assert(!oel::can_memmove_with< int *, std::set::iterator >); + static_assert(!oel::can_memmove_with< int *, std::move_iterator >); + static_assert(oel::can_memmove_with< std::array::iterator, std::move_iterator >); - static_assert(!oel::iter_is_random_access, "?"); + static_assert(!oel::iter_is_random_access); } TEST(utilTest, ForwardT) { using oel::_detail::ForwardT; - static_assert(std::is_same< ForwardT, double >::value, "?"); - static_assert(std::is_same< ForwardT, double >::value, "?"); - static_assert(std::is_same< ForwardT, double >::value, "?"); - static_assert(std::is_same< ForwardT, double >::value, "?"); - static_assert(std::is_same< ForwardT, double & >::value, "?"); + static_assert(std::is_same< ForwardT, double >::value); + static_assert(std::is_same< ForwardT, double >::value); + static_assert(std::is_same< ForwardT, double >::value); + static_assert(std::is_same< ForwardT, double >::value); + static_assert(std::is_same< ForwardT, double & >::value); - static_assert(std::is_same< ForwardT, int(&&)[1] >::value, "?"); - static_assert(std::is_same< ForwardT, int(&&)[1] >::value, "?"); - static_assert(std::is_same< ForwardT, const int(&)[1] >::value, "?"); + static_assert(std::is_same< ForwardT, int(&&)[1] >::value); + static_assert(std::is_same< ForwardT, int(&&)[1] >::value); + static_assert(std::is_same< ForwardT, const int(&)[1] >::value); using P = std::unique_ptr; - static_assert(std::is_same< ForwardT

, const P && >::value, "?"); - static_assert(std::is_same< ForwardT, const P && >::value, "?"); - static_assert(std::is_same< ForwardT

, P & >::value, "?"); - static_assert(std::is_same< ForwardT, const P & >::value, "?"); + static_assert(std::is_same< ForwardT

, const P && >::value); + static_assert(std::is_same< ForwardT, const P && >::value); + static_assert(std::is_same< ForwardT

, P & >::value); + static_assert(std::is_same< ForwardT, const P & >::value); // Small, non-trivial copy - static_assert(std::is_same< ForwardT, const TrivialRelocat && >(), "?"); - static_assert(std::is_same< ForwardT, const TrivialRelocat && >(), "?"); - static_assert(std::is_same< ForwardT, TrivialRelocat & >(), "?"); - static_assert(std::is_same< ForwardT, const TrivialRelocat & >(), "?"); + static_assert(std::is_same< ForwardT, const TrivialRelocat && >()); + static_assert(std::is_same< ForwardT, const TrivialRelocat && >()); + static_assert(std::is_same< ForwardT, TrivialRelocat & >()); + static_assert(std::is_same< ForwardT, const TrivialRelocat & >()); #ifdef _MSC_VER - static_assert(std::is_same< ForwardT

, P >::value, "?"); - static_assert(std::is_same< ForwardT

, P >::value, "?"); + static_assert(std::is_same< ForwardT

, P >::value); + static_assert(std::is_same< ForwardT

, P >::value); - static_assert(std::is_same< ForwardT, TrivialRelocat >::value, "?"); - static_assert(std::is_same< ForwardT, TrivialRelocat >::value, "?"); + static_assert(std::is_same< ForwardT, TrivialRelocat >::value); + static_assert(std::is_same< ForwardT, TrivialRelocat >::value); #else - static_assert(std::is_same< ForwardT

, P && >::value, "?"); - static_assert(std::is_same< ForwardT

, P && >::value, "?"); + static_assert(std::is_same< ForwardT

, P && >::value); + static_assert(std::is_same< ForwardT

, P && >::value); - static_assert(std::is_same< ForwardT, TrivialRelocat && >::value, "?"); - static_assert(std::is_same< ForwardT, TrivialRelocat && >::value, "?"); + static_assert(std::is_same< ForwardT, TrivialRelocat && >::value); + static_assert(std::is_same< ForwardT, TrivialRelocat && >::value); #endif #ifdef _MSC_VER using A = std::array; - static_assert(std::is_same< ForwardT, A && >::value, "?"); - static_assert(std::is_same< ForwardT, A && >::value, "?"); - static_assert(std::is_same< ForwardT, const A && >::value, "?"); - static_assert(std::is_same< ForwardT, const A && >::value, "?"); - static_assert(std::is_same< ForwardT, A & >::value, "?"); - static_assert(std::is_same< ForwardT, const A & >::value, "?"); + static_assert(std::is_same< ForwardT, A && >::value); + static_assert(std::is_same< ForwardT, A && >::value); + static_assert(std::is_same< ForwardT, const A && >::value); + static_assert(std::is_same< ForwardT, const A && >::value); + static_assert(std::is_same< ForwardT, A & >::value); + static_assert(std::is_same< ForwardT, const A & >::value); #else using A = std::array; - static_assert(std::is_same< ForwardT, A >::value, "?"); - static_assert(std::is_same< ForwardT, A >::value, "?"); - static_assert(std::is_same< ForwardT, A >::value, "?"); - static_assert(std::is_same< ForwardT, A >::value, "?"); - static_assert(std::is_same< ForwardT, A & >::value, "?"); + static_assert(std::is_same< ForwardT, A >::value); + static_assert(std::is_same< ForwardT, A >::value); + static_assert(std::is_same< ForwardT, A >::value); + static_assert(std::is_same< ForwardT, A >::value); + static_assert(std::is_same< ForwardT, A & >::value); #endif #if HAS_STD_PMR using Alloc = std::pmr::polymorphic_allocator; - static_assert(std::is_same< ForwardT, Alloc >::value, "?"); - static_assert(std::is_same< ForwardT, Alloc >::value, "?"); - static_assert(std::is_same< ForwardT, Alloc >::value, "?"); + static_assert(std::is_same< ForwardT, Alloc >::value); + static_assert(std::is_same< ForwardT, Alloc >::value); + static_assert(std::is_same< ForwardT, Alloc >::value); #endif } @@ -124,8 +124,8 @@ TEST(utilTest, ssize) using test = decltype( oel::ssize(DummyRange{0}) ); using test2 = decltype( oel::ssize(DummyRange{0}) ); - static_assert(std::is_same::value, "?"); - static_assert(std::is_same::value, "?"); + static_assert(std::is_same::value); + static_assert(std::is_same::value); } TEST(utilTest, indexValid) @@ -174,27 +174,27 @@ TEST(utilTest, toPointerContiguous) std::basic_string s; using P = decltype( to_pointer_contiguous(s.begin()) ); using CP = decltype( to_pointer_contiguous(s.cbegin()) ); - static_assert(std::is_same::value, "?"); - static_assert(std::is_same::value, "?"); + static_assert(std::is_same::value); + static_assert(std::is_same::value); #if _HAS_CXX17 std::string_view v; using Q = decltype( to_pointer_contiguous(v.begin()) ); - static_assert(std::is_same::value, "?"); + static_assert(std::is_same::value); #endif } std::array a; using P = decltype(to_pointer_contiguous( std::make_move_iterator(a.begin()) )); using CP = decltype( to_pointer_contiguous(a.cbegin()) ); - static_assert(std::is_same::value, "?"); - static_assert(std::is_same::value, "?"); + static_assert(std::is_same::value); + static_assert(std::is_same::value); #if __cpp_lib_concepts auto addr = &a[1]; dynarray_iterator it{addr, nullptr, 0}; auto result = std::to_address(it); - static_assert(std::is_same(), "?"); + static_assert(std::is_same()); EXPECT_EQ(addr, result); #endif } diff --git a/unit_test/view_gtest.cpp b/unit_test/view_gtest.cpp index 57c3bb4d..39c8d346 100644 --- a/unit_test/view_gtest.cpp +++ b/unit_test/view_gtest.cpp @@ -25,7 +25,7 @@ TEST(viewTest, basicView) { using BV = oel::basic_view; - static_assert(std::is_trivially_constructible::value, "?"); + static_assert(std::is_trivially_constructible::value); #if OEL_STD_RANGES static_assert(std::ranges::contiguous_range); @@ -46,7 +46,7 @@ TEST(viewTest, countedView) { using CV = oel::counted_view; - static_assert(std::is_trivially_constructible::value, "?"); + static_assert(std::is_trivially_constructible::value); #if OEL_STD_RANGES static_assert(std::ranges::contiguous_range); @@ -82,9 +82,9 @@ TEST(viewTest, viewTransformBasics) using IEmptyLambda = decltype(v.begin()); using IMoveOnly = decltype(itMoveOnly); - static_assert(std::is_same< IEmptyLambda::iterator_category, std::bidirectional_iterator_tag >(), "?"); - static_assert(std::is_same< IMoveOnly::iterator_category, std::input_iterator_tag >(), "?"); - static_assert(std::is_same< decltype(itMoveOnly++), void >(), "?"); + static_assert(std::is_same_v< IEmptyLambda::iterator_category, std::bidirectional_iterator_tag >); + static_assert(std::is_same_v< IMoveOnly::iterator_category, std::input_iterator_tag >); + static_assert(std::is_same_v< decltype(itMoveOnly++), void >); static_assert(sizeof(IEmptyLambda) == sizeof(Elem *), "Not critical, this assert can be removed"); #if OEL_STD_RANGES static_assert(std::ranges::bidirectional_range); @@ -114,8 +114,6 @@ TEST(viewTest, viewTransformBasics) EXPECT_FALSE(it != r + 0); } -#if __cplusplus > 201500 or _HAS_CXX17 - using StdArrInt2 = std::array; constexpr auto multBy2(StdArrInt2 a) @@ -140,7 +138,6 @@ void testViewTransformConstexpr() static_assert(res[1] == 6); } -#endif struct Square { int operator()(int i) const @@ -168,7 +165,7 @@ TEST(viewTest, viewTransformSizedRange) EXPECT_EQ(4, dest[2]); EXPECT_EQ(li.end(), last.base()); - static_assert(std::is_same< decltype(last)::iterator_category, std::forward_iterator_tag >(), "?"); + static_assert(std::is_same_v< decltype(last)::iterator_category, std::forward_iterator_tag >); } TEST(viewTest, viewTransformNonSizedRange) @@ -190,7 +187,7 @@ TEST(viewTest, viewTransformMutableLambda) auto v = view::transform(dummy, iota); using I = decltype(v.begin()); - static_assert(std::is_same(), "?"); + static_assert(std::is_same_v); #if OEL_STD_RANGES static_assert(std::input_or_output_iterator); static_assert(std::ranges::range); @@ -231,8 +228,8 @@ TEST(viewTest, viewMoveEndDifferentType) { auto nonEmpty = [i = -1](int j) { return i + j; }; int src[1]; - oel::transform_iterator it{nonEmpty, src + 0}; - auto v = view::subrange(it, src + 1) | view::move(); + oel::transform_iterator it{nonEmpty, src + 0}; + auto v = view::subrange(it, src + 1) | view::move; EXPECT_NE(v.begin(), v.end()); EXPECT_EQ(src + 1, v.end().base()); @@ -243,7 +240,7 @@ TEST(viewTest, viewMoveEndDifferentType) TEST(viewTest, viewMoveMutableEmptyAndSize) { int src[] {0, 1}; - auto v = src | std::views::drop_while([](int i) { return i <= 0; }) | view::move(); + auto v = src | std::views::drop_while([](int i) { return i <= 0; }) | view::move; EXPECT_FALSE(v.empty()); EXPECT_EQ(1U, v.size()); } @@ -253,9 +250,9 @@ TEST(viewTest, chainWithStd) auto f = [](int i) { return -i; }; int src[] {0, 1}; - void( src | view::move() | std::views::drop_while([](int i) { return i <= 0; }) ); + void( src | view::move | std::views::drop_while([](int i) { return i <= 0; }) ); void( src | std::views::reverse | view::transform(f) | std::views::take(1) ); - void( src | view::transform(f) | std::views::drop(1) | view::move() ); + void( src | view::transform(f) | std::views::drop(1) | view::move ); } #endif diff --git a/util.h b/util.h index f669e697..d874e373 100644 --- a/util.h +++ b/util.h @@ -42,7 +42,7 @@ constexpr auto ssize(SizedRangeLike && r) template< typename Integral, typename SizedRangeLike > constexpr bool index_valid(SizedRangeLike & r, Integral index) { - static_assert( sizeof(Integral) >= sizeof _detail::Size(r) or std::is_unsigned::value, + static_assert( sizeof(Integral) >= sizeof _detail::Size(r) or std::is_unsigned_v, "Mismatched index type, please use a wider integer (or unsigned)" ); return as_unsigned(index) < as_unsigned(_detail::Size(r)); } @@ -53,32 +53,35 @@ struct reserve_tag { explicit constexpr reserve_tag() {} }; -constexpr reserve_tag reserve; //!< An instance of reserve_tag for convenience +inline constexpr reserve_tag reserve; //!< An instance of reserve_tag for convenience //! Tag to specify default initialization struct for_overwrite_t { explicit constexpr for_overwrite_t() {} }; -constexpr for_overwrite_t for_overwrite; //!< An instance of for_overwrite_t for convenience +inline constexpr for_overwrite_t for_overwrite; //!< An instance of for_overwrite_t for convenience -//////////////////////////////////////////////////////////////////////////////// -// -// The rest of the file is not for users (implementation) +//! Same as `begin(range)` with a previous `using std::begin;`. For use in classes with a member named begin +inline constexpr auto adl_begin = + [](auto && range) -> decltype(begin(range)) { return begin(range); }; +//! Same as `end(range)` with a previous `using std::end;`. For use in classes with a member named end +inline constexpr auto adl_end = + [](auto && range) -> decltype(end(range)) { return end(range); }; -// Cannot do ADL `begin(r)` in implementation of class with begin member -template< typename Range > OEL_ALWAYS_INLINE -constexpr auto adl_begin(Range && r) -> decltype(begin(r)) { return begin(r); } +//////////////////////////////////////////////////////////////////////////////// +// +// The rest of the file is not for users (implementation) template< typename T > struct #ifdef __GNUC__ - __attribute__((may_alias)) + [[gnu::may_alias]] #endif storage_for { @@ -89,7 +92,7 @@ struct namespace _detail { template< typename T, typename U, - bool = std::is_empty::value > + bool = std::is_empty_v > struct TightPair { T first; diff --git a/view/all.h b/view/all.h index bd58174a..b5a76dee 100644 --- a/view/all.h +++ b/view/all.h @@ -14,37 +14,46 @@ namespace oel { -//! View creation functions. The API tries to mimic views in std::ranges -namespace view +namespace _detail { -#if OEL_STD_RANGES - template< std::ranges::viewable_range R > - constexpr auto all(R && r) + struct All + { + #if OEL_STD_RANGES + template< std::ranges::viewable_range R > + constexpr auto operator()(R && r) const { return std::views::all(static_cast(r)); } -#endif + #endif -template< typename I, typename S > -constexpr auto all(basic_view v) { return v; } + template< typename I, typename S > + constexpr auto operator()(basic_view v) const { return v; } -template< typename I > -constexpr auto all(counted_view v) { return v; } + template< typename I > + constexpr auto operator()(counted_view v) const { return v; } -template< typename SizedRange > -constexpr auto all(SizedRange & r) --> decltype( view::counted(begin(r), oel::ssize(r)) ) - { return view::counted(begin(r), oel::ssize(r)); } + template< typename SizedRange > + constexpr auto operator()(SizedRange & r) const + -> decltype( view::counted(begin(r), oel::ssize(r)) ) + { return view::counted(begin(r), oel::ssize(r)); } -template< typename Range, typename... None > -constexpr auto all(Range & r, None...) - { - return view::subrange(begin(r), end(r)); - } + template< typename Range, typename... None > + constexpr auto operator()(Range & r, None...) const + { + return view::subrange(begin(r), end(r)); + } + + template< typename R > + void operator()(R &&) const = delete; + }; +} -template< typename R > -constexpr void all(R &&) = delete; +//! View creation functions. The API tries to mimic views in std::ranges +namespace view +{ +//! Substitute for std::views::all +inline constexpr _detail::All all; } } // oel diff --git a/view/counted.h b/view/counted.h index 75545802..d0fe7a44 100644 --- a/view/counted.h +++ b/view/counted.h @@ -49,9 +49,9 @@ class counted_view namespace view { -//! Create a counted_view from iterator and count, with type deduced from first -template< typename Iterator > -constexpr counted_view counted(Iterator first, iter_difference_t n) { return {std::move(first), n}; } +//! Create a counted_view from iterator and count (convertible to iter_difference_t for iterator) +inline constexpr auto counted = + [](auto iterator, auto count) { return counted_view(std::move(iterator), count); }; } diff --git a/view/detail/misc.h b/view/detail/misc.h index b0ea8748..d7fa9697 100644 --- a/view/detail/misc.h +++ b/view/detail/misc.h @@ -8,28 +8,25 @@ #include "../../auxi/type_traits.h" -namespace oel -{ -namespace _detail -{ - template< typename T, - enable_if< std::is_copy_constructible::value > = 0 - > OEL_ALWAYS_INLINE - constexpr const T & MoveIfNotCopyable(T & ob) { return ob; } - template< typename T, typename... None > - constexpr T MoveIfNotCopyable(T & ob, None...) +namespace oel::_detail +{ + template< typename T > + constexpr T MoveIfNotCopyable(T & ob) { - return static_cast(ob); + if constexpr (std::is_copy_constructible_v) + return ob; + else + return static_cast(ob); } template< typename T, - bool = std::is_move_assignable::value > + bool = std::is_move_assignable_v > class AssignableWrap { - static_assert( std::is_trivially_copy_constructible::value and std::is_trivially_destructible::value, + static_assert( std::is_trivially_copy_constructible_v and std::is_trivially_destructible_v, "The user-supplied function must be move assignable, or trivially copy constructible and trivially destructible" ); union Impl @@ -65,7 +62,7 @@ namespace _detail }; public: - using Type = std::conditional_t< std::is_empty::value, ImplEmpty, Impl >; + using Type = std::conditional_t< std::is_empty_v, ImplEmpty, Impl >; }; template< typename T > @@ -74,6 +71,4 @@ namespace _detail public: using Type = T; }; -} - -} // oel +} \ No newline at end of file diff --git a/view/move.h b/view/move.h index 09f43658..685f318a 100644 --- a/view/move.h +++ b/view/move.h @@ -66,8 +66,7 @@ namespace _detail template< typename InputRange > friend constexpr auto operator |(InputRange && r, MovePartial) { - using V = decltype(view::all( static_cast(r) )); - return MoveView{view::all( static_cast(r) )}; + return MoveView{view::all( static_cast(r) )}; } }; } @@ -76,11 +75,24 @@ namespace _detail namespace view { -//! Wrap an input range with std::move_iterator (and conditionally std::move_sentinel), using operator | (like std::views) -constexpr auto move() { return _detail::MovePartial{}; } -//! Create a view with std::move_iterator (and conditionally std::move_sentinel) from a range, normal function style -template< typename InputRange > -constexpr auto move(InputRange && r) { return static_cast(r) | _detail::MovePartial{}; } +struct _moveFn +{ + /** @brief Create view, for chaining like std::views + @code + std::string moveFrom[2] {"abc", "def"}; + dynarray movedStrings(moveFrom | view::move); + @endcode */ + template< typename InputRange > + friend constexpr auto operator |(InputRange && r, _moveFn) + { + return _detail::MoveView{view::all( static_cast(r) )}; + } + //! Same as `std::views::as_rvalue(r)` (C++23) + template< typename InputRange > + constexpr auto operator()(InputRange && r) const { return static_cast(r) | _moveFn{}; } +}; +//! Very similar to views::move in the Range-v3 library and std::views::as_rvalue +inline constexpr _moveFn move; } diff --git a/view/subrange.h b/view/subrange.h index 6476e504..50e2ad59 100644 --- a/view/subrange.h +++ b/view/subrange.h @@ -50,8 +50,8 @@ namespace view { //! Create a basic_view from iterator pair, or iterator and sentinel -template< typename Iterator, typename Sentinel > -constexpr basic_view subrange(Iterator first, Sentinel last) { return {std::move(first), last}; } +inline constexpr auto subrange = + [](auto first, auto last) { return basic_view{std::move(first), last}; }; } diff --git a/view/transform.h b/view/transform.h index 8337bc36..719920ff 100644 --- a/view/transform.h +++ b/view/transform.h @@ -24,7 +24,7 @@ namespace _detail TightPair< View, typename _detail::AssignableWrap::Type > _m; template< typename F_ = Func, - enable_if< std::is_empty::value > = 0 + enable_if< std::is_empty_v > = 0 > constexpr _iter _makeSent(iterator_t last) { @@ -74,8 +74,7 @@ namespace _detail template< typename Range > friend constexpr auto operator |(Range && r, TransfPartial t) { - using V = decltype(view::all( static_cast(r) )); - return TransformView{view::all( static_cast(r) ), std::move(t)._f}; + return TransformView{view::all( static_cast(r) ), std::move(t)._f}; } }; } @@ -84,23 +83,27 @@ namespace _detail namespace view { -/** @brief Given a source range, transform each element when dereferenced, using operator | (like std::views) -@code -std::bitset<8> arr[] { 3, 5, 7, 11 }; -dynarray result( arr | view::transform([](const auto & bs) { return bs.to_string(); }) ); -@endcode +struct _transformFn +{ + //! Used with operator | + template< typename UnaryFunc > + constexpr auto operator()(UnaryFunc f) const { return _detail::TransfPartial{std::move(f)}; } + + template< typename Range, typename UnaryFunc > + constexpr auto operator()(Range && r, UnaryFunc f) const + { + return static_cast(r) | (*this)(std::move(f)); + } +}; +/** @brief Similar to std::views::transform +* * Unlike std::views::transform, copies or moves the function into the iterator rather than * storing it just in the view, thus saving one indirection when dereferencing the iterator. * When used in OE-Lib, the function does not need to model std::regular_invocable, -* meaning it's all right to return different results for the same input. */ -template< typename UnaryFunc > -constexpr auto transform(UnaryFunc f) { return _detail::TransfPartial{std::move(f)}; } -/* -* @brief Similar to `std::views::transform(r, f)` +* meaning it's all right to return different results for the same input. * -* See view::transform(UnaryFunc) above. */ -template< typename UnaryFunc, typename Range > -constexpr auto transform(Range && r, UnaryFunc f) { return static_cast(r) | view::transform(std::move(f)); } +* https://en.cppreference.com/w/cpp/ranges/transform_view */ +inline constexpr _transformFn transform; } diff --git a/view/transform_iterator.h b/view/transform_iterator.h index f2100115..2b2369ad 100644 --- a/view/transform_iterator.h +++ b/view/transform_iterator.h @@ -30,7 +30,7 @@ class transform_iterator public: using iterator_category = std::conditional_t< - std::is_copy_constructible::value, + std::is_copy_constructible_v, std::conditional_t< _isBidirectional, std::bidirectional_iterator_tag, @@ -63,20 +63,18 @@ class transform_iterator constexpr transform_iterator & operator++() OEL_ALWAYS_INLINE { ++_m.first; return *this; } //! Post-increment: return type is transform_iterator if iterator_category is-a forward_iterator_tag, else void - template< typename T = transform_iterator, - enable_if< iter_is_forward > = 0 - > - constexpr transform_iterator operator++(int) & + constexpr auto operator++(int) & { - auto tmp = *this; - ++_m.first; - return tmp; + if constexpr (iter_is_forward) + { + auto tmp = *this; + ++_m.first; + return tmp; + } + else + { ++_m.first; + } } - template< typename T = transform_iterator, - enable_if< ! iter_is_forward > = 0 - > OEL_ALWAYS_INLINE - constexpr void operator++(int) & { ++_m.first; } - constexpr transform_iterator & operator--() OEL_ALWAYS_INLINE OEL_REQUIRES(_isBidirectional) { --_m.first; return *this; } @@ -121,7 +119,7 @@ class transform_iterator }; template< typename F, typename I > -constexpr bool disable_sized_sentinel_for< transform_iterator, transform_iterator > +inline constexpr bool disable_sized_sentinel_for< transform_iterator, transform_iterator > = !iter_is_random_access; } // namespace oel