Program Listing for File arrayContainer.hpp#

Return to documentation for file (librapid/include/librapid/array/arrayContainer.hpp)

#ifndef LIBRAPID_ARRAY_ARRAY_CONTAINER_HPP
#define LIBRAPID_ARRAY_ARRAY_CONTAINER_HPP

namespace librapid {
    namespace detail {
        template<typename T>
        struct SubscriptType {
            using Scalar = T;
            using Direct = const Scalar &;
            using Ref    = Scalar &;
        };

        template<typename T>
        struct SubscriptType<Storage<T>> {
            using Scalar = T;
            using Direct = const Scalar &;
            using Ref    = Scalar &;
        };

        template<typename T, size_t... Dims>
        struct SubscriptType<FixedStorage<T, Dims...>> {
            using Scalar = T;
            using Direct = const Scalar &;
            using Ref    = Scalar &;
        };

#if defined(LIBRAPID_HAS_OPENCL)
        template<typename T>
        struct SubscriptType<OpenCLStorage<T>> {
            using Scalar = T;
            using Direct = const OpenCLRef<Scalar>;
            using Ref    = OpenCLRef<Scalar>;
        };
#endif // LIBRAPID_HAS_OPENCL

#if defined(LIBRAPID_HAS_CUDA)
        template<typename T>
        struct SubscriptType<CudaStorage<T>> {
            using Scalar = T;
            using Direct = const detail::CudaRef<Scalar>;
            using Ref    = detail::CudaRef<Scalar>;
        };
#endif // LIBRAPID_HAS_CUDA
    }  // namespace detail

    namespace typetraits {
        template<typename ShapeType_, typename StorageType_>
        struct TypeInfo<array::ArrayContainer<ShapeType_, StorageType_>> {
            static constexpr detail::LibRapidType type = detail::LibRapidType::ArrayContainer;
            using Scalar                               = typename TypeInfo<StorageType_>::Scalar;
            using Packet                               = std::false_type;
            using Backend                              = typename TypeInfo<StorageType_>::Backend;
            static constexpr int64_t packetWidth       = 1;
            static constexpr bool supportsArithmetic   = TypeInfo<Scalar>::supportsArithmetic;
            static constexpr bool supportsLogical      = TypeInfo<Scalar>::supportsLogical;
            static constexpr bool supportsBinary       = TypeInfo<Scalar>::supportsBinary;
            static constexpr bool allowVectorisation   = TypeInfo<Scalar>::packetWidth > 1;

#if defined(LIBRAPID_HAS_CUDA)
            static constexpr cudaDataType_t CudaType = TypeInfo<Scalar>::CudaType;
            static constexpr int64_t cudaPacketWidth = 1;
#endif // LIBRAPID_HAS_CUDA

            static constexpr bool canAlign     = false;
            static constexpr int64_t canMemcpy = false;
        };

        template<typename T>
        struct IsArrayContainer : std::false_type {};

        template<typename SizeType, size_t dims, typename StorageScalar>
        struct IsArrayContainer<array::ArrayContainer<Shape<SizeType, dims>, StorageScalar>>
                : std::true_type {};

        LIBRAPID_DEFINE_AS_TYPE(
          typename SizeType COMMA size_t dims COMMA typename StorageScalar,
          array::ArrayContainer<Shape<SizeType COMMA dims> COMMA StorageScalar>);
    } // namespace typetraits

    namespace array {
        template<typename ShapeType_, typename StorageType_>
        class ArrayContainer {
        public:
            using StorageType = StorageType_;
            using ShapeType   = ShapeType_;
            using StrideType  = Stride<size_t, 32>;
            using SizeType    = typename ShapeType::SizeType;
            using Scalar      = typename StorageType::Scalar;
            using Packet      = typename typetraits::TypeInfo<Scalar>::Packet;
            using Backend     = typename typetraits::TypeInfo<ArrayContainer>::Backend;
            using Iterator    = detail::ArrayIterator<ArrayView<ArrayContainer>>;

            using DirectSubscriptType    = typename detail::SubscriptType<StorageType>::Direct;
            using DirectRefSubscriptType = typename detail::SubscriptType<StorageType>::Ref;

            ArrayContainer();

            template<typename T>
            LIBRAPID_ALWAYS_INLINE ArrayContainer(const std::initializer_list<T> &data);

            template<typename T>
            explicit LIBRAPID_ALWAYS_INLINE ArrayContainer(const std::vector<T> &data);

            LIBRAPID_ALWAYS_INLINE explicit ArrayContainer(const ShapeType &shape);

            LIBRAPID_ALWAYS_INLINE ArrayContainer(const ShapeType &shape, const Scalar &value);

            LIBRAPID_ALWAYS_INLINE explicit ArrayContainer(const Scalar &value);

            LIBRAPID_ALWAYS_INLINE explicit ArrayContainer(ShapeType &&shape);

            LIBRAPID_ALWAYS_INLINE ArrayContainer(const ArrayContainer &other) = default;

            LIBRAPID_ALWAYS_INLINE ArrayContainer(ArrayContainer &&other) noexcept = default;

            template<typename TransposeType>
            LIBRAPID_ALWAYS_INLINE ArrayContainer(const Transpose<TransposeType> &trans);

            template<typename ShapeTypeA, typename StorageTypeA, typename ShapeTypeB,
                     typename StorageTypeB, typename Alpha, typename Beta>
            LIBRAPID_ALWAYS_INLINE
            ArrayContainer(const linalg::ArrayMultiply<ShapeTypeA, StorageTypeA, ShapeTypeB,
                                                       StorageTypeB, Alpha, Beta> &multiply);

            template<typename desc, typename Functor_, typename... Args>
            LIBRAPID_ALWAYS_INLINE ArrayContainer &
            assign(const detail::Function<desc, Functor_, Args...> &function);

            template<typename desc, typename Functor_, typename... Args>
            LIBRAPID_ALWAYS_INLINE ArrayContainer(
              const detail::Function<desc, Functor_, Args...> &function) LIBRAPID_RELEASE_NOEXCEPT;

            LIBRAPID_ALWAYS_INLINE ArrayContainer &operator=(const ArrayContainer &other) = default;

            LIBRAPID_ALWAYS_INLINE ArrayContainer &operator=(const Scalar &value);

            LIBRAPID_ALWAYS_INLINE ArrayContainer &
            operator=(ArrayContainer &&other) noexcept = default;

            template<typename desc, typename Functor_, typename... Args>
            LIBRAPID_ALWAYS_INLINE ArrayContainer &
            operator=(const detail::Function<desc, Functor_, Args...> &function);

            template<typename TransposeType>
            LIBRAPID_ALWAYS_INLINE ArrayContainer &
            operator=(const Transpose<TransposeType> &transpose);

            template<typename ShapeTypeA, typename StorageTypeA, typename ShapeTypeB,
                     typename StorageTypeB, typename Alpha, typename Beta>
            LIBRAPID_ALWAYS_INLINE ArrayContainer &
            operator=(const linalg::ArrayMultiply<ShapeTypeA, StorageTypeA, ShapeTypeB,
                                                  StorageTypeB, Alpha, Beta> &multiply);

            template<typename T>
            detail::CommaInitializer<ArrayContainer> operator<<(const T &value);

            // template<typename ScalarTo = Scalar, typename BackendTo = Backend>
            // LIBRAPID_NODISCARD auto cast() const;

            LIBRAPID_NODISCARD ArrayContainer copy() const;

            LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator[](int64_t index) const;

            LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE auto operator[](int64_t index);

            template<typename... Indices>
            LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE DirectSubscriptType
            operator()(Indices... indices) const;

            template<typename... Indices>
            LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE DirectRefSubscriptType
            operator()(Indices... indices);

            LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Scalar get() const;

            LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE typename ShapeType::SizeType
            ndim() const noexcept;

            LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE const ShapeType &shape() const noexcept;

            LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE const StorageType &storage() const noexcept;

            LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE StorageType &storage() noexcept;

            LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Packet packet(size_t index) const;

            LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Scalar scalar(size_t index) const;

            LIBRAPID_ALWAYS_INLINE void writePacket(size_t index, const Packet &value);

            LIBRAPID_ALWAYS_INLINE void write(size_t index, const Scalar &value);

            LIBRAPID_INLINE Iterator begin() const noexcept;

            LIBRAPID_INLINE Iterator end() const noexcept;

            LIBRAPID_INLINE Iterator begin();

            LIBRAPID_INLINE Iterator end();

            LIBRAPID_NODISCARD std::string str(const std::string &format = "{}") const;

        private:
            ShapeType m_shape;     // The shape type of the array
            StorageType m_storage; // The storage container of the array
        };

        template<typename ShapeType_, typename StorageType_>
        ArrayContainer<ShapeType_, StorageType_>::ArrayContainer() :
                m_shape(StorageType_::template defaultShape<ShapeType_>()) {}

        template<typename ShapeType_, typename StorageType_>
        template<typename T>
        ArrayContainer<ShapeType_, StorageType_>::ArrayContainer(
          const std::initializer_list<T> &data) :
                m_shape({data.size()}),
                m_storage(StorageType::fromData(data)) {}

        template<typename ShapeType_, typename StorageType_>
        template<typename T>
        ArrayContainer<ShapeType_, StorageType_>::ArrayContainer(const std::vector<T> &data) :
                m_shape({data.size()}), m_storage(StorageType::fromData(data)) {}

        template<typename ShapeType_, typename StorageType_>
        ArrayContainer<ShapeType_, StorageType_>::ArrayContainer(const ShapeType &shape) :
                m_shape(shape), m_storage(shape.size()) {
            static_assert(!typetraits::IsFixedStorage<StorageType_>::value,
                          "For a compile-time-defined shape, "
                          "the storage type must be "
                          "a FixedStorage object");
        }

        template<typename ShapeType_, typename StorageType_>
        ArrayContainer<ShapeType_, StorageType_>::ArrayContainer(const ShapeType &shape,
                                                                 const Scalar &value) :
                m_shape(shape),
                m_storage(shape.size(), value) {
            static_assert(typetraits::IsStorage<StorageType_>::value ||
                            typetraits::IsOpenCLStorage<StorageType_>::value ||
                            typetraits::IsCudaStorage<StorageType_>::value,
                          "For a runtime-defined shape, "
                          "the storage type must be "
                          "either a Storage or a "
                          "CudaStorage object");
            static_assert(!typetraits::IsFixedStorage<StorageType_>::value,
                          "For a compile-time-defined shape, "
                          "the storage type must be "
                          "a FixedStorage object");
        }

        template<typename ShapeType_, typename StorageType_>
        ArrayContainer<ShapeType_, StorageType_>::ArrayContainer(const Scalar &value) :
                m_shape(detail::shapeFromFixedStorage(m_storage)), m_storage(value) {
            static_assert(typetraits::IsFixedStorage<StorageType_>::value,
                          "For a compile-time-defined shape, "
                          "the storage type must be "
                          "a FixedStorage object");
        }

        template<typename ShapeType_, typename StorageType_>
        ArrayContainer<ShapeType_, StorageType_>::ArrayContainer(ShapeType_ &&shape) :
                m_shape(std::forward<ShapeType_>(shape)), m_storage(m_shape.size()) {}

        template<typename ShapeType_, typename StorageType_>
        template<typename TransposeType>
        ArrayContainer<ShapeType_, StorageType_>::ArrayContainer(
          const array::Transpose<TransposeType> &trans) {
            *this = trans;
        }

        template<typename ShapeType_, typename StorageType_>
        template<typename ShapeTypeA, typename StorageTypeA, typename ShapeTypeB,
                 typename StorageTypeB, typename Alpha, typename Beta>
        ArrayContainer<ShapeType_, StorageType_>::ArrayContainer(
          const linalg::ArrayMultiply<ShapeTypeA, StorageTypeA, ShapeTypeB, StorageTypeB, Alpha,
                                      Beta> &multiply) {
            *this = multiply;
        }

        template<typename ShapeType_, typename StorageType_>
        template<typename desc, typename Functor_, typename... Args>
        auto ArrayContainer<ShapeType_, StorageType_>::assign(
          const detail::Function<desc, Functor_, Args...> &function) -> ArrayContainer & {
            using FunctionType = detail::Function<desc, Functor_, Args...>;
            m_storage.resize(function.shape().size(), 0);
            if constexpr (std::is_same_v<typename FunctionType::Backend, backend::OpenCL> ||
                          std::is_same_v<typename FunctionType::Backend, backend::CUDA>) {
                detail::assign(*this, function);
            } else {
#if !defined(LIBRAPID_OPTIMISE_SMALL_ARRAYS)
                if (m_storage.size() > global::multithreadThreshold && global::numThreads > 1)
                    detail::assignParallel(*this, function);
                else
#endif // LIBRAPID_OPTIMISE_SMALL_ARRAYS
                    detail::assign(*this, function);
            }
            return *this;
        }

        template<typename ShapeType_, typename StorageType_>
        template<typename desc, typename Functor_, typename... Args>
        ArrayContainer<ShapeType_, StorageType_>::ArrayContainer(
          const detail::Function<desc, Functor_, Args...> &function) LIBRAPID_RELEASE_NOEXCEPT
                : m_shape(function.shape()),
                  m_storage(m_shape.size()) {
            assign(function);
        }

        template<typename ShapeType_, typename StorageType_>
        template<typename desc, typename Functor_, typename... Args>
        auto ArrayContainer<ShapeType_, StorageType_>::operator=(
          const detail::Function<desc, Functor_, Args...> &function) -> ArrayContainer & {
            return assign(function);
        }

        template<typename ShapeType_, typename StorageType_>
        template<typename TransposeType>
        auto ArrayContainer<ShapeType_, StorageType_>::operator=(
          const Transpose<TransposeType> &transpose) -> ArrayContainer & {
            m_shape = transpose.shape();
            m_storage.resize(m_shape.size(), 0);
            transpose.applyTo(*this);
            return *this;
        }

        template<typename ShapeType_, typename StorageType_>
        template<typename ShapeTypeA, typename StorageTypeA, typename ShapeTypeB,
                 typename StorageTypeB, typename Alpha, typename Beta>
        auto ArrayContainer<ShapeType_, StorageType_>::operator=(
          const linalg::ArrayMultiply<ShapeTypeA, StorageTypeA, ShapeTypeB, StorageTypeB, Alpha,
                                      Beta> &arrayMultiply) -> ArrayContainer & {
            m_shape = arrayMultiply.shape();
            m_storage.resize(m_shape.size(), 0);
            arrayMultiply.applyTo(*this);
            return *this;
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::operator=(const Scalar &value)
          -> ArrayContainer & {
            LIBRAPID_ASSERT(m_shape.ndim() == 0, "Cannot assign a scalar to an array");
            m_storage[0] = value;
            return *this;
        }

        template<typename ShapeType_, typename StorageType_>
        template<typename T>
        auto ArrayContainer<ShapeType_, StorageType_>::operator<<(const T &value)
          -> detail::CommaInitializer<ArrayContainer> {
            return detail::CommaInitializer<ArrayContainer>(*this, static_cast<Scalar>(value));
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::copy() const -> ArrayContainer {
            ArrayContainer res(m_shape);
            res.m_storage = m_storage.copy();
            return res;
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::operator[](int64_t index) const {
            LIBRAPID_ASSERT(
              index >= 0 && index < static_cast<int64_t>(m_shape[0]),
              "Index {} out of bounds in ArrayContainer::operator[] with leading dimension={}",
              index,
              m_shape[0]);

            if constexpr (typetraits::IsOpenCLStorage<StorageType_>::value) {
#if defined(LIBRAPID_HAS_OPENCL)
                ArrayContainer res;
                res.m_shape         = m_shape.subshape(1, ndim());
                auto subSize        = res.shape().size();
                int64_t storageSize = sizeof(typename StorageType_::Scalar);
                cl_buffer_region region {index * subSize * storageSize, subSize * storageSize};
                res.m_storage =
                  StorageType_(m_storage.data().createSubBuffer(
                                 StorageType_::bufferFlags, CL_BUFFER_CREATE_TYPE_REGION, &region),
                               subSize,
                               false);
                return res;
#else
                LIBRAPID_ERROR("OpenCL support not enabled");
#endif // LIBRAPID_HAS_OPENCL
            } else if constexpr (typetraits::IsCudaStorage<StorageType_>::value) {
#if defined(LIBRAPID_HAS_CUDA)
                ArrayContainer res;
                res.m_shape   = m_shape.subshape(1, ndim());
                auto subSize  = res.shape().size();
                Scalar *begin = m_storage.begin().get() + index * subSize;
                res.m_storage = StorageType_(begin, subSize, false);
                return res;
#else
                LIBRAPID_ERROR("CUDA support not enabled");
#endif // LIBRAPID_HAS_CUDA
            } else if constexpr (typetraits::IsFixedStorage<StorageType_>::value) {
                return ArrayView(*this)[index];
            } else {
                ArrayContainer res;
                res.m_shape   = m_shape.subshape(1, ndim());
                auto subSize  = res.shape().size();
                Scalar *begin = m_storage.begin() + index * subSize;
                Scalar *end   = begin + subSize;
                res.m_storage = StorageType_(begin, end, false);
                return res;
            }
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::operator[](int64_t index) {
            LIBRAPID_ASSERT(
              index >= 0 && index < static_cast<int64_t>(m_shape[0]),
              "Index {} out of bounds in ArrayContainer::operator[] with leading dimension={}",
              index,
              m_shape[0]);

            if constexpr (typetraits::IsOpenCLStorage<StorageType_>::value) {
#if defined(LIBRAPID_HAS_OPENCL)
                ArrayContainer res;
                res.m_shape         = m_shape.subshape(1, ndim());
                auto subSize        = res.shape().size();
                int64_t storageSize = sizeof(typename StorageType_::Scalar);
                cl_buffer_region region {index * subSize * storageSize, subSize * storageSize};
                res.m_storage.set(
                  StorageType_(m_storage.data().createSubBuffer(
                                 StorageType_::bufferFlags, CL_BUFFER_CREATE_TYPE_REGION, &region),
                               subSize,
                               false));
                return res;
#else
                LIBRAPID_ERROR("OpenCL support not enabled");
#endif // LIBRAPID_HAS_OPENCL
            } else if constexpr (typetraits::IsCudaStorage<StorageType_>::value) {
#if defined(LIBRAPID_HAS_CUDA)
                ArrayContainer res;
                res.m_shape   = m_shape.subshape(1, ndim());
                auto subSize  = res.shape().size();
                Scalar *begin = m_storage.begin().get() + index * subSize;
                res.m_storage.set(StorageType_(begin, subSize, false));
                return res;
#else
                LIBRAPID_ERROR("CUDA support not enabled");
#endif // LIBRAPID_HAS_CUDA
            } else if constexpr (typetraits::IsFixedStorage<StorageType_>::value) {
                return ArrayView(*this)[index];
            } else {
                ArrayContainer res;
                res.m_shape   = m_shape.subshape(1, ndim());
                auto subSize  = res.shape().size();
                Scalar *begin = m_storage.begin() + index * subSize;
                Scalar *end   = begin + subSize;
                res.m_storage.set(StorageType_(begin, end, false));
                return res;
            }
        }

        template<typename ShapeType_, typename StorageType_>
        template<typename... Indices>
        auto ArrayContainer<ShapeType_, StorageType_>::operator()(Indices... indices) const
          -> DirectSubscriptType {
            LIBRAPID_ASSERT(
              m_shape.ndim() == sizeof...(Indices),
              "ArrayContainer::operator() called with {} indices, but array has {} dimensions",
              sizeof...(Indices),
              m_shape.ndim());

            int64_t index = 0;
            for (int64_t i : {indices...}) {
                LIBRAPID_ASSERT(
                  i >= 0 && i < static_cast<int64_t>(m_shape[index]),
                  "Index {} out of bounds in ArrayContainer::operator() with dimension={}",
                  i,
                  m_shape[index]);
                index = index * m_shape[index] + i;
            }
            return m_storage[index];
        }

        template<typename ShapeType_, typename StorageType_>
        template<typename... Indices>
        auto ArrayContainer<ShapeType_, StorageType_>::operator()(Indices... indices)
          -> DirectRefSubscriptType {
            LIBRAPID_ASSERT(
              m_shape.ndim() == sizeof...(Indices),
              "ArrayContainer::operator() called with {} indices, but array has {} dimensions",
              sizeof...(Indices),
              m_shape.ndim());

            int64_t index = 0;
            int64_t count = 0;
            for (int64_t i : {indices...}) {
                LIBRAPID_ASSERT(
                  i >= 0 && i < static_cast<int64_t>(m_shape[count]),
                  "Index {} out of bounds in ArrayContainer::operator() with dimension={}",
                  i,
                  m_shape[index]);
                index = index * m_shape[count++] + i;
            }
            return m_storage[index];
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::get() const -> Scalar {
            LIBRAPID_ASSERT(m_shape.ndim() == 0,
                            "Can only cast a scalar ArrayView to a salar object");
            return scalar(0);
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::ndim() const noexcept ->
          typename ShapeType_::SizeType {
            return m_shape.ndim();
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::shape() const noexcept -> const ShapeType & {
            return m_shape;
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::storage() const noexcept
          -> const StorageType & {
            return m_storage;
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::storage() noexcept -> StorageType & {
            return m_storage;
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::packet(size_t index) const -> Packet {
            Packet res;
            res.load(m_storage.begin() + index);
            return res;
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::scalar(size_t index) const -> Scalar {
            return m_storage[index];
        }

        template<typename ShapeType_, typename StorageType_>
        void ArrayContainer<ShapeType_, StorageType_>::writePacket(size_t index,
                                                                   const Packet &value) {
            value.store(m_storage.begin() + index);
        }

        template<typename ShapeType_, typename StorageType_>
        void ArrayContainer<ShapeType_, StorageType_>::write(size_t index, const Scalar &value) {
            m_storage[index] = value;
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::begin() const noexcept -> Iterator {
            return Iterator(ArrayView(*this), 0);
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::end() const noexcept -> Iterator {
            return Iterator(ArrayView(*this), m_shape[0]);
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::begin() -> Iterator {
            return Iterator(ArrayView(*this), 0);
        }

        template<typename ShapeType_, typename StorageType_>
        auto ArrayContainer<ShapeType_, StorageType_>::end() -> Iterator {
            return Iterator(ArrayView(*this), m_shape[0]);
        }

        template<typename ShapeType_, typename StorageType_>
        std::string ArrayContainer<ShapeType_, StorageType_>::str(const std::string &format) const {
            return ArrayView(*this).str(format);
        }
    } // namespace array

    namespace detail {
        template<typename T>
        struct IsArrayType {
            static constexpr bool val = false;
        };

        template<typename T>
        struct IsArrayType<ArrayRef<T>> {
            static constexpr bool val = true;
        };

        template<typename... T>
        struct IsArrayType<FunctionRef<T...>> {
            static constexpr bool val = true;
        };

        template<typename T>
        struct IsArrayType<array::ArrayView<T>> {
            static constexpr bool val = true;
        };

        template<typename First, typename... Types>
        struct ContainsArrayType {
            static constexpr auto evaluator() {
                if constexpr (sizeof...(Types) == 0)
                    return IsArrayType<First>::val;
                else
                    return IsArrayType<First>::val || ContainsArrayType<Types...>::val;
            };

            static constexpr bool val = evaluator();
        };
    }; // namespace detail
} // namespace librapid

// Support FMT printing
#ifdef FMT_API
LIBRAPID_SIMPLE_IO_IMPL(typename ShapeType_ COMMA typename StorageType_,
                        librapid::array::ArrayContainer<ShapeType_ COMMA StorageType_>)
LIBRAPID_SIMPLE_IO_NORANGE(typename ShapeType_ COMMA typename StorageType_,
                           librapid::array::ArrayContainer<ShapeType_ COMMA StorageType_>)
#endif // FMT_API

#endif // LIBRAPID_ARRAY_ARRAY_CONTAINER_HPP