[libcamera-devel,01/31] libcamera: Add a C++20-compliant std::span<> implementation

Message ID 20200229164254.23604-2-laurent.pinchart@ideasonboard.com
State Superseded
Headers show
Series
  • libcamera: Add support for array controls
Related show

Commit Message

Laurent Pinchart Feb. 29, 2020, 4:42 p.m. UTC
From: Jacopo Mondi <jacopo@jmondi.org>

C++20 will contain a std::span<> class that represents a contiguous
sequence of objects. Its main purpose is to group array pointers and
size together in APIs.

Add a compatible implementation to the utils namespace. This will be
used to implement array controls.

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 Documentation/Doxyfile.in     |   4 +-
 include/libcamera/meson.build |   1 +
 include/libcamera/span.h      | 417 ++++++++++++++++++++++++++++++++++
 src/libcamera/meson.build     |   1 +
 src/libcamera/span.cpp        |  12 +
 5 files changed, 434 insertions(+), 1 deletion(-)
 create mode 100644 include/libcamera/span.h
 create mode 100644 src/libcamera/span.cpp

Comments

Kieran Bingham March 2, 2020, 10:21 p.m. UTC | #1
Hi Laurent / Jacopo,

On 29/02/2020 16:42, Laurent Pinchart wrote:
> From: Jacopo Mondi <jacopo@jmondi.org>
> 
> C++20 will contain a std::span<> class that represents a contiguous

Will contain ? or contains ...

(Yes, I know it depends on when you wrote that, but it might already be
out-dated)

> sequence of objects. 

How about the following text from the C++20 draft:

"A span is a view over a contiguous sequence of objects, the storage of
which is owned by some other object."

>Its main purpose is to group array pointers and
> size together in APIs.

"It provides a common interface to a contiguous collection of objects
and supports both a static extent where the number of elements is known
and stored as the size, and a dynamic extent where the number of
elements is not known or restricted by the implementation."

(Correct that where necessary or entirely)


> 
> Add a compatible implementation to the utils namespace. This will be
> used to implement array controls.
> 
> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

I'm not going to admit any kind of real Reviewed-by: on 417 lines of
template magic.

But if this is working, and follows the C++ interfaces, then (with the
commit message improved a little, and possibly dropping span.cpp, and
associated addition in src/libcamera/meson.build which seems pointless)

Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>



> ---
>  Documentation/Doxyfile.in     |   4 +-
>  include/libcamera/meson.build |   1 +
>  include/libcamera/span.h      | 417 ++++++++++++++++++++++++++++++++++
>  src/libcamera/meson.build     |   1 +

>  src/libcamera/span.cpp        |  12 +

Do we really need to add an empty span.cpp ?

>  5 files changed, 434 insertions(+), 1 deletion(-)
>  create mode 100644 include/libcamera/span.h
>  create mode 100644 src/libcamera/span.cpp
> 
> diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
> index beeaf6d3cf48..457e23a086a2 100644
> --- a/Documentation/Doxyfile.in
> +++ b/Documentation/Doxyfile.in
> @@ -840,8 +840,10 @@ RECURSIVE              = YES
>  # Note that relative paths are relative to the directory from which doxygen is
>  # run.
>  
> -EXCLUDE                = @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \
> +EXCLUDE                = @TOP_SRCDIR@/include/libcamera/span.h \
> +			 @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \
>  			 @TOP_SRCDIR@/src/libcamera/device_enumerator_udev.cpp \
> +			 @TOP_SRCDIR@/src/libcamera/span.cpp \

We even exclude this empty file from the doxygen build, so it really
does seem pointless.


>  			 @TOP_SRCDIR@/src/libcamera/include/device_enumerator_sysfs.h \
>  			 @TOP_SRCDIR@/src/libcamera/include/device_enumerator_udev.h \
>  			 @TOP_SRCDIR@/src/libcamera/pipeline/ \
> diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build
> index f58c02d2cf35..f47c583cbbc0 100644
> --- a/include/libcamera/meson.build
> +++ b/include/libcamera/meson.build
> @@ -14,6 +14,7 @@ libcamera_api = files([
>      'pixelformats.h',
>      'request.h',
>      'signal.h',
> +    'span.h',
>      'stream.h',
>      'timer.h',
>  ])
> diff --git a/include/libcamera/span.h b/include/libcamera/span.h
> new file mode 100644
> index 000000000000..513ddb432405
> --- /dev/null
> +++ b/include/libcamera/span.h
> @@ -0,0 +1,417 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2020, Google Inc.
> + *
> + * span.h - C++20 std::span<> implementation for C++11
> + */
> +
> +#ifndef __LIBCAMERA_SPAN_H__
> +#define __LIBCAMERA_SPAN_H__
> +
> +#include <array>
> +#include <iterator>
> +#include <limits>
> +#include <stddef.h>
> +#include <type_traits>
> +
> +namespace libcamera {
> +
> +static constexpr std::size_t dynamic_extent = std::numeric_limits<std::size_t>::max();
> +
> +template<typename T, std::size_t Extent = dynamic_extent>
> +class Span;
> +
> +namespace details {
> +
> +template<typename U>
> +struct is_array : public std::false_type {
> +};
> +
> +template<typename U, std::size_t N>
> +struct is_array<std::array<U, N>> : public std::true_type {
> +};
> +
> +template<typename U>
> +struct is_span : public std::false_type {
> +};
> +
> +template<typename U, std::size_t Extent>
> +struct is_span<Span<U, Extent>> : public std::true_type {
> +};
> +
> +} /* namespace details */
> +
> +namespace utils {
> +
> +template<typename C>
> +constexpr auto size(const C &c) -> decltype(c.size())
> +{
> +	return c.size();
> +}
> +
> +template<typename C>
> +constexpr auto data(const C &c) -> decltype(c.data())
> +{
> +	return c.data();
> +}
> +
> +template<typename C>
> +constexpr auto data(C &c) -> decltype(c.data())
> +{
> +	return c.data();
> +}
> +
> +template<class T, std::size_t N>
> +constexpr T *data(T (&array)[N]) noexcept
> +{
> +	return array;
> +}
> +
> +template<std::size_t I, typename T>
> +struct tuple_element;
> +
> +template<std::size_t I, typename T, std::size_t N>
> +struct tuple_element<I, Span<T, N>> {
> +	using type = T;
> +};
> +
> +template<typename T>
> +struct tuple_size;
> +
> +template<typename T, std::size_t N>
> +struct tuple_size<Span<T, N>> : public std::integral_constant<std::size_t, N> {
> +};
> +
> +template<typename T>
> +struct tuple_size<Span<T, dynamic_extent>>;
> +
> +} /* namespace utils */
> +
> +template<typename T, std::size_t Extent>
> +class Span
> +{
> +public:
> +	using element_type = T;
> +	using value_type = typename 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 = pointer;
> +	using const_iterator = const_pointer;
> +	using reverse_iterator = std::reverse_iterator<iterator>;
> +	using const_reverse_iterator = std::reverse_iterator<const_iterator>;
> +
> +	static constexpr std::size_t extent = Extent;
> +
> +	template<bool Dependent = false,
> +		 typename = std::enable_if_t<Dependent || Extent == 0>>
> +	constexpr Span() noexcept
> +		: data_(nullptr)
> +	{
> +	}
> +
> +	constexpr Span(pointer ptr, size_type count)
> +		: data_(ptr)
> +	{
> +	}
> +
> +	constexpr Span(pointer first, pointer last)
> +		: data_(first)
> +	{
> +	}
> +
> +	template<std::size_t N>
> +	constexpr Span(element_type (&arr)[N],
> +		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
> +							    element_type (*)[]>::value &&
> +					N == Extent,
> +					std::nullptr_t> = nullptr) noexcept
> +		: data_(arr)
> +	{
> +	}
> +
> +	template<std::size_t N>
> +	constexpr Span(std::array<value_type, N> &arr,
> +		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
> +							    element_type (*)[]>::value &&
> +					N == Extent,
> +					std::nullptr_t> = nullptr) noexcept
> +		: data_(arr.data())
> +	{
> +	}
> +
> +	template<std::size_t N>
> +	constexpr Span(const std::array<value_type, N> &arr,
> +		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
> +							    element_type (*)[]>::value &&
> +					N == Extent,
> +					std::nullptr_t> = nullptr) noexcept
> +		: data_(arr.data())
> +	{
> +	}
> +
> +	template<class Container>
> +	constexpr Span(Container &cont,
> +		       std::enable_if_t<!details::is_span<Container>::value &&
> +					!details::is_array<Container>::value &&
> +					!std::is_array<Container>::value &&
> +					std::is_convertible<std::remove_pointer_t<decltype(utils::data(cont))> (*)[],
> +							    element_type (*)[]>::value,
> +					std::nullptr_t> = nullptr)
> +		: data_(utils::data(cont))
> +	{
> +	}
> +
> +	template<class Container>
> +	constexpr Span(const Container &cont,
> +		       std::enable_if_t<!details::is_span<Container>::value &&
> +					!details::is_array<Container>::value &&
> +					!std::is_array<Container>::value &&
> +					std::is_convertible<std::remove_pointer_t<decltype(utils::data(cont))> (*)[],
> +							    element_type (*)[]>::value,
> +					std::nullptr_t> = nullptr)
> +		: data_(utils::data(cont))
> +	{
> +		static_assert(utils::size(cont) == Extent, "Size mismatch");
> +	}
> +
> +	template<class U, std::size_t N>
> +	constexpr Span(const Span<U, N> &s,
> +		       std::enable_if_t<std::is_convertible<U (*)[], element_type (*)[]>::value &&
> +					N == Extent,
> +					std::nullptr_t> = nullptr) noexcept
> +		: data_(s.data())
> +	{
> +	}
> +
> +	constexpr Span(const Span &other) noexcept = default;
> +
> +	constexpr Span &operator=(const Span &other) noexcept
> +	{
> +		data_ = other.data_;
> +		return *this;
> +	}
> +
> +	constexpr iterator begin() const { return data(); }
> +	constexpr const_iterator cbegin() const { return begin(); }
> +	constexpr iterator end() const { return data() + size(); }
> +	constexpr const_iterator cend() const { return end(); }
> +	constexpr reverse_iterator rbegin() const { return reverse_iterator(data() + size() - 1); }
> +	constexpr const_reverse_iterator crbegin() const { return rbegin(); }
> +	constexpr reverse_iterator rend() const { return reverse_iterator(data() - 1); }
> +	constexpr const_reverse_iterator crend() const { return rend(); }
> +
> +	constexpr reference front() const { return *data(); }
> +	constexpr reference back() const { return *(data() + size() - 1); }
> +	constexpr reference operator[](size_type idx) const { return data()[idx]; }
> +	constexpr pointer data() const noexcept { return data_; }
> +
> +	constexpr size_type size() const noexcept { return Extent; }
> +	constexpr size_type size_bytes() const noexcept { return size() * sizeof(element_type); }
> +	constexpr bool empty() const noexcept { return size() == 0; }
> +
> +	template<std::size_t Count>
> +	constexpr Span<element_type, Count> first() const
> +	{
> +		static_assert(Count <= Extent, "Count larger than size");
> +		return { data(), Count };
> +	}
> +
> +	constexpr Span<element_type, dynamic_extent> first(std::size_t Count) const
> +	{
> +		return { data(), Count };
> +	}
> +
> +	template<std::size_t Count>
> +	constexpr Span<element_type, Count> last() const
> +	{
> +		static_assert(Count <= Extent, "Count larger than size");
> +		return { data() + size() - Count, Count };
> +	}
> +
> +	constexpr Span<element_type, dynamic_extent> last(std::size_t Count) const
> +	{
> +		return { data() + size() - Count, Count };
> +	}
> +
> +	template<std::size_t Offset, std::size_t Count = dynamic_extent>
> +	constexpr Span<element_type, Count != dynamic_extent ? Count : Extent - Offset> subspan() const
> +	{
> +		static_assert(Offset <= Extent, "Offset larger than size");
> +		static_assert(Count == dynamic_extent || Count + Offset <= Extent,
> +			      "Offset + Count larger than size");
> +		return { data() + Offset, Count == dynamic_extent ? size() - Offset : Count };
> +	}
> +
> +	constexpr Span<element_type, dynamic_extent>
> +	subspan(std::size_t Offset, std::size_t Count = dynamic_extent) const
> +	{
> +		return { data() + Offset, Count == dynamic_extent ? size() - Offset : Count };
> +	}
> +
> +private:
> +	pointer data_;
> +};
> +
> +template<typename T>
> +class Span<T, dynamic_extent>
> +{
> +public:
> +	using element_type = T;
> +	using value_type = typename 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 = T *;
> +	using const_iterator = const T *;
> +	using reverse_iterator = std::reverse_iterator<iterator>;
> +	using const_reverse_iterator = std::reverse_iterator<const_iterator>;
> +
> +	static constexpr std::size_t extent = dynamic_extent;
> +
> +	constexpr Span() noexcept
> +		: data_(nullptr), size_(0)
> +	{
> +	}
> +
> +	constexpr Span(pointer ptr, size_type count)
> +		: data_(ptr), size_(count)
> +	{
> +	}
> +
> +	constexpr Span(pointer first, pointer last)
> +		: data_(first), size_(last - first)
> +	{
> +	}
> +
> +	template<std::size_t N>
> +	constexpr Span(element_type (&arr)[N],
> +		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
> +							    element_type (*)[]>::value,
> +					std::nullptr_t> = nullptr) noexcept
> +		: data_(arr), size_(N)
> +	{
> +	}
> +
> +	template<std::size_t N>
> +	constexpr Span(std::array<value_type, N> &arr,
> +		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
> +							    element_type (*)[]>::value,
> +					std::nullptr_t> = nullptr) noexcept
> +		: data_(utils::data(arr)), size_(N)
> +	{
> +	}
> +
> +	template<std::size_t N>
> +	constexpr Span(const std::array<value_type, N> &arr) noexcept
> +		: data_(utils::data(arr)), size_(N)
> +	{
> +	}
> +
> +	template<class Container>
> +	constexpr Span(Container &cont,
> +		       std::enable_if_t<!details::is_span<Container>::value &&
> +					!details::is_array<Container>::value &&
> +					!std::is_array<Container>::value &&
> +					std::is_convertible<std::remove_pointer_t<decltype(utils::data(cont))> (*)[],
> +							    element_type (*)[]>::value,
> +					std::nullptr_t> = nullptr)
> +		: data_(utils::data(cont)), size_(utils::size(cont))
> +	{
> +	}
> +
> +	template<class Container>
> +	constexpr Span(const Container &cont,
> +		       std::enable_if_t<!details::is_span<Container>::value &&
> +					!details::is_array<Container>::value &&
> +					!std::is_array<Container>::value &&
> +					std::is_convertible<std::remove_pointer_t<decltype(utils::data(cont))> (*)[],
> +							    element_type (*)[]>::value,
> +					std::nullptr_t> = nullptr)
> +		: data_(utils::data(cont)), size_(utils::size(cont))
> +	{
> +	}
> +
> +	template<class U, std::size_t N>
> +	constexpr Span(const Span<U, N> &s,
> +		       std::enable_if_t<std::is_convertible<U (*)[], element_type (*)[]>::value,
> +					std::nullptr_t> = nullptr) noexcept
> +		: data_(s.data()), size_(s.size())
> +	{
> +	}
> +
> +	constexpr Span(const Span &other) noexcept = default;
> +
> +	constexpr Span &operator=(const Span &other) noexcept
> +	{
> +		data_ = other.data_;
> +		size_ = other.size_;
> +		return *this;
> +	}
> +
> +	constexpr iterator begin() const { return data(); }
> +	constexpr const_iterator cbegin() const { return begin(); }
> +	constexpr iterator end() const { return data() + size(); }
> +	constexpr const_iterator cend() const { return end(); }
> +	constexpr reverse_iterator rbegin() const { return reverse_iterator(data() + size() - 1); }
> +	constexpr const_reverse_iterator crbegin() const { return rbegin(); }
> +	constexpr reverse_iterator rend() const { return reverse_iterator(data() - 1); }
> +	constexpr const_reverse_iterator crend() const { return rend(); }
> +
> +	constexpr reference front() const { return *data(); }
> +	constexpr reference back() const { return *(data() + size() - 1); }
> +	constexpr reference operator[](size_type idx) const { return data()[idx]; }
> +	constexpr pointer data() const noexcept { return data_; }
> +
> +	constexpr size_type size() const noexcept { return size_; }
> +	constexpr size_type size_bytes() const noexcept { return size() * sizeof(element_type); }
> +	constexpr bool empty() const noexcept { return size() == 0; }
> +
> +	template<std::size_t Count>
> +	constexpr Span<element_type, Count> first() const
> +	{
> +		return { data(), Count };
> +	}
> +
> +	constexpr Span<element_type, dynamic_extent> first(std::size_t Count) const
> +	{
> +		return { data(), Count };
> +	}
> +
> +	template<std::size_t Count>
> +	constexpr Span<element_type, Count> last() const
> +	{
> +		return { data() + size() - Count, Count };
> +	}
> +
> +	constexpr Span<element_type, dynamic_extent> last(std::size_t Count) const
> +	{
> +		return { data() + size() - Count, Count };
> +	}
> +
> +	template<std::size_t Offset, std::size_t Count = dynamic_extent>
> +	constexpr Span<element_type, Count> subspan() const
> +	{
> +		return { data() + Offset, Count == dynamic_extent ? size() - Offset : Count };
> +	}
> +
> +	constexpr Span<element_type, dynamic_extent>
> +	subspan(std::size_t Offset, std::size_t Count = dynamic_extent) const
> +	{
> +		return { data() + Offset, Count == dynamic_extent ? size() - Offset : Count };
> +	}
> +
> +private:
> +	pointer data_;
> +	size_type size_;
> +};
> +
> +}; /* namespace libcamera */
> +
> +#endif /* __LIBCAMERA_SPAN_H__ */
> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> index 88658ac563f7..2448f0e96468 100644
> --- a/src/libcamera/meson.build
> +++ b/src/libcamera/meson.build
> @@ -36,6 +36,7 @@ libcamera_sources = files([
>      'request.cpp',
>      'semaphore.cpp',
>      'signal.cpp',
> +    'span.cpp',
>      'stream.cpp',
>      'thread.cpp',
>      'timer.cpp',
> diff --git a/src/libcamera/span.cpp b/src/libcamera/span.cpp
> new file mode 100644
> index 000000000000..753104765cea
> --- /dev/null
> +++ b/src/libcamera/span.cpp
> @@ -0,0 +1,12 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2020, Google Inc.
> + *
> + * span.h - C++20 std::span<> implementation for C++11

.h? But if this file is dropped then it goes away.


> + */
> +
> +#include <libcamera/span.h>
> +
> +namespace libcamera {
> +
> +} /* namespace libcamera */
> 


Do we actually need span.cpp at all?
Laurent Pinchart March 3, 2020, 4:29 p.m. UTC | #2
Hi Kieran,

On Mon, Mar 02, 2020 at 10:21:03PM +0000, Kieran Bingham wrote:
> On 29/02/2020 16:42, Laurent Pinchart wrote:
> > From: Jacopo Mondi <jacopo@jmondi.org>
> > 
> > C++20 will contain a std::span<> class that represents a contiguous
> 
> Will contain ? or contains ...
> 
> (Yes, I know it depends on when you wrote that, but it might already be
> out-dated)

"C++20 is the informal name for the revision of the ISO/IEC standard for
the C++ programming language expected to follow C++17. The standard got
technically finalized by WG21 at the meeting in Prague in February 2020.
The standard is expected to be officially published after the end of the
DIS ballot in May 2020."

So technically we're still correct :-)

> > sequence of objects. 
> 
> How about the following text from the C++20 draft:
> 
> "A span is a view over a contiguous sequence of objects, the storage of
> which is owned by some other object."
> 
> >Its main purpose is to group array pointers and
> > size together in APIs.
> 
> "It provides a common interface to a contiguous collection of objects
> and supports both a static extent where the number of elements is known
> and stored as the size, and a dynamic extent where the number of
> elements is not known or restricted by the implementation."
> 
> (Correct that where necessary or entirely)

I've replaced the commit message with

    libcamera: Add a C++20-compliant std::span<> implementation

    C++20 will contain a std::span<> class that provides view over a
    contiguous sequence of objects, the storage of which is owned by some
    other object.

    Add a compatible implementation to the utils namespace. This will be
    used to implement array controls.

> > Add a compatible implementation to the utils namespace. This will be
> > used to implement array controls.
> > 
> > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> 
> I'm not going to admit any kind of real Reviewed-by: on 417 lines of
> template magic.
> 
> But if this is working, and follows the C++ interfaces, then (with the
> commit message improved a little, and possibly dropping span.cpp, and
> associated addition in src/libcamera/meson.build which seems pointless)

span.cpp ensures that the header is self-contained. It's a bit pointless
as the test case can do so too. I'll modify the test to include
libcamera/span.h first, and drop span.cpp here.

> Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> 
> > ---
> >  Documentation/Doxyfile.in     |   4 +-
> >  include/libcamera/meson.build |   1 +
> >  include/libcamera/span.h      | 417 ++++++++++++++++++++++++++++++++++
> >  src/libcamera/meson.build     |   1 +
> >  src/libcamera/span.cpp        |  12 +
> 
> Do we really need to add an empty span.cpp ?
> 
> >  5 files changed, 434 insertions(+), 1 deletion(-)
> >  create mode 100644 include/libcamera/span.h
> >  create mode 100644 src/libcamera/span.cpp
> > 
> > diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
> > index beeaf6d3cf48..457e23a086a2 100644
> > --- a/Documentation/Doxyfile.in
> > +++ b/Documentation/Doxyfile.in
> > @@ -840,8 +840,10 @@ RECURSIVE              = YES
> >  # Note that relative paths are relative to the directory from which doxygen is
> >  # run.
> >  
> > -EXCLUDE                = @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \
> > +EXCLUDE                = @TOP_SRCDIR@/include/libcamera/span.h \
> > +			 @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \
> >  			 @TOP_SRCDIR@/src/libcamera/device_enumerator_udev.cpp \
> > +			 @TOP_SRCDIR@/src/libcamera/span.cpp \
> 
> We even exclude this empty file from the doxygen build, so it really
> does seem pointless.
> 
> >  			 @TOP_SRCDIR@/src/libcamera/include/device_enumerator_sysfs.h \
> >  			 @TOP_SRCDIR@/src/libcamera/include/device_enumerator_udev.h \
> >  			 @TOP_SRCDIR@/src/libcamera/pipeline/ \
> > diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build
> > index f58c02d2cf35..f47c583cbbc0 100644
> > --- a/include/libcamera/meson.build
> > +++ b/include/libcamera/meson.build
> > @@ -14,6 +14,7 @@ libcamera_api = files([
> >      'pixelformats.h',
> >      'request.h',
> >      'signal.h',
> > +    'span.h',
> >      'stream.h',
> >      'timer.h',
> >  ])
> > diff --git a/include/libcamera/span.h b/include/libcamera/span.h
> > new file mode 100644
> > index 000000000000..513ddb432405
> > --- /dev/null
> > +++ b/include/libcamera/span.h
> > @@ -0,0 +1,417 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2020, Google Inc.
> > + *
> > + * span.h - C++20 std::span<> implementation for C++11
> > + */
> > +
> > +#ifndef __LIBCAMERA_SPAN_H__
> > +#define __LIBCAMERA_SPAN_H__
> > +
> > +#include <array>
> > +#include <iterator>
> > +#include <limits>
> > +#include <stddef.h>
> > +#include <type_traits>
> > +
> > +namespace libcamera {
> > +
> > +static constexpr std::size_t dynamic_extent = std::numeric_limits<std::size_t>::max();
> > +
> > +template<typename T, std::size_t Extent = dynamic_extent>
> > +class Span;
> > +
> > +namespace details {
> > +
> > +template<typename U>
> > +struct is_array : public std::false_type {
> > +};
> > +
> > +template<typename U, std::size_t N>
> > +struct is_array<std::array<U, N>> : public std::true_type {
> > +};
> > +
> > +template<typename U>
> > +struct is_span : public std::false_type {
> > +};
> > +
> > +template<typename U, std::size_t Extent>
> > +struct is_span<Span<U, Extent>> : public std::true_type {
> > +};
> > +
> > +} /* namespace details */
> > +
> > +namespace utils {
> > +
> > +template<typename C>
> > +constexpr auto size(const C &c) -> decltype(c.size())
> > +{
> > +	return c.size();
> > +}
> > +
> > +template<typename C>
> > +constexpr auto data(const C &c) -> decltype(c.data())
> > +{
> > +	return c.data();
> > +}
> > +
> > +template<typename C>
> > +constexpr auto data(C &c) -> decltype(c.data())
> > +{
> > +	return c.data();
> > +}
> > +
> > +template<class T, std::size_t N>
> > +constexpr T *data(T (&array)[N]) noexcept
> > +{
> > +	return array;
> > +}
> > +
> > +template<std::size_t I, typename T>
> > +struct tuple_element;
> > +
> > +template<std::size_t I, typename T, std::size_t N>
> > +struct tuple_element<I, Span<T, N>> {
> > +	using type = T;
> > +};
> > +
> > +template<typename T>
> > +struct tuple_size;
> > +
> > +template<typename T, std::size_t N>
> > +struct tuple_size<Span<T, N>> : public std::integral_constant<std::size_t, N> {
> > +};
> > +
> > +template<typename T>
> > +struct tuple_size<Span<T, dynamic_extent>>;
> > +
> > +} /* namespace utils */
> > +
> > +template<typename T, std::size_t Extent>
> > +class Span
> > +{
> > +public:
> > +	using element_type = T;
> > +	using value_type = typename 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 = pointer;
> > +	using const_iterator = const_pointer;
> > +	using reverse_iterator = std::reverse_iterator<iterator>;
> > +	using const_reverse_iterator = std::reverse_iterator<const_iterator>;
> > +
> > +	static constexpr std::size_t extent = Extent;
> > +
> > +	template<bool Dependent = false,
> > +		 typename = std::enable_if_t<Dependent || Extent == 0>>
> > +	constexpr Span() noexcept
> > +		: data_(nullptr)
> > +	{
> > +	}
> > +
> > +	constexpr Span(pointer ptr, size_type count)
> > +		: data_(ptr)
> > +	{
> > +	}
> > +
> > +	constexpr Span(pointer first, pointer last)
> > +		: data_(first)
> > +	{
> > +	}
> > +
> > +	template<std::size_t N>
> > +	constexpr Span(element_type (&arr)[N],
> > +		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
> > +							    element_type (*)[]>::value &&
> > +					N == Extent,
> > +					std::nullptr_t> = nullptr) noexcept
> > +		: data_(arr)
> > +	{
> > +	}
> > +
> > +	template<std::size_t N>
> > +	constexpr Span(std::array<value_type, N> &arr,
> > +		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
> > +							    element_type (*)[]>::value &&
> > +					N == Extent,
> > +					std::nullptr_t> = nullptr) noexcept
> > +		: data_(arr.data())
> > +	{
> > +	}
> > +
> > +	template<std::size_t N>
> > +	constexpr Span(const std::array<value_type, N> &arr,
> > +		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
> > +							    element_type (*)[]>::value &&
> > +					N == Extent,
> > +					std::nullptr_t> = nullptr) noexcept
> > +		: data_(arr.data())
> > +	{
> > +	}
> > +
> > +	template<class Container>
> > +	constexpr Span(Container &cont,
> > +		       std::enable_if_t<!details::is_span<Container>::value &&
> > +					!details::is_array<Container>::value &&
> > +					!std::is_array<Container>::value &&
> > +					std::is_convertible<std::remove_pointer_t<decltype(utils::data(cont))> (*)[],
> > +							    element_type (*)[]>::value,
> > +					std::nullptr_t> = nullptr)
> > +		: data_(utils::data(cont))
> > +	{
> > +	}
> > +
> > +	template<class Container>
> > +	constexpr Span(const Container &cont,
> > +		       std::enable_if_t<!details::is_span<Container>::value &&
> > +					!details::is_array<Container>::value &&
> > +					!std::is_array<Container>::value &&
> > +					std::is_convertible<std::remove_pointer_t<decltype(utils::data(cont))> (*)[],
> > +							    element_type (*)[]>::value,
> > +					std::nullptr_t> = nullptr)
> > +		: data_(utils::data(cont))
> > +	{
> > +		static_assert(utils::size(cont) == Extent, "Size mismatch");
> > +	}
> > +
> > +	template<class U, std::size_t N>
> > +	constexpr Span(const Span<U, N> &s,
> > +		       std::enable_if_t<std::is_convertible<U (*)[], element_type (*)[]>::value &&
> > +					N == Extent,
> > +					std::nullptr_t> = nullptr) noexcept
> > +		: data_(s.data())
> > +	{
> > +	}
> > +
> > +	constexpr Span(const Span &other) noexcept = default;
> > +
> > +	constexpr Span &operator=(const Span &other) noexcept
> > +	{
> > +		data_ = other.data_;
> > +		return *this;
> > +	}
> > +
> > +	constexpr iterator begin() const { return data(); }
> > +	constexpr const_iterator cbegin() const { return begin(); }
> > +	constexpr iterator end() const { return data() + size(); }
> > +	constexpr const_iterator cend() const { return end(); }
> > +	constexpr reverse_iterator rbegin() const { return reverse_iterator(data() + size() - 1); }
> > +	constexpr const_reverse_iterator crbegin() const { return rbegin(); }
> > +	constexpr reverse_iterator rend() const { return reverse_iterator(data() - 1); }
> > +	constexpr const_reverse_iterator crend() const { return rend(); }
> > +
> > +	constexpr reference front() const { return *data(); }
> > +	constexpr reference back() const { return *(data() + size() - 1); }
> > +	constexpr reference operator[](size_type idx) const { return data()[idx]; }
> > +	constexpr pointer data() const noexcept { return data_; }
> > +
> > +	constexpr size_type size() const noexcept { return Extent; }
> > +	constexpr size_type size_bytes() const noexcept { return size() * sizeof(element_type); }
> > +	constexpr bool empty() const noexcept { return size() == 0; }
> > +
> > +	template<std::size_t Count>
> > +	constexpr Span<element_type, Count> first() const
> > +	{
> > +		static_assert(Count <= Extent, "Count larger than size");
> > +		return { data(), Count };
> > +	}
> > +
> > +	constexpr Span<element_type, dynamic_extent> first(std::size_t Count) const
> > +	{
> > +		return { data(), Count };
> > +	}
> > +
> > +	template<std::size_t Count>
> > +	constexpr Span<element_type, Count> last() const
> > +	{
> > +		static_assert(Count <= Extent, "Count larger than size");
> > +		return { data() + size() - Count, Count };
> > +	}
> > +
> > +	constexpr Span<element_type, dynamic_extent> last(std::size_t Count) const
> > +	{
> > +		return { data() + size() - Count, Count };
> > +	}
> > +
> > +	template<std::size_t Offset, std::size_t Count = dynamic_extent>
> > +	constexpr Span<element_type, Count != dynamic_extent ? Count : Extent - Offset> subspan() const
> > +	{
> > +		static_assert(Offset <= Extent, "Offset larger than size");
> > +		static_assert(Count == dynamic_extent || Count + Offset <= Extent,
> > +			      "Offset + Count larger than size");
> > +		return { data() + Offset, Count == dynamic_extent ? size() - Offset : Count };
> > +	}
> > +
> > +	constexpr Span<element_type, dynamic_extent>
> > +	subspan(std::size_t Offset, std::size_t Count = dynamic_extent) const
> > +	{
> > +		return { data() + Offset, Count == dynamic_extent ? size() - Offset : Count };
> > +	}
> > +
> > +private:
> > +	pointer data_;
> > +};
> > +
> > +template<typename T>
> > +class Span<T, dynamic_extent>
> > +{
> > +public:
> > +	using element_type = T;
> > +	using value_type = typename 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 = T *;
> > +	using const_iterator = const T *;
> > +	using reverse_iterator = std::reverse_iterator<iterator>;
> > +	using const_reverse_iterator = std::reverse_iterator<const_iterator>;
> > +
> > +	static constexpr std::size_t extent = dynamic_extent;
> > +
> > +	constexpr Span() noexcept
> > +		: data_(nullptr), size_(0)
> > +	{
> > +	}
> > +
> > +	constexpr Span(pointer ptr, size_type count)
> > +		: data_(ptr), size_(count)
> > +	{
> > +	}
> > +
> > +	constexpr Span(pointer first, pointer last)
> > +		: data_(first), size_(last - first)
> > +	{
> > +	}
> > +
> > +	template<std::size_t N>
> > +	constexpr Span(element_type (&arr)[N],
> > +		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
> > +							    element_type (*)[]>::value,
> > +					std::nullptr_t> = nullptr) noexcept
> > +		: data_(arr), size_(N)
> > +	{
> > +	}
> > +
> > +	template<std::size_t N>
> > +	constexpr Span(std::array<value_type, N> &arr,
> > +		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
> > +							    element_type (*)[]>::value,
> > +					std::nullptr_t> = nullptr) noexcept
> > +		: data_(utils::data(arr)), size_(N)
> > +	{
> > +	}
> > +
> > +	template<std::size_t N>
> > +	constexpr Span(const std::array<value_type, N> &arr) noexcept
> > +		: data_(utils::data(arr)), size_(N)
> > +	{
> > +	}
> > +
> > +	template<class Container>
> > +	constexpr Span(Container &cont,
> > +		       std::enable_if_t<!details::is_span<Container>::value &&
> > +					!details::is_array<Container>::value &&
> > +					!std::is_array<Container>::value &&
> > +					std::is_convertible<std::remove_pointer_t<decltype(utils::data(cont))> (*)[],
> > +							    element_type (*)[]>::value,
> > +					std::nullptr_t> = nullptr)
> > +		: data_(utils::data(cont)), size_(utils::size(cont))
> > +	{
> > +	}
> > +
> > +	template<class Container>
> > +	constexpr Span(const Container &cont,
> > +		       std::enable_if_t<!details::is_span<Container>::value &&
> > +					!details::is_array<Container>::value &&
> > +					!std::is_array<Container>::value &&
> > +					std::is_convertible<std::remove_pointer_t<decltype(utils::data(cont))> (*)[],
> > +							    element_type (*)[]>::value,
> > +					std::nullptr_t> = nullptr)
> > +		: data_(utils::data(cont)), size_(utils::size(cont))
> > +	{
> > +	}
> > +
> > +	template<class U, std::size_t N>
> > +	constexpr Span(const Span<U, N> &s,
> > +		       std::enable_if_t<std::is_convertible<U (*)[], element_type (*)[]>::value,
> > +					std::nullptr_t> = nullptr) noexcept
> > +		: data_(s.data()), size_(s.size())
> > +	{
> > +	}
> > +
> > +	constexpr Span(const Span &other) noexcept = default;
> > +
> > +	constexpr Span &operator=(const Span &other) noexcept
> > +	{
> > +		data_ = other.data_;
> > +		size_ = other.size_;
> > +		return *this;
> > +	}
> > +
> > +	constexpr iterator begin() const { return data(); }
> > +	constexpr const_iterator cbegin() const { return begin(); }
> > +	constexpr iterator end() const { return data() + size(); }
> > +	constexpr const_iterator cend() const { return end(); }
> > +	constexpr reverse_iterator rbegin() const { return reverse_iterator(data() + size() - 1); }
> > +	constexpr const_reverse_iterator crbegin() const { return rbegin(); }
> > +	constexpr reverse_iterator rend() const { return reverse_iterator(data() - 1); }
> > +	constexpr const_reverse_iterator crend() const { return rend(); }
> > +
> > +	constexpr reference front() const { return *data(); }
> > +	constexpr reference back() const { return *(data() + size() - 1); }
> > +	constexpr reference operator[](size_type idx) const { return data()[idx]; }
> > +	constexpr pointer data() const noexcept { return data_; }
> > +
> > +	constexpr size_type size() const noexcept { return size_; }
> > +	constexpr size_type size_bytes() const noexcept { return size() * sizeof(element_type); }
> > +	constexpr bool empty() const noexcept { return size() == 0; }
> > +
> > +	template<std::size_t Count>
> > +	constexpr Span<element_type, Count> first() const
> > +	{
> > +		return { data(), Count };
> > +	}
> > +
> > +	constexpr Span<element_type, dynamic_extent> first(std::size_t Count) const
> > +	{
> > +		return { data(), Count };
> > +	}
> > +
> > +	template<std::size_t Count>
> > +	constexpr Span<element_type, Count> last() const
> > +	{
> > +		return { data() + size() - Count, Count };
> > +	}
> > +
> > +	constexpr Span<element_type, dynamic_extent> last(std::size_t Count) const
> > +	{
> > +		return { data() + size() - Count, Count };
> > +	}
> > +
> > +	template<std::size_t Offset, std::size_t Count = dynamic_extent>
> > +	constexpr Span<element_type, Count> subspan() const
> > +	{
> > +		return { data() + Offset, Count == dynamic_extent ? size() - Offset : Count };
> > +	}
> > +
> > +	constexpr Span<element_type, dynamic_extent>
> > +	subspan(std::size_t Offset, std::size_t Count = dynamic_extent) const
> > +	{
> > +		return { data() + Offset, Count == dynamic_extent ? size() - Offset : Count };
> > +	}
> > +
> > +private:
> > +	pointer data_;
> > +	size_type size_;
> > +};
> > +
> > +}; /* namespace libcamera */
> > +
> > +#endif /* __LIBCAMERA_SPAN_H__ */
> > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> > index 88658ac563f7..2448f0e96468 100644
> > --- a/src/libcamera/meson.build
> > +++ b/src/libcamera/meson.build
> > @@ -36,6 +36,7 @@ libcamera_sources = files([
> >      'request.cpp',
> >      'semaphore.cpp',
> >      'signal.cpp',
> > +    'span.cpp',
> >      'stream.cpp',
> >      'thread.cpp',
> >      'timer.cpp',
> > diff --git a/src/libcamera/span.cpp b/src/libcamera/span.cpp
> > new file mode 100644
> > index 000000000000..753104765cea
> > --- /dev/null
> > +++ b/src/libcamera/span.cpp
> > @@ -0,0 +1,12 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2020, Google Inc.
> > + *
> > + * span.h - C++20 std::span<> implementation for C++11
> 
> .h? But if this file is dropped then it goes away.
> 
> > + */
> > +
> > +#include <libcamera/span.h>
> > +
> > +namespace libcamera {
> > +
> > +} /* namespace libcamera */
> > 
> 
> Do we actually need span.cpp at all?

Patch

diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
index beeaf6d3cf48..457e23a086a2 100644
--- a/Documentation/Doxyfile.in
+++ b/Documentation/Doxyfile.in
@@ -840,8 +840,10 @@  RECURSIVE              = YES
 # Note that relative paths are relative to the directory from which doxygen is
 # run.
 
-EXCLUDE                = @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \
+EXCLUDE                = @TOP_SRCDIR@/include/libcamera/span.h \
+			 @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \
 			 @TOP_SRCDIR@/src/libcamera/device_enumerator_udev.cpp \
+			 @TOP_SRCDIR@/src/libcamera/span.cpp \
 			 @TOP_SRCDIR@/src/libcamera/include/device_enumerator_sysfs.h \
 			 @TOP_SRCDIR@/src/libcamera/include/device_enumerator_udev.h \
 			 @TOP_SRCDIR@/src/libcamera/pipeline/ \
diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build
index f58c02d2cf35..f47c583cbbc0 100644
--- a/include/libcamera/meson.build
+++ b/include/libcamera/meson.build
@@ -14,6 +14,7 @@  libcamera_api = files([
     'pixelformats.h',
     'request.h',
     'signal.h',
+    'span.h',
     'stream.h',
     'timer.h',
 ])
diff --git a/include/libcamera/span.h b/include/libcamera/span.h
new file mode 100644
index 000000000000..513ddb432405
--- /dev/null
+++ b/include/libcamera/span.h
@@ -0,0 +1,417 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * span.h - C++20 std::span<> implementation for C++11
+ */
+
+#ifndef __LIBCAMERA_SPAN_H__
+#define __LIBCAMERA_SPAN_H__
+
+#include <array>
+#include <iterator>
+#include <limits>
+#include <stddef.h>
+#include <type_traits>
+
+namespace libcamera {
+
+static constexpr std::size_t dynamic_extent = std::numeric_limits<std::size_t>::max();
+
+template<typename T, std::size_t Extent = dynamic_extent>
+class Span;
+
+namespace details {
+
+template<typename U>
+struct is_array : public std::false_type {
+};
+
+template<typename U, std::size_t N>
+struct is_array<std::array<U, N>> : public std::true_type {
+};
+
+template<typename U>
+struct is_span : public std::false_type {
+};
+
+template<typename U, std::size_t Extent>
+struct is_span<Span<U, Extent>> : public std::true_type {
+};
+
+} /* namespace details */
+
+namespace utils {
+
+template<typename C>
+constexpr auto size(const C &c) -> decltype(c.size())
+{
+	return c.size();
+}
+
+template<typename C>
+constexpr auto data(const C &c) -> decltype(c.data())
+{
+	return c.data();
+}
+
+template<typename C>
+constexpr auto data(C &c) -> decltype(c.data())
+{
+	return c.data();
+}
+
+template<class T, std::size_t N>
+constexpr T *data(T (&array)[N]) noexcept
+{
+	return array;
+}
+
+template<std::size_t I, typename T>
+struct tuple_element;
+
+template<std::size_t I, typename T, std::size_t N>
+struct tuple_element<I, Span<T, N>> {
+	using type = T;
+};
+
+template<typename T>
+struct tuple_size;
+
+template<typename T, std::size_t N>
+struct tuple_size<Span<T, N>> : public std::integral_constant<std::size_t, N> {
+};
+
+template<typename T>
+struct tuple_size<Span<T, dynamic_extent>>;
+
+} /* namespace utils */
+
+template<typename T, std::size_t Extent>
+class Span
+{
+public:
+	using element_type = T;
+	using value_type = typename 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 = pointer;
+	using const_iterator = const_pointer;
+	using reverse_iterator = std::reverse_iterator<iterator>;
+	using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+	static constexpr std::size_t extent = Extent;
+
+	template<bool Dependent = false,
+		 typename = std::enable_if_t<Dependent || Extent == 0>>
+	constexpr Span() noexcept
+		: data_(nullptr)
+	{
+	}
+
+	constexpr Span(pointer ptr, size_type count)
+		: data_(ptr)
+	{
+	}
+
+	constexpr Span(pointer first, pointer last)
+		: data_(first)
+	{
+	}
+
+	template<std::size_t N>
+	constexpr Span(element_type (&arr)[N],
+		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
+							    element_type (*)[]>::value &&
+					N == Extent,
+					std::nullptr_t> = nullptr) noexcept
+		: data_(arr)
+	{
+	}
+
+	template<std::size_t N>
+	constexpr Span(std::array<value_type, N> &arr,
+		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
+							    element_type (*)[]>::value &&
+					N == Extent,
+					std::nullptr_t> = nullptr) noexcept
+		: data_(arr.data())
+	{
+	}
+
+	template<std::size_t N>
+	constexpr Span(const std::array<value_type, N> &arr,
+		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
+							    element_type (*)[]>::value &&
+					N == Extent,
+					std::nullptr_t> = nullptr) noexcept
+		: data_(arr.data())
+	{
+	}
+
+	template<class Container>
+	constexpr Span(Container &cont,
+		       std::enable_if_t<!details::is_span<Container>::value &&
+					!details::is_array<Container>::value &&
+					!std::is_array<Container>::value &&
+					std::is_convertible<std::remove_pointer_t<decltype(utils::data(cont))> (*)[],
+							    element_type (*)[]>::value,
+					std::nullptr_t> = nullptr)
+		: data_(utils::data(cont))
+	{
+	}
+
+	template<class Container>
+	constexpr Span(const Container &cont,
+		       std::enable_if_t<!details::is_span<Container>::value &&
+					!details::is_array<Container>::value &&
+					!std::is_array<Container>::value &&
+					std::is_convertible<std::remove_pointer_t<decltype(utils::data(cont))> (*)[],
+							    element_type (*)[]>::value,
+					std::nullptr_t> = nullptr)
+		: data_(utils::data(cont))
+	{
+		static_assert(utils::size(cont) == Extent, "Size mismatch");
+	}
+
+	template<class U, std::size_t N>
+	constexpr Span(const Span<U, N> &s,
+		       std::enable_if_t<std::is_convertible<U (*)[], element_type (*)[]>::value &&
+					N == Extent,
+					std::nullptr_t> = nullptr) noexcept
+		: data_(s.data())
+	{
+	}
+
+	constexpr Span(const Span &other) noexcept = default;
+
+	constexpr Span &operator=(const Span &other) noexcept
+	{
+		data_ = other.data_;
+		return *this;
+	}
+
+	constexpr iterator begin() const { return data(); }
+	constexpr const_iterator cbegin() const { return begin(); }
+	constexpr iterator end() const { return data() + size(); }
+	constexpr const_iterator cend() const { return end(); }
+	constexpr reverse_iterator rbegin() const { return reverse_iterator(data() + size() - 1); }
+	constexpr const_reverse_iterator crbegin() const { return rbegin(); }
+	constexpr reverse_iterator rend() const { return reverse_iterator(data() - 1); }
+	constexpr const_reverse_iterator crend() const { return rend(); }
+
+	constexpr reference front() const { return *data(); }
+	constexpr reference back() const { return *(data() + size() - 1); }
+	constexpr reference operator[](size_type idx) const { return data()[idx]; }
+	constexpr pointer data() const noexcept { return data_; }
+
+	constexpr size_type size() const noexcept { return Extent; }
+	constexpr size_type size_bytes() const noexcept { return size() * sizeof(element_type); }
+	constexpr bool empty() const noexcept { return size() == 0; }
+
+	template<std::size_t Count>
+	constexpr Span<element_type, Count> first() const
+	{
+		static_assert(Count <= Extent, "Count larger than size");
+		return { data(), Count };
+	}
+
+	constexpr Span<element_type, dynamic_extent> first(std::size_t Count) const
+	{
+		return { data(), Count };
+	}
+
+	template<std::size_t Count>
+	constexpr Span<element_type, Count> last() const
+	{
+		static_assert(Count <= Extent, "Count larger than size");
+		return { data() + size() - Count, Count };
+	}
+
+	constexpr Span<element_type, dynamic_extent> last(std::size_t Count) const
+	{
+		return { data() + size() - Count, Count };
+	}
+
+	template<std::size_t Offset, std::size_t Count = dynamic_extent>
+	constexpr Span<element_type, Count != dynamic_extent ? Count : Extent - Offset> subspan() const
+	{
+		static_assert(Offset <= Extent, "Offset larger than size");
+		static_assert(Count == dynamic_extent || Count + Offset <= Extent,
+			      "Offset + Count larger than size");
+		return { data() + Offset, Count == dynamic_extent ? size() - Offset : Count };
+	}
+
+	constexpr Span<element_type, dynamic_extent>
+	subspan(std::size_t Offset, std::size_t Count = dynamic_extent) const
+	{
+		return { data() + Offset, Count == dynamic_extent ? size() - Offset : Count };
+	}
+
+private:
+	pointer data_;
+};
+
+template<typename T>
+class Span<T, dynamic_extent>
+{
+public:
+	using element_type = T;
+	using value_type = typename 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 = T *;
+	using const_iterator = const T *;
+	using reverse_iterator = std::reverse_iterator<iterator>;
+	using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+	static constexpr std::size_t extent = dynamic_extent;
+
+	constexpr Span() noexcept
+		: data_(nullptr), size_(0)
+	{
+	}
+
+	constexpr Span(pointer ptr, size_type count)
+		: data_(ptr), size_(count)
+	{
+	}
+
+	constexpr Span(pointer first, pointer last)
+		: data_(first), size_(last - first)
+	{
+	}
+
+	template<std::size_t N>
+	constexpr Span(element_type (&arr)[N],
+		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
+							    element_type (*)[]>::value,
+					std::nullptr_t> = nullptr) noexcept
+		: data_(arr), size_(N)
+	{
+	}
+
+	template<std::size_t N>
+	constexpr Span(std::array<value_type, N> &arr,
+		       std::enable_if_t<std::is_convertible<std::remove_pointer_t<decltype(utils::data(arr))> (*)[],
+							    element_type (*)[]>::value,
+					std::nullptr_t> = nullptr) noexcept
+		: data_(utils::data(arr)), size_(N)
+	{
+	}
+
+	template<std::size_t N>
+	constexpr Span(const std::array<value_type, N> &arr) noexcept
+		: data_(utils::data(arr)), size_(N)
+	{
+	}
+
+	template<class Container>
+	constexpr Span(Container &cont,
+		       std::enable_if_t<!details::is_span<Container>::value &&
+					!details::is_array<Container>::value &&
+					!std::is_array<Container>::value &&
+					std::is_convertible<std::remove_pointer_t<decltype(utils::data(cont))> (*)[],
+							    element_type (*)[]>::value,
+					std::nullptr_t> = nullptr)
+		: data_(utils::data(cont)), size_(utils::size(cont))
+	{
+	}
+
+	template<class Container>
+	constexpr Span(const Container &cont,
+		       std::enable_if_t<!details::is_span<Container>::value &&
+					!details::is_array<Container>::value &&
+					!std::is_array<Container>::value &&
+					std::is_convertible<std::remove_pointer_t<decltype(utils::data(cont))> (*)[],
+							    element_type (*)[]>::value,
+					std::nullptr_t> = nullptr)
+		: data_(utils::data(cont)), size_(utils::size(cont))
+	{
+	}
+
+	template<class U, std::size_t N>
+	constexpr Span(const Span<U, N> &s,
+		       std::enable_if_t<std::is_convertible<U (*)[], element_type (*)[]>::value,
+					std::nullptr_t> = nullptr) noexcept
+		: data_(s.data()), size_(s.size())
+	{
+	}
+
+	constexpr Span(const Span &other) noexcept = default;
+
+	constexpr Span &operator=(const Span &other) noexcept
+	{
+		data_ = other.data_;
+		size_ = other.size_;
+		return *this;
+	}
+
+	constexpr iterator begin() const { return data(); }
+	constexpr const_iterator cbegin() const { return begin(); }
+	constexpr iterator end() const { return data() + size(); }
+	constexpr const_iterator cend() const { return end(); }
+	constexpr reverse_iterator rbegin() const { return reverse_iterator(data() + size() - 1); }
+	constexpr const_reverse_iterator crbegin() const { return rbegin(); }
+	constexpr reverse_iterator rend() const { return reverse_iterator(data() - 1); }
+	constexpr const_reverse_iterator crend() const { return rend(); }
+
+	constexpr reference front() const { return *data(); }
+	constexpr reference back() const { return *(data() + size() - 1); }
+	constexpr reference operator[](size_type idx) const { return data()[idx]; }
+	constexpr pointer data() const noexcept { return data_; }
+
+	constexpr size_type size() const noexcept { return size_; }
+	constexpr size_type size_bytes() const noexcept { return size() * sizeof(element_type); }
+	constexpr bool empty() const noexcept { return size() == 0; }
+
+	template<std::size_t Count>
+	constexpr Span<element_type, Count> first() const
+	{
+		return { data(), Count };
+	}
+
+	constexpr Span<element_type, dynamic_extent> first(std::size_t Count) const
+	{
+		return { data(), Count };
+	}
+
+	template<std::size_t Count>
+	constexpr Span<element_type, Count> last() const
+	{
+		return { data() + size() - Count, Count };
+	}
+
+	constexpr Span<element_type, dynamic_extent> last(std::size_t Count) const
+	{
+		return { data() + size() - Count, Count };
+	}
+
+	template<std::size_t Offset, std::size_t Count = dynamic_extent>
+	constexpr Span<element_type, Count> subspan() const
+	{
+		return { data() + Offset, Count == dynamic_extent ? size() - Offset : Count };
+	}
+
+	constexpr Span<element_type, dynamic_extent>
+	subspan(std::size_t Offset, std::size_t Count = dynamic_extent) const
+	{
+		return { data() + Offset, Count == dynamic_extent ? size() - Offset : Count };
+	}
+
+private:
+	pointer data_;
+	size_type size_;
+};
+
+}; /* namespace libcamera */
+
+#endif /* __LIBCAMERA_SPAN_H__ */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 88658ac563f7..2448f0e96468 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -36,6 +36,7 @@  libcamera_sources = files([
     'request.cpp',
     'semaphore.cpp',
     'signal.cpp',
+    'span.cpp',
     'stream.cpp',
     'thread.cpp',
     'timer.cpp',
diff --git a/src/libcamera/span.cpp b/src/libcamera/span.cpp
new file mode 100644
index 000000000000..753104765cea
--- /dev/null
+++ b/src/libcamera/span.cpp
@@ -0,0 +1,12 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * span.h - C++20 std::span<> implementation for C++11
+ */
+
+#include <libcamera/span.h>
+
+namespace libcamera {
+
+} /* namespace libcamera */