From 6fde8b52b243eea3025ae69318227264173f4c95 Mon Sep 17 00:00:00 2001 From: Pencilcaseman Date: Thu, 17 Aug 2023 18:10:08 +0100 Subject: [PATCH] Align storage pointers --- librapid/include/librapid/array/storage.hpp | 1704 +++++++++---------- 1 file changed, 851 insertions(+), 853 deletions(-) diff --git a/librapid/include/librapid/array/storage.hpp b/librapid/include/librapid/array/storage.hpp index 64a360f8..453dcf2a 100644 --- a/librapid/include/librapid/array/storage.hpp +++ b/librapid/include/librapid/array/storage.hpp @@ -7,875 +7,873 @@ */ namespace librapid { - namespace typetraits { - template - struct TypeInfo> { - static constexpr bool isLibRapidType = true; - using Scalar = Scalar_; - using Backend = backend::CPU; - }; - - template - struct TypeInfo> { - static constexpr bool isLibRapidType = true; - using Scalar = Scalar_; - using Backend = backend::CPU; - }; - - LIBRAPID_DEFINE_AS_TYPE(typename Scalar, Storage); - } // namespace typetraits - - template - class Storage { - public: - using Scalar = Scalar_; - using Packet = typename typetraits::TypeInfo::Packet; - static constexpr uint64_t packetWidth = typetraits::TypeInfo::packetWidth; - using RawPointer = Scalar *; - using ConstRawPointer = const Scalar *; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using Reference = Scalar &; - using ConstReference = const Scalar &; - using SizeType = size_t; - using DifferenceType = ptrdiff_t; - using Iterator = RawPointer; - using ConstIterator = ConstRawPointer; - using ReverseIterator = std::reverse_iterator; - using ConstReverseIterator = std::reverse_iterator; - - /// Default constructor - Storage() = default; - - /// Create a Storage object with \p size elements - /// \param size Number of elements to allocate - LIBRAPID_ALWAYS_INLINE explicit Storage(SizeType size); - - LIBRAPID_ALWAYS_INLINE explicit Storage(Scalar *begin, Scalar *end, bool ownsData); - - /// Create a Storage object with \p size elements, each initialized - /// to \p value. - /// \param size Number of elements to allocate - /// \param value Value to initialize each element to - LIBRAPID_ALWAYS_INLINE Storage(SizeType size, ConstReference value); - - /// Create a Storage object from another Storage object. Additionally - /// a custom allocator can be used. The data is **NOT** copied -- it is referenced - /// by the new Storage object. For a deep copy, use the ``copy()`` method. - /// \param other Storage object to copy - LIBRAPID_ALWAYS_INLINE Storage(const Storage &other); - - /// Move a Storage object into this object. - /// \param other Storage object to move - LIBRAPID_ALWAYS_INLINE Storage(Storage &&other) noexcept; - - /// Create a Storage object from an std::initializer_list - /// \tparam V Type of the elements in the initializer list - /// \param list Initializer list to copy - /// \param alloc Allocator to use - template - LIBRAPID_ALWAYS_INLINE Storage(const std::initializer_list &list); - - /// Create a Storage object from a std::vector - /// \tparam V Type of the elements in the vector - /// \param vec Vector to copy - template - LIBRAPID_ALWAYS_INLINE explicit Storage(const std::vector &vec); - - template - static Storage fromData(const std::initializer_list &vec); - - template - static Storage fromData(const std::vector &vec); - - /// Assignment operator for a Storage object - /// \param other Storage object to copy - /// \return *this - LIBRAPID_ALWAYS_INLINE Storage &operator=(const Storage &other); - - /// Move assignment operator for a Storage object - /// \param other Storage object to move - /// \return *this - LIBRAPID_ALWAYS_INLINE Storage &operator=(Storage &&other) LIBRAPID_RELEASE_NOEXCEPT; - - /// Free a Storage object - ~Storage(); - - /// \brief Set this storage object to reference the same data as \p other - /// \param other Storage object to reference - void set(const Storage &other); - - /// \brief Return a Storage object on the host with the same data as this Storage object - /// (mainly for use with CUDA or OpenCL) - /// \return - Storage toHostStorage() const; - - /// \brief Same as `toHostStorage()` but does not necessarily copy the data - /// \return Storage object on the host - Storage toHostStorageUnsafe() const; - - /// \brief Create a deep copy of this Storage object - /// \return Deep copy of this Storage object - Storage copy() const; - - template - static ShapeType defaultShape(); - - /// Resize a Storage object to \p size elements. Existing elements - /// are preserved. - /// \param size New size of the Storage object - LIBRAPID_ALWAYS_INLINE void resize(SizeType newSize); - - /// Resize a Storage object to \p size elements. Existing elements - /// are not preserved - /// \param size New size of the Storage object - LIBRAPID_ALWAYS_INLINE void resize(SizeType newSize, int); - - /// Return the number of elements in the Storage object - /// \return - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SizeType size() const noexcept; - - /// Const access to the element at index \p index - /// \param index Index of the element to access - /// \return Const reference to the element at index \p index - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReference operator[](SizeType index) const; - - /// Access to the element at index \p index - /// \param index Index of the element to access - /// \return Reference to the element at index \p index - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Reference operator[](SizeType index); - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Pointer data() const noexcept; - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE RawPointer begin() noexcept; - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE RawPointer end() noexcept; - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator begin() const noexcept; - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator end() const noexcept; - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator cbegin() const noexcept; - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator cend() const noexcept; - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ReverseIterator rbegin() noexcept; - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ReverseIterator rend() noexcept; - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator rbegin() const noexcept; - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator rend() const noexcept; - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator crbegin() const noexcept; - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator crend() const noexcept; - - private: - /// Copy data from \p begin to \p end into this Storage object - /// \tparam P Pointer type - /// \param begin Beginning of data to copy - /// \param end End of data to copy - template - LIBRAPID_ALWAYS_INLINE void initData(P begin, P end); - - template - LIBRAPID_ALWAYS_INLINE void initData(P begin, SizeType size); - - /// Resize the Storage Object to \p newSize elements, retaining existing - /// data. - /// \param newSize New size of the Storage object - LIBRAPID_ALWAYS_INLINE void resizeImpl(SizeType newSize, int); - - /// Resize the Storage object to \p newSize elements. Note this does not - /// initialize the new elements or maintain existing data. - /// \param newSize New size of the Storage object - LIBRAPID_ALWAYS_INLINE void resizeImpl(SizeType newSize); - - // #if defined(LIBRAPID_NATIVE_ARCH) && !defined(LIBRAPID_APPLE) - // alignas(LIBRAPID_DEFAULT_MEM_ALIGN) Pointer m_begin = nullptr; - // #else - // Pointer m_begin = nullptr; // Pointer to the beginning of the data - // #endif - - Pointer m_begin = nullptr; - - SizeType m_size = 0; // Number of elements in the Storage object - bool m_ownsData = true; // Whether this Storage object owns the data it points to - }; - - template - class FixedStorage { - public: - using Scalar = Scalar_; - using Pointer = Scalar *; - using ConstPointer = const Scalar *; - using Reference = Scalar &; - using ConstReference = const Scalar &; - using SizeType = size_t; - using DifferenceType = ptrdiff_t; - static constexpr SizeType Size = product(); - using Iterator = typename std::array()>::iterator; - using ConstIterator = typename std::array()>::const_iterator; - using ReverseIterator = std::reverse_iterator; - using ConstReverseIterator = std::reverse_iterator; - - /// Default constructor - FixedStorage(); - - /// Create a FixedStorage object filled with \p value - /// \param value Value to fill the FixedStorage object with - LIBRAPID_ALWAYS_INLINE explicit FixedStorage(const Scalar &value); - - /// Create a FixedStorage object from another FixedStorage object - /// \param other FixedStorage object to copy - LIBRAPID_ALWAYS_INLINE FixedStorage(const FixedStorage &other); - - /// Move constructor for a FixedStorage object - /// \param other FixedStorage object to move - LIBRAPID_ALWAYS_INLINE FixedStorage(FixedStorage &&other) noexcept; - - /// Create a FixedStorage object from a std::initializer_list - /// \tparam V Type of the elements in the initializer list - /// \param list Initializer list to copy - LIBRAPID_ALWAYS_INLINE explicit FixedStorage(const std::initializer_list &list); - - /// Create a FixedStorage object from a std::vector - /// \tparam V Type of the elements in the vector - /// \param vec Vector to copy - LIBRAPID_ALWAYS_INLINE explicit FixedStorage(const std::vector &vec); - - /// Assignment operator for a FixedStorage object - /// \param other FixedStorage object to copy - /// \return *this - LIBRAPID_ALWAYS_INLINE FixedStorage &operator=(const FixedStorage &other); - - /// Move assignment operator for a FixedStorage object - /// \param other FixedStorage object to move - /// \return *this - LIBRAPID_ALWAYS_INLINE FixedStorage &operator=(FixedStorage &&other) noexcept; - - /// Free a FixedStorage object - ~FixedStorage() = default; - - template - static ShapeType defaultShape(); - - /// Resize a Storage object to \p size elements. Existing elements - /// are preserved. - /// \param size New size of the Storage object - LIBRAPID_ALWAYS_INLINE void resize(SizeType newSize); - - /// Resize a Storage object to \p size elements. Existing elements - /// are not preserved - /// \param size New size of the Storage object - LIBRAPID_ALWAYS_INLINE void resize(SizeType newSize, int); - - /// Return the number of elements in the FixedStorage object - /// \return Number of elements in the FixedStorage object - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SizeType size() const noexcept; - - /// \brief Create a copy of the FixedStorage object - /// \return Copy of the FixedStorage object - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE FixedStorage copy() const; - - /// Const access to the element at index \p index - /// \param index Index of the element to access - /// \return Const reference to the element at index \p index - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReference operator[](SizeType index) const; - - /// Access to the element at index \p index - /// \param index Index of the element to access - /// \return Reference to the element at index \p index - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Reference operator[](SizeType index); - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Pointer data() const noexcept; - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Iterator begin() noexcept; - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Iterator end() noexcept; - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator begin() const noexcept; - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator end() const noexcept; - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator cbegin() const noexcept; - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator cend() const noexcept; - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ReverseIterator rbegin() noexcept; - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ReverseIterator rend() noexcept; - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator rbegin() const noexcept; - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator rend() const noexcept; - - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator crbegin() const noexcept; - LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator crend() const noexcept; - - private: + namespace typetraits { + template + struct TypeInfo> { + static constexpr bool isLibRapidType = true; + using Scalar = Scalar_; + using Backend = backend::CPU; + }; + + template + struct TypeInfo> { + static constexpr bool isLibRapidType = true; + using Scalar = Scalar_; + using Backend = backend::CPU; + }; + + LIBRAPID_DEFINE_AS_TYPE(typename Scalar, Storage); + } // namespace typetraits + + template + class Storage { + public: + using Scalar = Scalar_; + using Packet = typename typetraits::TypeInfo::Packet; + static constexpr uint64_t packetWidth = typetraits::TypeInfo::packetWidth; + using RawPointer = Scalar *; + using ConstRawPointer = const Scalar *; + using Pointer = std::shared_ptr; + using ConstPointer = std::shared_ptr; + using Reference = Scalar &; + using ConstReference = const Scalar &; + using SizeType = size_t; + using DifferenceType = ptrdiff_t; + using Iterator = RawPointer; + using ConstIterator = ConstRawPointer; + using ReverseIterator = std::reverse_iterator; + using ConstReverseIterator = std::reverse_iterator; + + /// Default constructor + Storage() = default; + + /// Create a Storage object with \p size elements + /// \param size Number of elements to allocate + LIBRAPID_ALWAYS_INLINE explicit Storage(SizeType size); + + LIBRAPID_ALWAYS_INLINE explicit Storage(Scalar *begin, Scalar *end, bool ownsData); + + /// Create a Storage object with \p size elements, each initialized + /// to \p value. + /// \param size Number of elements to allocate + /// \param value Value to initialize each element to + LIBRAPID_ALWAYS_INLINE Storage(SizeType size, ConstReference value); + + /// Create a Storage object from another Storage object. Additionally + /// a custom allocator can be used. The data is **NOT** copied -- it is referenced + /// by the new Storage object. For a deep copy, use the ``copy()`` method. + /// \param other Storage object to copy + LIBRAPID_ALWAYS_INLINE Storage(const Storage &other); + + /// Move a Storage object into this object. + /// \param other Storage object to move + LIBRAPID_ALWAYS_INLINE Storage(Storage &&other) noexcept; + + /// Create a Storage object from an std::initializer_list + /// \tparam V Type of the elements in the initializer list + /// \param list Initializer list to copy + /// \param alloc Allocator to use + template + LIBRAPID_ALWAYS_INLINE Storage(const std::initializer_list &list); + + /// Create a Storage object from a std::vector + /// \tparam V Type of the elements in the vector + /// \param vec Vector to copy + template + LIBRAPID_ALWAYS_INLINE explicit Storage(const std::vector &vec); + + template + static Storage fromData(const std::initializer_list &vec); + + template + static Storage fromData(const std::vector &vec); + + /// Assignment operator for a Storage object + /// \param other Storage object to copy + /// \return *this + LIBRAPID_ALWAYS_INLINE Storage &operator=(const Storage &other); + + /// Move assignment operator for a Storage object + /// \param other Storage object to move + /// \return *this + LIBRAPID_ALWAYS_INLINE Storage &operator=(Storage &&other) LIBRAPID_RELEASE_NOEXCEPT; + + /// Free a Storage object + ~Storage(); + + /// \brief Set this storage object to reference the same data as \p other + /// \param other Storage object to reference + void set(const Storage &other); + + /// \brief Return a Storage object on the host with the same data as this Storage object + /// (mainly for use with CUDA or OpenCL) + /// \return + Storage toHostStorage() const; + + /// \brief Same as `toHostStorage()` but does not necessarily copy the data + /// \return Storage object on the host + Storage toHostStorageUnsafe() const; + + /// \brief Create a deep copy of this Storage object + /// \return Deep copy of this Storage object + Storage copy() const; + + template + static ShapeType defaultShape(); + + /// Resize a Storage object to \p size elements. Existing elements + /// are preserved. + /// \param size New size of the Storage object + LIBRAPID_ALWAYS_INLINE void resize(SizeType newSize); + + /// Resize a Storage object to \p size elements. Existing elements + /// are not preserved + /// \param size New size of the Storage object + LIBRAPID_ALWAYS_INLINE void resize(SizeType newSize, int); + + /// Return the number of elements in the Storage object + /// \return + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SizeType size() const noexcept; + + /// Const access to the element at index \p index + /// \param index Index of the element to access + /// \return Const reference to the element at index \p index + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReference operator[](SizeType index) const; + + /// Access to the element at index \p index + /// \param index Index of the element to access + /// \return Reference to the element at index \p index + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Reference operator[](SizeType index); + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Pointer data() const noexcept; + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE RawPointer begin() noexcept; + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE RawPointer end() noexcept; + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator begin() const noexcept; + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator end() const noexcept; + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator cbegin() const noexcept; + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator cend() const noexcept; + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ReverseIterator rbegin() noexcept; + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ReverseIterator rend() noexcept; + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator rbegin() const noexcept; + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator rend() const noexcept; + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator crbegin() const noexcept; + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator crend() const noexcept; + + private: + /// Copy data from \p begin to \p end into this Storage object + /// \tparam P Pointer type + /// \param begin Beginning of data to copy + /// \param end End of data to copy + template + LIBRAPID_ALWAYS_INLINE void initData(P begin, P end); + + template + LIBRAPID_ALWAYS_INLINE void initData(P begin, SizeType size); + + /// Resize the Storage Object to \p newSize elements, retaining existing + /// data. + /// \param newSize New size of the Storage object + LIBRAPID_ALWAYS_INLINE void resizeImpl(SizeType newSize, int); + + /// Resize the Storage object to \p newSize elements. Note this does not + /// initialize the new elements or maintain existing data. + /// \param newSize New size of the Storage object + LIBRAPID_ALWAYS_INLINE void resizeImpl(SizeType newSize); + +#if defined(LIBRAPID_NATIVE_ARCH) && !defined(LIBRAPID_APPLE) + alignas(LIBRAPID_DEFAULT_MEM_ALIGN) Pointer m_begin = nullptr; +#else + Pointer m_begin = nullptr; // Pointer to the beginning of the data +#endif + + SizeType m_size = 0; // Number of elements in the Storage object + bool m_ownsData = true; // Whether this Storage object owns the data it points to + }; + + template + class FixedStorage { + public: + using Scalar = Scalar_; + using Pointer = Scalar *; + using ConstPointer = const Scalar *; + using Reference = Scalar &; + using ConstReference = const Scalar &; + using SizeType = size_t; + using DifferenceType = ptrdiff_t; + static constexpr SizeType Size = product(); + using Iterator = typename std::array()>::iterator; + using ConstIterator = typename std::array()>::const_iterator; + using ReverseIterator = std::reverse_iterator; + using ConstReverseIterator = std::reverse_iterator; + + /// Default constructor + FixedStorage(); + + /// Create a FixedStorage object filled with \p value + /// \param value Value to fill the FixedStorage object with + LIBRAPID_ALWAYS_INLINE explicit FixedStorage(const Scalar &value); + + /// Create a FixedStorage object from another FixedStorage object + /// \param other FixedStorage object to copy + LIBRAPID_ALWAYS_INLINE FixedStorage(const FixedStorage &other); + + /// Move constructor for a FixedStorage object + /// \param other FixedStorage object to move + LIBRAPID_ALWAYS_INLINE FixedStorage(FixedStorage &&other) noexcept; + + /// Create a FixedStorage object from a std::initializer_list + /// \tparam V Type of the elements in the initializer list + /// \param list Initializer list to copy + LIBRAPID_ALWAYS_INLINE explicit FixedStorage(const std::initializer_list &list); + + /// Create a FixedStorage object from a std::vector + /// \tparam V Type of the elements in the vector + /// \param vec Vector to copy + LIBRAPID_ALWAYS_INLINE explicit FixedStorage(const std::vector &vec); + + /// Assignment operator for a FixedStorage object + /// \param other FixedStorage object to copy + /// \return *this + LIBRAPID_ALWAYS_INLINE FixedStorage &operator=(const FixedStorage &other); + + /// Move assignment operator for a FixedStorage object + /// \param other FixedStorage object to move + /// \return *this + LIBRAPID_ALWAYS_INLINE FixedStorage &operator=(FixedStorage &&other) noexcept; + + /// Free a FixedStorage object + ~FixedStorage() = default; + + template + static ShapeType defaultShape(); + + /// Resize a Storage object to \p size elements. Existing elements + /// are preserved. + /// \param size New size of the Storage object + LIBRAPID_ALWAYS_INLINE void resize(SizeType newSize); + + /// Resize a Storage object to \p size elements. Existing elements + /// are not preserved + /// \param size New size of the Storage object + LIBRAPID_ALWAYS_INLINE void resize(SizeType newSize, int); + + /// Return the number of elements in the FixedStorage object + /// \return Number of elements in the FixedStorage object + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SizeType size() const noexcept; + + /// \brief Create a copy of the FixedStorage object + /// \return Copy of the FixedStorage object + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE FixedStorage copy() const; + + /// Const access to the element at index \p index + /// \param index Index of the element to access + /// \return Const reference to the element at index \p index + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReference operator[](SizeType index) const; + + /// Access to the element at index \p index + /// \param index Index of the element to access + /// \return Reference to the element at index \p index + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Reference operator[](SizeType index); + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Pointer data() const noexcept; + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Iterator begin() noexcept; + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Iterator end() noexcept; + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator begin() const noexcept; + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator end() const noexcept; + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator cbegin() const noexcept; + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstIterator cend() const noexcept; + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ReverseIterator rbegin() noexcept; + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ReverseIterator rend() noexcept; + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator rbegin() const noexcept; + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator rend() const noexcept; + + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator crbegin() const noexcept; + LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE ConstReverseIterator crend() const noexcept; + + private: #if defined(LIBRAPID_NATIVE_ARCH) && !defined(LIBRAPID_APPLE) - alignas(LIBRAPID_DEFAULT_MEM_ALIGN) std::array m_data; + alignas(LIBRAPID_DEFAULT_MEM_ALIGN) std::array m_data; #else - // No memory alignment on Apple platforms or if it is disabled - std::array m_data; + // No memory alignment on Apple platforms or if it is disabled + std::array m_data; #endif - }; - - // Trait implementations - namespace typetraits { - template - struct IsStorage : std::false_type {}; - - template - struct IsStorage> : std::true_type {}; - - template - struct IsFixedStorage : std::false_type {}; - - template - struct IsFixedStorage> : std::true_type {}; - } // namespace typetraits - - namespace detail { - /// Safely deallocate memory for \p size elements, using an std::allocator \p alloc. If the - /// object cannot be trivially destroyed, the destructor will be called on each element of - /// the data, ensuring that it is safe to free the allocated memory. - /// \tparam A The allocator type - /// \param alloc The allocator object - /// \param ptr The pointer to free - /// \param size The number of elements of type \p in the memory block - template - void safeDeallocate(T *ptr, size_t size) { - if constexpr (!std::is_trivially_destructible_v) { - for (size_t i = 0; i < size; ++i) { ptr[i].~T(); } - } + }; + + // Trait implementations + namespace typetraits { + template + struct IsStorage : std::false_type {}; + + template + struct IsStorage> : std::true_type {}; + + template + struct IsFixedStorage : std::false_type {}; + + template + struct IsFixedStorage> : std::true_type {}; + } // namespace typetraits + + namespace detail { + /// Safely deallocate memory for \p size elements, using an std::allocator \p alloc. If the + /// object cannot be trivially destroyed, the destructor will be called on each element of + /// the data, ensuring that it is safe to free the allocated memory. + /// \tparam A The allocator type + /// \param alloc The allocator object + /// \param ptr The pointer to free + /// \param size The number of elements of type \p in the memory block + template + void safeDeallocate(T *ptr, size_t size) { + if constexpr (!std::is_trivially_destructible_v) { + for (size_t i = 0; i < size; ++i) { ptr[i].~T(); } + } #if defined(LIBRAPID_BLAS_MKLBLAS) - mkl_free(ptr); + mkl_free(ptr); #elif defined(LIBRAPID_APPLE) - free(ptr); + free(ptr); #elif defined(LIBRAPID_NATIVE_ARCH) && defined(LIBRAPID_MSVC) - _aligned_free(ptr); + _aligned_free(ptr); #else - free(ptr); + free(ptr); #endif - } - - /// Safely allocate memory for \p size elements using the allocator \p alloc. If the data - /// can be trivially default constructed, then the constructor is not called and no data - /// is initialized. Otherwise, the correct default constructor will be called for each - /// element in the data, making sure the returned pointer is safe to use. - /// \tparam A The allocator type to use - /// \param alloc The allocator object to use - /// \param size Number of elements to allocate - /// \return Pointer to the first element - /// \see safeDeallocate - template - std::shared_ptr safeAllocate(size_t size) { - using RawPointer = T *; - using Pointer = std::shared_ptr; + } + + /// Safely allocate memory for \p size elements using the allocator \p alloc. If the data + /// can be trivially default constructed, then the constructor is not called and no data + /// is initialized. Otherwise, the correct default constructor will be called for each + /// element in the data, making sure the returned pointer is safe to use. + /// \tparam A The allocator type to use + /// \param alloc The allocator object to use + /// \param size Number of elements to allocate + /// \return Pointer to the first element + /// \see safeDeallocate + template + std::shared_ptr safeAllocate(size_t size) { + using RawPointer = T *; + using Pointer = std::shared_ptr; #if defined(LIBRAPID_BLAS_MKLBLAS) - // MKL has its own memory allocation function - auto ptr = static_cast(mkl_malloc(size * sizeof(T), 64)); + // MKL has its own memory allocation function + auto ptr = static_cast(mkl_malloc(size * sizeof(T), 64)); #elif defined(LIBRAPID_APPLE) - // Use posix_memalign - void *_ptr; - auto err = posix_memalign(&_ptr, global::memoryAlignment, size * sizeof(T)); - LIBRAPID_ASSERT(err == 0, "posix_memalign failed with error code {}", err); - auto ptr = static_cast(_ptr); + // Use posix_memalign + void *_ptr; + auto err = posix_memalign(&_ptr, global::memoryAlignment, size * sizeof(T)); + LIBRAPID_ASSERT(err == 0, "posix_memalign failed with error code {}", err); + auto ptr = static_cast(_ptr); #elif defined(LIBRAPID_MSVC) || defined(LIBRAPID_MINGW) - auto ptr = - static_cast(_aligned_malloc(size * sizeof(T), global::memoryAlignment)); + auto ptr = + static_cast(_aligned_malloc(size * sizeof(T), global::memoryAlignment)); #else - auto ptr = static_cast( - std::aligned_alloc(global::memoryAlignment, size * sizeof(T))); + auto ptr = static_cast( + std::aligned_alloc(global::memoryAlignment, size * sizeof(T))); #endif - LIBRAPID_ASSERT( - ptr != nullptr, "Failed to allocate {} bytes of memory", size * sizeof(T)); - - // If the type cannot be trivially constructed, we need to - // initialize each value - if constexpr (!typetraits::TriviallyDefaultConstructible::value && - !std::is_array::value) { - for (RawPointer p = ptr; p != ptr + size; ++p) { new (p) T(); } - } - - return Pointer(ptr, [size](RawPointer ptr) { safeDeallocate(ptr, size); }); - } - - /// Safely copy a pointer to a shared pointer. If \p ownsData is true, then the shared - /// pointer will be initialized with a custom deleter that will call safeDeallocate on the - /// pointer. Otherwise, the shared pointer will be initialized with a no-op deleter. - /// \tparam T Type of the pointer - /// \param ptr Raw pointer to copy - /// \param ownsData Whether the shared pointer should own the data - /// \return Shared pointer to the data - template - std::shared_ptr safePointerCopy(T *ptr, size_t size, bool ownsData) { - using RawPointer = T *; - using Pointer = std::shared_ptr; - - if (ownsData) { - return Pointer(ptr, [size](RawPointer ptr) { safeDeallocate(ptr, size); }); - } else { - return Pointer(ptr, [](RawPointer) {}); - } - } - - template - std::shared_ptr safePointerCopy(const std::shared_ptr &ptr, size_t size, - bool ownsData = true) { - using RawPointer = T *; - using Pointer = std::shared_ptr; - - if (ownsData) { - return Pointer(ptr.get(), [size](RawPointer ptr) { safeDeallocate(ptr, size); }); - } else { - return Pointer(ptr.get(), [](RawPointer) {}); - } - } - } // namespace detail - - template - Storage::Storage(SizeType size) : - m_begin(detail::safeAllocate(size)), m_size(size), m_ownsData(true) {} - - template - Storage::Storage(Scalar *begin, Scalar *end, bool ownsData) : - m_begin(detail::safePointerCopy(begin, std::distance(begin, end), ownsData)), - m_size(std::distance(begin, end)), m_ownsData(ownsData) {} - - template - Storage::Storage(SizeType size, ConstReference value) : - m_begin(detail::safeAllocate(size)), m_size(size), m_ownsData(true) { - for (SizeType i = 0; i < size; ++i) { m_begin.get()[i] = value; } - } - - template - Storage::Storage(const Storage &other) : - m_begin(other.m_begin), m_size(other.m_size), m_ownsData(other.m_ownsData) {} - - template - Storage::Storage(Storage &&other) noexcept : - m_begin(std::move(other.m_begin)), m_size(std::move(other.m_size)), - m_ownsData(std::move(other.m_ownsData)) { - other.m_begin = nullptr; - other.m_size = 0; - other.m_ownsData = false; - } - - template - template - Storage::Storage(const std::initializer_list &list) : - m_begin(nullptr), m_size(0), m_ownsData(true) { - initData(list.begin(), list.end()); - } - - template - template - Storage::Storage(const std::vector &vector) : - m_begin(nullptr), m_size(0), m_ownsData(true) { - initData(vector.begin(), vector.end()); - } - - template - template - auto Storage::fromData(const std::initializer_list &list) -> Storage { - return Storage(list); - } - - template - template - auto Storage::fromData(const std::vector &vec) -> Storage { - return Storage(vec); - } - - template - Storage &Storage::operator=(const Storage &other) { - if (this != &other) { - if (m_ownsData) { - // If we own the data already, we can just copy the pointer since we know it won't - // affect anything else. The shared pointer deals with the reference counting, so - // we don't need to worry about other arrays that might be using the same data. - m_begin = other.m_begin; - m_size = other.m_size; - } else { - LIBRAPID_ASSERT(m_size == other.m_size, - "Cannot copy storage with {} elements to dependent storage with " - "{} elements", - other.m_size, - m_size); - - // If we don't own the data, the size must be the same since it is being used - // elsewhere, and we can't change it - - if (typetraits::TriviallyDefaultConstructible::value) { - // Use a slightly faster memcpy if the type is trivially default constructible - std::uninitialized_copy(other.begin(), other.end(), m_begin.get()); - } else { - // Otherwise, use the standard copy algorithm - std::copy(other.begin(), other.end(), m_begin.get()); - } - } - } - return *this; - } - - template - Storage &Storage::operator=(Storage &&other) LIBRAPID_RELEASE_NOEXCEPT { - if (this != &other) { - if (m_ownsData) { - std::swap(m_begin, other.m_begin); - std::swap(m_size, other.m_size); - m_ownsData = other.m_ownsData; - } else { - LIBRAPID_ASSERT( - size() == other.size(), - "Mismatched storage sizes. Cannot assign storage with {} elements to " - "dependent storage with {} elements", - other.size(), - size()); - - if (typetraits::TriviallyDefaultConstructible::value) { - // Use a slightly faster memcpy if the type is trivially default constructible - std::uninitialized_copy(other.begin(), other.end(), m_begin.get()); - } else { - // Otherwise, use the standard copy algorithm - std::copy(other.begin(), other.end(), m_begin.get()); - } - } - } - return *this; - } - - template - Storage::~Storage() { - // All deallocation is handled by the shared pointer, which has a custom deleter which - // depends on whether the data is owned by the storage object or not. If it is owned, the - // data is deallocated, otherwise it is left alone. - } - - template - template - void Storage::initData(P begin, P end) { - m_size = static_cast(std::distance(begin, end)); - m_begin = detail::safeAllocate(m_size); - - if constexpr (typetraits::TypeInfo::canMemcpy) { - if constexpr (typetraits::TriviallyDefaultConstructible::value) { - // Use a slightly faster memcpy if the type is trivially default constructible - std::uninitialized_copy(begin, end, m_begin.get()); - } else { - // Otherwise, use the standard copy algorithm - std::copy(begin, end, m_begin.get()); - } - } else { - // Since we can't memcpy, we have to copy each element individually - for (SizeType i = 0; i < m_size; ++i) { m_begin.get()[i] = begin[i]; } - } - } - - template - template - void Storage::initData(P begin, SizeType size) { - initData(begin, begin + size); - } - - template - void Storage::set(const Storage &other) { - // We can simply copy the shared pointers across - m_begin = other.m_begin; - m_size = other.m_size; - m_ownsData = other.m_ownsData; - } - - template - auto Storage::toHostStorage() const -> Storage { - return copy(); - } - - template - auto Storage::toHostStorageUnsafe() const -> Storage { - return copy(); - } - - template - auto Storage::copy() const -> Storage { - Storage ret; - ret.initData(m_begin.get(), m_size); - return ret; - } - - template - template - auto Storage::defaultShape() -> ShapeType { - return ShapeType({0}); - } - - template - auto Storage::size() const noexcept -> SizeType { - return m_size; - } - - template - void Storage::resize(SizeType newSize) { - resizeImpl(newSize); - } - - template - void Storage::resize(SizeType newSize, int) { - resizeImpl(newSize, 0); - } - - template - LIBRAPID_ALWAYS_INLINE void Storage::resizeImpl(SizeType newSize) { - // Resize and retain data - - if (newSize == size()) return; - LIBRAPID_ASSERT(m_ownsData, "Dependent storage cannot be resized"); - - // Copy the existing data to a new location - Pointer oldBegin = m_begin; - SizeType oldSize = m_size; - - // Allocate a new block of memory - m_begin = detail::safeAllocate(newSize); - m_size = newSize; - - // Copy the data across - if constexpr (typetraits::TriviallyDefaultConstructible::value) { - // Use a slightly faster memcpy if the type is trivially default constructible - std::uninitialized_copy( - oldBegin.get(), oldBegin.get() + ::librapid::min(oldSize, newSize), m_begin.get()); - } else { - // Otherwise, use the standard copy algorithm - std::copy( - oldBegin.get(), oldBegin.get() + ::librapid::min(oldSize, newSize), m_begin.get()); - } - } - - template - LIBRAPID_ALWAYS_INLINE void Storage::resizeImpl(SizeType newSize, int) { - // Resize and discard data - - if (size() == newSize) return; - LIBRAPID_ASSERT(m_ownsData, "Dependent storage cannot be resized"); - - // Allocate a new block of memory - m_begin = detail::safeAllocate(newSize); - m_size = newSize; - } - - template - auto Storage::operator[](Storage::SizeType index) const -> ConstReference { - LIBRAPID_ASSERT(index < size(), "Index {} out of bounds for size {}", index, size()); - return m_begin.get()[index]; - } - - template - auto Storage::operator[](Storage::SizeType index) -> Reference { - LIBRAPID_ASSERT(index < size(), "Index {} out of bounds for size {}", index, size()); - return m_begin.get()[index]; - } - - template - auto Storage::data() const noexcept -> Pointer { - return m_begin; - } - - template - auto Storage::begin() noexcept -> RawPointer { - return m_begin.get(); - } - - template - auto Storage::end() noexcept -> RawPointer { - return m_begin.get() + m_size; - } - - template - auto Storage::begin() const noexcept -> ConstIterator { - return m_begin.get(); - } - - template - auto Storage::end() const noexcept -> ConstIterator { - return m_begin.get() + m_size; - } - - template - auto Storage::cbegin() const noexcept -> ConstIterator { - return begin(); - } - - template - auto Storage::cend() const noexcept -> ConstIterator { - return end(); - } - - template - auto Storage::rbegin() noexcept -> ReverseIterator { - return ReverseIterator(m_begin.get() + m_size); - } - - template - auto Storage::rend() noexcept -> ReverseIterator { - return ReverseIterator(m_begin.get()); - } - - template - auto Storage::rbegin() const noexcept -> ConstReverseIterator { - return ConstReverseIterator(m_begin.get() + m_size); - } - - template - auto Storage::rend() const noexcept -> ConstReverseIterator { - return ConstReverseIterator(m_begin.get()); - } - - template - auto Storage::crbegin() const noexcept -> ConstReverseIterator { - return rbegin(); - } - - template - auto Storage::crend() const noexcept -> ConstReverseIterator { - return rend(); - } - - template - FixedStorage::FixedStorage() = default; - - template - FixedStorage::FixedStorage(const Scalar &value) { - for (size_t i = 0; i < Size; ++i) { m_data[i] = value; } - } - - template - FixedStorage::FixedStorage(const FixedStorage &other) = default; - - template - FixedStorage::FixedStorage(FixedStorage &&other) noexcept = default; - - template - FixedStorage::FixedStorage(const std::initializer_list &list) { - LIBRAPID_ASSERT(list.size() == size(), "Initializer list size does not match storage size"); - for (size_t i = 0; i < Size; ++i) { m_data[i] = list.begin()[i]; } - } - - template - FixedStorage::FixedStorage(const std::vector &vec) { - LIBRAPID_ASSERT(vec.size() == size(), "Initializer list size does not match storage size"); - for (size_t i = 0; i < Size; ++i) { m_data[i] = vec[i]; } - } - - template - auto FixedStorage::operator=(const FixedStorage &other) -> FixedStorage & { - if (this != &other) { - for (size_t i = 0; i < Size; ++i) { m_data[i] = other.m_data[i]; } - } - return *this; - } - - template - auto FixedStorage::operator=(FixedStorage &&other) noexcept - -> FixedStorage & = default; - - template - template - auto FixedStorage::defaultShape() -> ShapeType { - return ShapeType({D...}); - } - - template - void FixedStorage::resize(SizeType newSize) { - LIBRAPID_ASSERT(newSize == size(), "FixedStorage cannot be resized"); - } - - template - void FixedStorage::resize(SizeType newSize, int) { - LIBRAPID_ASSERT(newSize == size(), "FixedStorage cannot be resized"); - } - - template - auto FixedStorage::size() const noexcept -> SizeType { - return Size; - } - - template - auto FixedStorage::copy() const -> FixedStorage { - return FixedStorage(); - } - - template - auto FixedStorage::operator[](SizeType index) const -> ConstReference { - LIBRAPID_ASSERT(index < size(), "Index out of bounds"); - return m_data[index]; - } - - template - auto FixedStorage::operator[](SizeType index) -> Reference { - LIBRAPID_ASSERT(index < size(), "Index out of bounds"); - return m_data[index]; - } - - template - auto FixedStorage::data() const noexcept -> Pointer { - return const_cast(m_data.data()); - } - - template - auto FixedStorage::begin() noexcept -> Iterator { - return m_data.begin(); - } - - template - auto FixedStorage::end() noexcept -> Iterator { - return m_data.end(); - } - - template - auto FixedStorage::begin() const noexcept -> ConstIterator { - return m_data.begin(); - } - - template - auto FixedStorage::end() const noexcept -> ConstIterator { - return m_data.end(); - } - - template - auto FixedStorage::cbegin() const noexcept -> ConstIterator { - return begin(); - } - - template - auto FixedStorage::cend() const noexcept -> ConstIterator { - return end(); - } - - template - auto FixedStorage::rbegin() noexcept -> ReverseIterator { - return ReverseIterator(end()); - } - - template - auto FixedStorage::rend() noexcept -> ReverseIterator { - return ReverseIterator(begin()); - } - - template - auto FixedStorage::rbegin() const noexcept -> ConstReverseIterator { - return ConstReverseIterator(end()); - } - - template - auto FixedStorage::rend() const noexcept -> ConstReverseIterator { - return ConstReverseIterator(begin()); - } - - template - auto FixedStorage::crbegin() const noexcept -> ConstReverseIterator { - return rbegin(); - } - - template - auto FixedStorage::crend() const noexcept -> ConstReverseIterator { - return rend(); - } + LIBRAPID_ASSERT( + ptr != nullptr, "Failed to allocate {} bytes of memory", size * sizeof(T)); + + // If the type cannot be trivially constructed, we need to + // initialize each value + if constexpr (!typetraits::TriviallyDefaultConstructible::value && + !std::is_array::value) { + for (RawPointer p = ptr; p != ptr + size; ++p) { new (p) T(); } + } + + return Pointer(ptr, [size](RawPointer ptr) { safeDeallocate(ptr, size); }); + } + + /// Safely copy a pointer to a shared pointer. If \p ownsData is true, then the shared + /// pointer will be initialized with a custom deleter that will call safeDeallocate on the + /// pointer. Otherwise, the shared pointer will be initialized with a no-op deleter. + /// \tparam T Type of the pointer + /// \param ptr Raw pointer to copy + /// \param ownsData Whether the shared pointer should own the data + /// \return Shared pointer to the data + template + std::shared_ptr safePointerCopy(T *ptr, size_t size, bool ownsData) { + using RawPointer = T *; + using Pointer = std::shared_ptr; + + if (ownsData) { + return Pointer(ptr, [size](RawPointer ptr) { safeDeallocate(ptr, size); }); + } else { + return Pointer(ptr, [](RawPointer) {}); + } + } + + template + std::shared_ptr safePointerCopy(const std::shared_ptr &ptr, size_t size, + bool ownsData = true) { + using RawPointer = T *; + using Pointer = std::shared_ptr; + + if (ownsData) { + return Pointer(ptr.get(), [size](RawPointer ptr) { safeDeallocate(ptr, size); }); + } else { + return Pointer(ptr.get(), [](RawPointer) {}); + } + } + } // namespace detail + + template + Storage::Storage(SizeType size) : + m_begin(detail::safeAllocate(size)), m_size(size), m_ownsData(true) {} + + template + Storage::Storage(Scalar *begin, Scalar *end, bool ownsData) : + m_begin(detail::safePointerCopy(begin, std::distance(begin, end), ownsData)), + m_size(std::distance(begin, end)), m_ownsData(ownsData) {} + + template + Storage::Storage(SizeType size, ConstReference value) : + m_begin(detail::safeAllocate(size)), m_size(size), m_ownsData(true) { + for (SizeType i = 0; i < size; ++i) { m_begin.get()[i] = value; } + } + + template + Storage::Storage(const Storage &other) : + m_begin(other.m_begin), m_size(other.m_size), m_ownsData(other.m_ownsData) {} + + template + Storage::Storage(Storage &&other) noexcept : + m_begin(std::move(other.m_begin)), m_size(std::move(other.m_size)), + m_ownsData(std::move(other.m_ownsData)) { + other.m_begin = nullptr; + other.m_size = 0; + other.m_ownsData = false; + } + + template + template + Storage::Storage(const std::initializer_list &list) : + m_begin(nullptr), m_size(0), m_ownsData(true) { + initData(list.begin(), list.end()); + } + + template + template + Storage::Storage(const std::vector &vector) : + m_begin(nullptr), m_size(0), m_ownsData(true) { + initData(vector.begin(), vector.end()); + } + + template + template + auto Storage::fromData(const std::initializer_list &list) -> Storage { + return Storage(list); + } + + template + template + auto Storage::fromData(const std::vector &vec) -> Storage { + return Storage(vec); + } + + template + Storage &Storage::operator=(const Storage &other) { + if (this != &other) { + if (m_ownsData) { + // If we own the data already, we can just copy the pointer since we know it won't + // affect anything else. The shared pointer deals with the reference counting, so + // we don't need to worry about other arrays that might be using the same data. + m_begin = other.m_begin; + m_size = other.m_size; + } else { + LIBRAPID_ASSERT(m_size == other.m_size, + "Cannot copy storage with {} elements to dependent storage with " + "{} elements", + other.m_size, + m_size); + + // If we don't own the data, the size must be the same since it is being used + // elsewhere, and we can't change it + + if (typetraits::TriviallyDefaultConstructible::value) { + // Use a slightly faster memcpy if the type is trivially default constructible + std::uninitialized_copy(other.begin(), other.end(), m_begin.get()); + } else { + // Otherwise, use the standard copy algorithm + std::copy(other.begin(), other.end(), m_begin.get()); + } + } + } + return *this; + } + + template + Storage &Storage::operator=(Storage &&other) LIBRAPID_RELEASE_NOEXCEPT { + if (this != &other) { + if (m_ownsData) { + std::swap(m_begin, other.m_begin); + std::swap(m_size, other.m_size); + m_ownsData = other.m_ownsData; + } else { + LIBRAPID_ASSERT( + size() == other.size(), + "Mismatched storage sizes. Cannot assign storage with {} elements to " + "dependent storage with {} elements", + other.size(), + size()); + + if (typetraits::TriviallyDefaultConstructible::value) { + // Use a slightly faster memcpy if the type is trivially default constructible + std::uninitialized_copy(other.begin(), other.end(), m_begin.get()); + } else { + // Otherwise, use the standard copy algorithm + std::copy(other.begin(), other.end(), m_begin.get()); + } + } + } + return *this; + } + + template + Storage::~Storage() { + // All deallocation is handled by the shared pointer, which has a custom deleter which + // depends on whether the data is owned by the storage object or not. If it is owned, the + // data is deallocated, otherwise it is left alone. + } + + template + template + void Storage::initData(P begin, P end) { + m_size = static_cast(std::distance(begin, end)); + m_begin = detail::safeAllocate(m_size); + + if constexpr (typetraits::TypeInfo::canMemcpy) { + if constexpr (typetraits::TriviallyDefaultConstructible::value) { + // Use a slightly faster memcpy if the type is trivially default constructible + std::uninitialized_copy(begin, end, m_begin.get()); + } else { + // Otherwise, use the standard copy algorithm + std::copy(begin, end, m_begin.get()); + } + } else { + // Since we can't memcpy, we have to copy each element individually + for (SizeType i = 0; i < m_size; ++i) { m_begin.get()[i] = begin[i]; } + } + } + + template + template + void Storage::initData(P begin, SizeType size) { + initData(begin, begin + size); + } + + template + void Storage::set(const Storage &other) { + // We can simply copy the shared pointers across + m_begin = other.m_begin; + m_size = other.m_size; + m_ownsData = other.m_ownsData; + } + + template + auto Storage::toHostStorage() const -> Storage { + return copy(); + } + + template + auto Storage::toHostStorageUnsafe() const -> Storage { + return copy(); + } + + template + auto Storage::copy() const -> Storage { + Storage ret; + ret.initData(m_begin.get(), m_size); + return ret; + } + + template + template + auto Storage::defaultShape() -> ShapeType { + return ShapeType({0}); + } + + template + auto Storage::size() const noexcept -> SizeType { + return m_size; + } + + template + void Storage::resize(SizeType newSize) { + resizeImpl(newSize); + } + + template + void Storage::resize(SizeType newSize, int) { + resizeImpl(newSize, 0); + } + + template + LIBRAPID_ALWAYS_INLINE void Storage::resizeImpl(SizeType newSize) { + // Resize and retain data + + if (newSize == size()) return; + LIBRAPID_ASSERT(m_ownsData, "Dependent storage cannot be resized"); + + // Copy the existing data to a new location + Pointer oldBegin = m_begin; + SizeType oldSize = m_size; + + // Allocate a new block of memory + m_begin = detail::safeAllocate(newSize); + m_size = newSize; + + // Copy the data across + if constexpr (typetraits::TriviallyDefaultConstructible::value) { + // Use a slightly faster memcpy if the type is trivially default constructible + std::uninitialized_copy( + oldBegin.get(), oldBegin.get() + ::librapid::min(oldSize, newSize), m_begin.get()); + } else { + // Otherwise, use the standard copy algorithm + std::copy( + oldBegin.get(), oldBegin.get() + ::librapid::min(oldSize, newSize), m_begin.get()); + } + } + + template + LIBRAPID_ALWAYS_INLINE void Storage::resizeImpl(SizeType newSize, int) { + // Resize and discard data + + if (size() == newSize) return; + LIBRAPID_ASSERT(m_ownsData, "Dependent storage cannot be resized"); + + // Allocate a new block of memory + m_begin = detail::safeAllocate(newSize); + m_size = newSize; + } + + template + auto Storage::operator[](Storage::SizeType index) const -> ConstReference { + LIBRAPID_ASSERT(index < size(), "Index {} out of bounds for size {}", index, size()); + return m_begin.get()[index]; + } + + template + auto Storage::operator[](Storage::SizeType index) -> Reference { + LIBRAPID_ASSERT(index < size(), "Index {} out of bounds for size {}", index, size()); + return m_begin.get()[index]; + } + + template + auto Storage::data() const noexcept -> Pointer { + return m_begin; + } + + template + auto Storage::begin() noexcept -> RawPointer { + return m_begin.get(); + } + + template + auto Storage::end() noexcept -> RawPointer { + return m_begin.get() + m_size; + } + + template + auto Storage::begin() const noexcept -> ConstIterator { + return m_begin.get(); + } + + template + auto Storage::end() const noexcept -> ConstIterator { + return m_begin.get() + m_size; + } + + template + auto Storage::cbegin() const noexcept -> ConstIterator { + return begin(); + } + + template + auto Storage::cend() const noexcept -> ConstIterator { + return end(); + } + + template + auto Storage::rbegin() noexcept -> ReverseIterator { + return ReverseIterator(m_begin.get() + m_size); + } + + template + auto Storage::rend() noexcept -> ReverseIterator { + return ReverseIterator(m_begin.get()); + } + + template + auto Storage::rbegin() const noexcept -> ConstReverseIterator { + return ConstReverseIterator(m_begin.get() + m_size); + } + + template + auto Storage::rend() const noexcept -> ConstReverseIterator { + return ConstReverseIterator(m_begin.get()); + } + + template + auto Storage::crbegin() const noexcept -> ConstReverseIterator { + return rbegin(); + } + + template + auto Storage::crend() const noexcept -> ConstReverseIterator { + return rend(); + } + + template + FixedStorage::FixedStorage() = default; + + template + FixedStorage::FixedStorage(const Scalar &value) { + for (size_t i = 0; i < Size; ++i) { m_data[i] = value; } + } + + template + FixedStorage::FixedStorage(const FixedStorage &other) = default; + + template + FixedStorage::FixedStorage(FixedStorage &&other) noexcept = default; + + template + FixedStorage::FixedStorage(const std::initializer_list &list) { + LIBRAPID_ASSERT(list.size() == size(), "Initializer list size does not match storage size"); + for (size_t i = 0; i < Size; ++i) { m_data[i] = list.begin()[i]; } + } + + template + FixedStorage::FixedStorage(const std::vector &vec) { + LIBRAPID_ASSERT(vec.size() == size(), "Initializer list size does not match storage size"); + for (size_t i = 0; i < Size; ++i) { m_data[i] = vec[i]; } + } + + template + auto FixedStorage::operator=(const FixedStorage &other) -> FixedStorage & { + if (this != &other) { + for (size_t i = 0; i < Size; ++i) { m_data[i] = other.m_data[i]; } + } + return *this; + } + + template + auto FixedStorage::operator=(FixedStorage &&other) noexcept + -> FixedStorage & = default; + + template + template + auto FixedStorage::defaultShape() -> ShapeType { + return ShapeType({D...}); + } + + template + void FixedStorage::resize(SizeType newSize) { + LIBRAPID_ASSERT(newSize == size(), "FixedStorage cannot be resized"); + } + + template + void FixedStorage::resize(SizeType newSize, int) { + LIBRAPID_ASSERT(newSize == size(), "FixedStorage cannot be resized"); + } + + template + auto FixedStorage::size() const noexcept -> SizeType { + return Size; + } + + template + auto FixedStorage::copy() const -> FixedStorage { + return FixedStorage(); + } + + template + auto FixedStorage::operator[](SizeType index) const -> ConstReference { + LIBRAPID_ASSERT(index < size(), "Index out of bounds"); + return m_data[index]; + } + + template + auto FixedStorage::operator[](SizeType index) -> Reference { + LIBRAPID_ASSERT(index < size(), "Index out of bounds"); + return m_data[index]; + } + + template + auto FixedStorage::data() const noexcept -> Pointer { + return const_cast(m_data.data()); + } + + template + auto FixedStorage::begin() noexcept -> Iterator { + return m_data.begin(); + } + + template + auto FixedStorage::end() noexcept -> Iterator { + return m_data.end(); + } + + template + auto FixedStorage::begin() const noexcept -> ConstIterator { + return m_data.begin(); + } + + template + auto FixedStorage::end() const noexcept -> ConstIterator { + return m_data.end(); + } + + template + auto FixedStorage::cbegin() const noexcept -> ConstIterator { + return begin(); + } + + template + auto FixedStorage::cend() const noexcept -> ConstIterator { + return end(); + } + + template + auto FixedStorage::rbegin() noexcept -> ReverseIterator { + return ReverseIterator(end()); + } + + template + auto FixedStorage::rend() noexcept -> ReverseIterator { + return ReverseIterator(begin()); + } + + template + auto FixedStorage::rbegin() const noexcept -> ConstReverseIterator { + return ConstReverseIterator(end()); + } + + template + auto FixedStorage::rend() const noexcept -> ConstReverseIterator { + return ConstReverseIterator(begin()); + } + + template + auto FixedStorage::crbegin() const noexcept -> ConstReverseIterator { + return rbegin(); + } + + template + auto FixedStorage::crend() const noexcept -> ConstReverseIterator { + return rend(); + } } // namespace librapid #endif // LIBRAPID_ARRAY_STORAGE_HPP \ No newline at end of file