Span.h#

Fully qualified name: carb/cpp/Span.h

File members: carb/cpp/Span.h

// SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: LicenseRef-NvidiaProprietary
//
// NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
// property and proprietary rights in and to this material, related
// documentation and any modifications thereto. Any use, reproduction,
// disclosure or distribution of this material and related documentation
// without an express license agreement from NVIDIA CORPORATION or
// its affiliates is strictly prohibited.

#pragma once

#include "../Defines.h"
#include "TypeTraits.h"
#include "detail/ImplData.h"
#include "StdDef.h"
#include "Memory.h"
#include "../../omni/detail/PointerIterator.h"

#include <array>
#include <stdexcept>

namespace carb
{
namespace cpp
{

constexpr size_t dynamic_extent = size_t(-1);

template <class T, size_t Extent>
class span;

namespace detail
{

template <size_t Extent, size_t Offset, size_t Count>
struct DetermineSubspanExtent
{
    constexpr static size_t value = Count;
};

template <size_t Extent, size_t Offset>
struct DetermineSubspanExtent<Extent, Offset, dynamic_extent>
{
    constexpr static size_t value = Extent - Offset;
};

template <size_t Offset>
struct DetermineSubspanExtent<dynamic_extent, Offset, dynamic_extent>
{
    constexpr static size_t value = dynamic_extent;
};

template <class T>
struct IsStdArray : public std::false_type
{
};

template <class T, size_t N>
struct IsStdArray<std::array<T, N>> : public std::true_type
{
};

template <class T>
struct IsSpan : public std::false_type
{
};

template <class T, size_t N>
struct IsSpan<span<T, N>> : public std::true_type
{
};

template <typename T>
struct always_false
{
    static constexpr bool value = false;
};

#if CARB_EXCEPTIONS_ENABLED
#    define CARBLOCAL_THROW_OR_FATAL(check, msg)                                                                       \
        if (!CARB_LIKELY(check))                                                                                       \
            CARB_CPP20_UNLIKELY                                                                                        \
        throw std::out_of_range(msg)
#else
#    define CARBLOCAL_THROW_OR_FATAL(check, msg) CARB_FATAL_UNLESS((check), msg)
#endif

} // namespace detail

template <class T, size_t Extent = dynamic_extent>
class span
{
    // NOTE: This implementation is used for Extent != 0 && Extent != dynamic_extent, both of which have specializations
public:
    using element_type = T;
    using value_type = std::remove_cv_t<T>;
    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;
    using pointer = T*;
    using const_pointer = const T*;
    using reference = T&;
    using const_reference = const T&;

    using iterator = omni::detail::PointerIterator<pointer, span>;
    using reverse_iterator = std::reverse_iterator<iterator>;

    constexpr static size_type extent = Extent;

#ifdef DOXYGEN_BUILD
    constexpr span() noexcept
    {
    }
#endif

    template <class It CARB_NO_DOC(
        ,
        std::enable_if_t<std::is_same<typename std::iterator_traits<It>::iterator_category, std::random_access_iterator_tag>::value,
                         bool> = false)>
    constexpr explicit span(It first, size_type count) : m_p(to_address(first))
    {
        CARB_ASSERT(extent == count, "Behavior is undefined if count != extent");
    }

    template <class It CARB_NO_DOC(
        ,
        std::enable_if_t<std::is_same<typename std::iterator_traits<It>::iterator_category, std::random_access_iterator_tag>::value,
                         bool> = false)>
    constexpr explicit span(It first, It last) : m_p(to_address(first))
    {
        CARB_ASSERT((last - first) == extent, "Behavior is undefined if (last - first) != extent");
    }

    template <std::size_t N>
    constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept : m_p(cpp::data(arr))
    {
        static_assert(N == extent, "Undefined if N != extent");
    }

    template <class U, std::size_t N CARB_NO_DOC(, std::enable_if_t<std::is_convertible<U, element_type>::value, bool> = false)>
    constexpr span(std::array<U, N>& arr) noexcept : m_p(cpp::data(arr))
    {
        static_assert(N == extent, "Undefined if N != extent");
    }

    template <class U, std::size_t N CARB_NO_DOC(, std::enable_if_t<std::is_convertible<U, element_type>::value, bool> = false)>
    constexpr span(const std::array<U, N>& arr) noexcept : m_p(cpp::data(arr))
    {
        static_assert(N == extent, "Undefined if N != extent");
    }

    // template< class R >
    // explicit(extent != dynamic_extent)
    // constexpr span( R&& range );
    // (Constructor not available without ranges, but approximate constructor follows)

    template <class R CARB_NO_DOC(
        ,
        std::enable_if_t<detail::IsConvertibleRange<cpp::remove_cvref_t<R>, pointer>::value &&
                             // These types have a separate constructor
                             !detail::IsStdArray<cpp::remove_cvref_t<R>>::value &&
                             !detail::IsSpan<cpp::remove_cvref_t<R>>::value && !std::is_array<cpp::remove_cvref_t<R>>::value,
                         bool> = false)>
    constexpr explicit span(R&& range) : m_p(range.data())
    {
        CARB_ASSERT(range.size() == extent, "Behavior is undefined if R.size() != extent");
    }

#ifndef DOXYGEN_BUILD
    template <class U,
              std::size_t N,
              std::enable_if_t<N == dynamic_extent && std::is_convertible<U, element_type>::value, bool> = false>
    constexpr explicit span(const span<U, N>& source) noexcept : m_p(source.data())
    {
        CARB_ASSERT(source.size() == extent, "Behavior is undefined if source.size() != extent");
    }

    template <class U,
              std::size_t N,
              std::enable_if_t<N != dynamic_extent && std::is_convertible<U, element_type>::value, bool> = false>
#else
    template <class U, std::size_t N>
#endif
    constexpr span(const span<U, N>& source) noexcept : m_p(source.data())
    {
        static_assert(N == extent, "Undefined if N != extent");
    }

    constexpr span(const span& other) noexcept = default;

    constexpr span& operator=(const span& other) noexcept = default;

    constexpr iterator begin() const noexcept
    {
        return iterator(m_p);
    }

    constexpr iterator end() const noexcept
    {
        return iterator(m_p + extent);
    }

    constexpr reverse_iterator rbegin() const noexcept
    {
        return reverse_iterator(end());
    }

    constexpr reverse_iterator rend() const noexcept
    {
        return reverse_iterator(begin());
    }

    constexpr reference front() const noexcept /*strengthened*/
    {
        return data()[0];
    }

    constexpr reference back() const noexcept /*strengthened*/
    {
        return data()[extent - 1];
    }

    constexpr reference at(size_type index) const
    {
        CARBLOCAL_THROW_OR_FATAL(index < extent, "Given index is not within the extent of the span");
        return data()[index];
    }

    constexpr reference operator[](size_type index) const noexcept /*strengthened*/
    {
        CARB_ASSERT(index < extent);
        return data()[index];
    }

    constexpr pointer data() const noexcept
    {
        return m_p;
    }

    constexpr size_type size() const noexcept
    {
        return extent;
    }

    constexpr size_type size_bytes() const noexcept
    {
        return sizeof(element_type) * extent;
    }

    [[nodiscard]] constexpr bool empty() const noexcept
    {
        return false;
    }

    template <std::size_t Count>
    constexpr span<element_type, Count> first() const noexcept /*strengthened*/
    {
        static_assert(Count <= extent, "Program ill-formed if Count > extent");
        return span<element_type, Count>{ data(), Count };
    }

    constexpr span<element_type, dynamic_extent> first(size_type Count) const noexcept /*strengthened*/
    {
        CARB_ASSERT(Count <= size(), "Behavior is undefined if Count > size()");
        return span<element_type, dynamic_extent>{ data(), Count };
    }

    template <std::size_t Count>
    constexpr span<element_type, Count> last() const noexcept /*strengthened*/
    {
        static_assert(Count <= extent, "Program ill-formed if Count > extent");
        return span<element_type, Count>{ data() + (extent - Count), Count };
    }

    constexpr span<element_type, dynamic_extent> last(size_type Count) const noexcept /*strengthened*/
    {
        CARB_ASSERT(Count <= size(), "Behavior is undefined if Count > extent");
        return span<element_type, dynamic_extent>{ m_p + (extent - Count), Count };
    }

    template <std::size_t Offset, std::size_t Count = dynamic_extent>
    constexpr span<element_type, detail::DetermineSubspanExtent<extent, Offset, Count>::value> subspan() const noexcept /*strengthened*/
    {
        static_assert(!(Offset > extent), "Ill-formed if Offset > extent");
        static_assert(!(Count != dynamic_extent && Count > (extent - Offset)),
                      "Ill-formed if Count is not dynamic_extent and Count is greater than (extent - Offset)");
        return span<element_type, detail::DetermineSubspanExtent<extent, Offset, Count>::value>{
            data() + Offset, carb_min(size() - Offset, Count)
        };
    }

    constexpr span<element_type, dynamic_extent> subspan(size_type Offset,
                                                         size_type Count = dynamic_extent) const noexcept /*strengthened*/
    {
        CARB_ASSERT(!(Offset > extent), "Behavior is undefined if Offset > extent");
        CARB_ASSERT(!(Count != dynamic_extent && Count > (extent - Offset)),
                    "Behavior is undefined if Count is not dynamic_extent and is greater than Extent - Offset");
        return { data() + Offset, carb_min(size() - Offset, Count) };
    }

private:
    pointer m_p;
};

CARB_ASSERT_INTEROP_SAFE(span<int, 1>);

// Doxygen can ignore the specializations
#ifndef DOXYGEN_BUILD
// Specialization for dynamic_extent
template <class T>
class span<T, dynamic_extent>
{
    // NOTE: This specialization is for Extent == dynamic_extent
public:
    using element_type = T;
    using value_type = std::remove_cv_t<T>;
    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;
    using pointer = T*;
    using const_pointer = const T*;
    using reference = T&;
    using const_reference = const T&;
    using iterator = omni::detail::PointerIterator<pointer, span>;
    using reverse_iterator = std::reverse_iterator<iterator>;

    constexpr static size_type extent = dynamic_extent;

    constexpr span() noexcept = default;

    template <class It,
              std::enable_if_t<std::is_same<typename std::iterator_traits<It>::iterator_category, std::random_access_iterator_tag>::value,
                               bool> = false>
    constexpr span(It first, size_type count) noexcept(noexcept(to_address(first))) /*strengthened*/
        : m_p(to_address(first)), m_size(count)
    {
    }

    template <class It,
              std::enable_if_t<std::is_same<typename std::iterator_traits<It>::iterator_category, std::random_access_iterator_tag>::value,
                               bool> = false>
    constexpr span(It first, It last) noexcept(noexcept(to_address(first))) /*strengthened*/
        : m_p(to_address(first)), m_size(last - first)
    {
    }

    template <std::size_t N>
    constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept : m_p(cpp::data(arr)), m_size(N)
    {
    }

    template <class U, std::size_t N, std::enable_if_t<std::is_convertible<U, element_type>::value, bool> = false>
    constexpr span(std::array<U, N>& arr) noexcept : m_p(cpp::data(arr)), m_size(N)
    {
    }

    template <class U, std::size_t N, std::enable_if_t<std::is_convertible<U, element_type>::value, bool> = false>
    constexpr span(const std::array<U, N>& arr) noexcept : m_p(cpp::data(arr)), m_size(N)
    {
    }

    // template< class R >
    // explicit(extent != dynamic_extent)
    // constexpr span( R&& range );
    // (Constructor not available without ranges, but approximate constructor follows)

    template <class R,
              std::enable_if_t<detail::IsConvertibleRange<cpp::remove_cvref_t<R>, pointer>::value &&
                                   !detail::IsStdArray<cpp::remove_cvref_t<R>>::value && // has separate constructor
                                   !detail::IsSpan<cpp::remove_cvref_t<R>>::value && // has separate constructor
                                   !std::is_array<cpp::remove_cvref_t<R>>::value, // has separate constructor
                               bool> = false>
    constexpr span(R&& range) : m_p(range.data()), m_size(range.size())
    {
    }

    template <class U, std::size_t N, std::enable_if_t<std::is_convertible<U, element_type>::value, bool> = false>
    constexpr span(const span<U, N>& source) noexcept : m_p(source.data()), m_size(source.size())
    {
    }

    constexpr span(const span& other) noexcept = default;

    constexpr span& operator=(const span& other) noexcept = default;

    constexpr iterator begin() const noexcept
    {
        return iterator(data());
    }

    constexpr iterator end() const noexcept
    {
        return iterator(data() + size());
    }

    constexpr reverse_iterator rbegin() const noexcept
    {
        return reverse_iterator(end());
    }

    constexpr reverse_iterator rend() const noexcept
    {
        return reverse_iterator(begin());
    }

    constexpr reference front() const
    {
        CARB_ASSERT(!empty(), "Behavior is undefined when calling front() on an empty span");
        return data()[0];
    }

    constexpr reference back() const
    {
        CARB_ASSERT(!empty(), "Behavior is undefined when calling back() on an empty span");
        return data()[size() - 1];
    }

    constexpr reference at(size_type index) const
    {
        CARBLOCAL_THROW_OR_FATAL(index < m_size, "Given index is not within the extent of the span");
        return data()[index];
    }

    constexpr reference operator[](size_type index) const noexcept /*strengthened*/
    {
        CARB_ASSERT(index < size());
        return data()[index];
    }

    constexpr pointer data() const noexcept
    {
        return m_p;
    }

    constexpr size_type size() const noexcept
    {
        return m_size;
    }

    constexpr size_type size_bytes() const noexcept
    {
        CARB_ASSERT(size() <= (dynamic_extent / sizeof(element_type)), "size_bytes() exceeds maximum value of size_type");
        return sizeof(element_type) * size();
    }

    [[nodiscard]] constexpr bool empty() const noexcept
    {
        return size() == 0;
    }

    template <std::size_t Count>
    constexpr span<element_type, Count> first() const
    {
        CARB_ASSERT(!(Count > size()), "Behavior is undefined when Count > size()");
        return span<element_type, Count>{ data(), Count };
    }

    constexpr span<element_type, dynamic_extent> first(size_type Count) const
    {
        CARB_ASSERT(!(Count > size()), "Behavior is undefined when Count > size()");
        return span<element_type, dynamic_extent>{ data(), Count };
    }

    template <std::size_t Count>
    constexpr span<element_type, Count> last() const
    {
        CARB_ASSERT(!(Count > size()), "Behavior is undefined when Count > size()");
        return span<element_type, Count>{ data() + (size() - Count), Count };
    }

    constexpr span<element_type, dynamic_extent> last(size_type Count) const
    {
        CARB_ASSERT(!(Count > size()), "Behavior is undefined when Count > size()");
        return span<element_type, dynamic_extent>{ data() + (size() - Count), Count };
    }

    template <std::size_t Offset, std::size_t Count = dynamic_extent>
    constexpr span<element_type, detail::DetermineSubspanExtent<extent, Offset, Count>::value> subspan() const
    {
        CARB_ASSERT(!(Offset > size()), "Behavior is undefined when Offset > size()");
        CARB_ASSERT(!(Count != dynamic_extent && Count > (size() - Offset)),
                    "Behavior is undefined when Count != dynamic_extent and Count > (size() - Offset)");
        return span<element_type, detail::DetermineSubspanExtent<extent, Offset, Count>::value>{
            data() + Offset, carb_min(size() - Offset, Count)
        };
    }

    constexpr span<element_type, dynamic_extent> subspan(size_type Offset, size_type Count = dynamic_extent) const
    {
        CARB_ASSERT(!(Offset > size()), "Behavior is undefined when Offset > size()");
        CARB_ASSERT(!(Count != dynamic_extent && Count > (size() - Offset)),
                    "Behavior is undefined when Count != dynamic_extent and (Offset + Count) > size()");
        return { data() + Offset, carb_min(size() - Offset, Count) };
    }

private:
    pointer m_p = nullptr;
    size_type m_size = 0;
};

// Specialization for zero
template <class T>
class span<T, 0>
{
    // NOTE: This specialization is for Extent == 0
public:
    using element_type = T;
    using value_type = std::remove_cv_t<T>;
    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;
    using pointer = T*;
    using const_pointer = const T*;
    using reference = T&;
    using const_reference = const T&;
    using iterator = omni::detail::PointerIterator<pointer, span>;
    using reverse_iterator = std::reverse_iterator<iterator>;

    constexpr static size_type extent = 0;

    constexpr span() noexcept = default;

    template <class It,
              std::enable_if_t<std::is_same<typename std::iterator_traits<It>::iterator_category, std::random_access_iterator_tag>::value,
                               bool> = false>
    constexpr explicit span(It first, size_type count) : m_p(to_address(first))
    {
        CARB_UNUSED(count);
        CARB_ASSERT(count == 0, "Behavior is undefined if count != extent");
    }

    template <class It,
              std::enable_if_t<std::is_same<typename std::iterator_traits<It>::iterator_category, std::random_access_iterator_tag>::value,
                               bool> = false>
    constexpr explicit span(It first, It last) : m_p(to_address(first))
    {
        CARB_UNUSED(last);
        CARB_ASSERT(first == last, "Behavior is undefined if (last - first) != extent");
    }

    template <std::size_t N>
    constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept : m_p(cpp::data(arr))
    {
        static_assert(N == extent, "Undefined if N != extent");
    }

    template <class U, std::size_t N, std::enable_if_t<std::is_convertible<U, element_type>::value, bool> = false>
    constexpr span(std::array<U, N>& arr) noexcept : m_p(cpp::data(arr))
    {
        static_assert(N == extent, "Undefined if N != extent");
    }

    template <class U, std::size_t N, std::enable_if_t<std::is_convertible<U, element_type>::value, bool> = false>
    constexpr span(const std::array<U, N>& arr) noexcept : m_p(cpp::data(arr))
    {
        static_assert(N == extent, "Undefined if N != extent");
    }

    // template< class R >
    // explicit(extent != dynamic_extent)
    // constexpr span( R&& range );
    // (Constructor not available without ranges, but approximate constructor follows)

    template <class R,
              std::enable_if_t<detail::IsConvertibleRange<cpp::remove_cvref_t<R>, pointer>::value &&
                                   !detail::IsStdArray<cpp::remove_cvref_t<R>>::value && // has separate constructor
                                   !detail::IsSpan<cpp::remove_cvref_t<R>>::value && // has separate constructor
                                   !std::is_array<cpp::remove_cvref_t<R>>::value, // has separate constructor
                               bool> = false>
    constexpr explicit span(R&& range) : m_p(range.data())
    {
        CARB_ASSERT(range.size() == extent, "Behavior is undefined if R.size() != extent");
    }

    template <class U,
              std::size_t N,
              std::enable_if_t<N == dynamic_extent && std::is_convertible<U, element_type>::value, bool> = false>
    constexpr explicit span(const span<U, N>& source) noexcept : m_p(source.data())
    {
        CARB_ASSERT(source.size() == extent, "Behavior is undefined if source.size() != extent");
    }

    template <class U,
              std::size_t N,
              std::enable_if_t<N != dynamic_extent && std::is_convertible<U, element_type>::value, bool> = false>
    constexpr span(const span<U, N>& source) noexcept : m_p(source.data())
    {
        CARB_ASSERT(N == extent, "Behavior is undefined if N != extent");
    }

    constexpr span(const span& other) noexcept = default;

    constexpr span& operator=(const span& other) noexcept = default;

    constexpr iterator begin() const noexcept
    {
        return end();
    }

    constexpr iterator end() const noexcept
    {
        return iterator(m_p);
    }

    constexpr reverse_iterator rbegin() const noexcept
    {
        return rend();
    }

    constexpr reverse_iterator rend() const noexcept
    {
        return reverse_iterator(begin());
    }

    constexpr reference front() const noexcept /*strengthened*/
    {
        CARB_ASSERT(!empty(), "Behavior is undefined if front() is called on an empty span");
        return data()[0];
    }

    constexpr reference back() const noexcept /*strengthened*/
    {
        CARB_ASSERT(!empty(), "Behavior is undefined if back() is called on an empty span");
        return data()[size() - 1];
    }

    constexpr reference at(size_type index) const
    {
        CARBLOCAL_THROW_OR_FATAL(index < size(), "Given index is not within the extent of the span");
        return data()[index];
    }

    constexpr reference operator[](size_type index) const noexcept /*strengthened*/
    {
        CARB_ASSERT(false, "Behavior is undefined if given index is not within the extent of the span");
        return data()[index];
    }

    constexpr pointer data() const noexcept
    {
        return m_p;
    }

    constexpr size_type size() const noexcept
    {
        return 0;
    }

    constexpr size_type size_bytes() const noexcept
    {
        return 0;
    }

    [[nodiscard]] constexpr bool empty() const noexcept
    {
        return true;
    }

    template <std::size_t Count>
    constexpr span<element_type, Count> first() const noexcept /*strengthened*/
    {
        static_assert(Count <= extent, "Program ill-formed if Count > extent");
        return span<element_type, Count>{ data(), Count };
    }

    constexpr span<element_type, dynamic_extent> first(size_type Count) const noexcept /*strengthened*/
    {
        CARB_UNUSED(Count);
        CARB_ASSERT(!(Count > extent), "Behavior is undefined if Count > extent");
        return span<element_type, dynamic_extent>{ data(), Count };
    }

    template <std::size_t Count>
    constexpr span<element_type, Count> last() const noexcept /*strengthened*/
    {
        static_assert(Count == 0, "Program ill-formed if Count > extent");
        return span<element_type, Count>{ data() + (extent - Count), Count };
    }

    constexpr span<element_type, dynamic_extent> last(size_type Count) const noexcept /*strengthened*/
    {
        CARB_UNUSED(Count);
        CARB_ASSERT(!(Count > extent), "Behavior is undefined if Count > extent");
        return span<element_type, dynamic_extent>{ data() + (extent - Count), Count };
    }

    template <std::size_t Offset, std::size_t Count = dynamic_extent>
    constexpr span<element_type, detail::DetermineSubspanExtent<extent, Offset, Count>::value> subspan() const noexcept /*strengthened*/
    {
        static_assert(!(Offset > extent), "Ill-formed if Offset > extent");
        static_assert(!(Count != dynamic_extent && Count > (extent - Offset)),
                      "Ill-formed if Count is not dynamic_extent and Count is greater than (extent - Offset)");
        return span<element_type, detail::DetermineSubspanExtent<extent, Offset, Count>::value>{
            data() + Offset, carb_min(size() - Offset, Count)
        };
    }

    constexpr span<element_type, dynamic_extent> subspan(size_type Offset,
                                                         size_type Count = dynamic_extent) const noexcept /*strengthened*/
    {
        CARB_UNUSED(Offset, Count);
        CARB_ASSERT(!(Offset > extent), "Behavior is undefined if Offset > extent");
        CARB_ASSERT(!(Count != dynamic_extent && Count > (extent - Offset)),
                    "Behavior is undefined if Count is not dynamic_extent and is greater than (extent - Offset)");
        return { data() + Offset, carb_min(size() - Offset, Count) };
    }

private:
    pointer m_p = nullptr;
};

CARB_ASSERT_INTEROP_SAFE(span<int, 0>);
CARB_ASSERT_INTEROP_SAFE(span<int, dynamic_extent>);
#endif

#ifndef DOXYGEN_BUILD
template <class T, std::size_t N, std::enable_if_t<N == dynamic_extent, bool> = false>
span<const cpp::byte, dynamic_extent> as_bytes(span<T, N> s) noexcept
{
    return { reinterpret_cast<const cpp::byte*>(s.data()), s.size_bytes() };
}

template <class T, std::size_t N, std::enable_if_t<N != dynamic_extent, bool> = false>
span<const cpp::byte, sizeof(T) * N> as_bytes(span<T, N> s) noexcept
#else
template <class T, std::size_t N>
auto as_bytes(span<T, N> s) noexcept
#endif
{
    return span<const cpp::byte, sizeof(T) * N>{ reinterpret_cast<const cpp::byte*>(s.data()), s.size_bytes() };
}

#ifndef DOXYGEN_BUILD
template <class T, std::size_t N, std::enable_if_t<N == dynamic_extent, bool> = false>
span<cpp::byte, dynamic_extent> as_writable_bytes(span<T, N> s) noexcept
{
    return { reinterpret_cast<cpp::byte*>(s.data()), s.size_bytes() };
}

template <class T, std::size_t N, std::enable_if_t<N != dynamic_extent, bool> = false>
span<cpp::byte, sizeof(T) * N> as_writable_bytes(span<T, N> s) noexcept
#else
template <class T, std::size_t N>
auto as_writable_bytes(span<T, N> s) noexcept
#endif
{
    return span<cpp::byte, sizeof(T) * N>{ reinterpret_cast<cpp::byte*>(s.data()), s.size_bytes() };
}

#undef CARBLOCAL_THROW_OR_FATAL

} // namespace cpp
} // namespace carb