---
title: Tuple protocol for fixed-size span
document: P3786R2
date: 2026-04-10
audience: LEWG
reply-to:
  - "Michael Florian Hava  < <mfh.cpp@gmail.com>"
  - "1"
paper-type: proposal
---

Document number: P3786R2 Date: 2026-04-10 Project: Programming Language C++ Audience: LEWG Reply-to: Michael Florian Hava [<mfh.cpp@gmail.com>](mailto:mfh.cpp@gmail.com) 1

# Tuple protocol for fixed-size `span`

**Before** **Proposed** `span``<``int``, 3> s{…};` `auto` `& x{s[0]};` `auto` `& y{s[1]};` `auto` `& z{s[2]};` `span``<``int``, 3> s{…};` `auto` `& [x, y, z]{s};` `vector``<``span``<``int``, 3>> ss{…};` `auto` `firsts{ss | views::``transform``(``auto` `s``) {` `return` `s``[0];` `})` `| ranges::``to``<``vector``>()};`

:::wording-add

<ins>//NOTE: views::transform returns a copy 😬</ins> vector<span<int, 3>> ss{…}; auto firsts{ss | views::elements<0> | ranges::to<vector>()};

:::

:::wording-add

<ins>//NOTE: views::elements returns by reference 😊</ins>

:::

:::wording-add

❌ span is not compatible with pattern matching <ins>//interaction with pattern matching proposal P2688R5</ins> span<double, 2> p{…}; p match { [0, 0] => std::println("at origin"); [let x, 0] => std::println("on x-axis at {}", x); [0, let y] => std::println("on y-axis at {}", y); let [x, y] => std::println("at {}, {}", x, y); };

:::

[RISC Software GmbH, Softwarepark 32a, 4232 Hagenberg, Austria, michael.hava@risc-software.at](mailto:michael.hava@risc-software.at) 1 structured binding are: `bitset`, `integer_sequence` and `span`.

We can come up with rationales for why the first two in this list do not support it: the former would have to provide proxy-references, something currently not supported by structured binding, the latter is a meta-programming tool primarily used for deduction of its values. For `span` we lack such a clear rationale.

[In fact P1024 already proposed this feature - together with several other useful additions and got](http://wg21.link/P1024) [accepted during the C++20 cycle. After its approval LWG3212 was filled, as the approved design](http://wg21.link/lwg3212) would have resulted in `tuple_element_t<const span<T, 3>>` yielding `const T`[. Per P2116 the](http://wg21.link/P2116) feature was dropped from C++20.

**Design Space** Given the established design of the *tuple-protocol* there is little to discuss, apart from revisiting the issue that previously lead to the removal of this feature: Our design is based on the fact that conceptually `span<T, 1>` is equivalent to `tuple<T &>`, therefore the *tuple-protocol* should be equivalent for these types as well. Which leads us to the following semantics:

* `tuple_size_v<top-level-cv span<cv T, N>>` == `N`

* `tuple_element_t<I, top-level-cv span<cv T, N>>` == `cv T &`

* `decltype(get(span<cv T, N>))` == `cv T &`

All of which is only valid if `N != dynamic_extent`.

Note that `top-level-cv` (which may either be empty or `const`) is always ignored, staying consistent with the existing language rules for applying `cv`-qualifiers to references. In addition to the above, we adjust the exposition-only `tuple-like` concept to include fixed-size `span`s, enabling support for adaptors like `views::elements`.

Extending the *tuple-protocol* has one unfortunate side effect: It renders the following previously valid code ambiguous.

The same is already true for other `tuple-like` [types (see: https://godbolt.org/z/r65rnPY81).](https://godbolt.org/z/r65rnPY81)

[This type of breakage was already explicitly pointed out in P2165 which introduced the](http://wg21.link/P2165) `tuplelike` concept. Therefore we don’t consider this issue novel or in any way blocking to this paper.

`void` `f``(``span``<``int``>) { … }` `void` `f``(``tuple``<``int``,` `int``>) { … }` `span``<``int``, 2> s{…}:` `f``(s);` `//`❗ `ambiguous as either user-defined conversion is valid` `void` `f``(``pair``<``int``,` `int``>) { … }` `void` `f``(``tuple``<``int``,` `int``>) { … }` `pair``<``const` `int``,` `int``> s{…};` `f``(s);` `//`❌ `ambiguous as either user-defined conversion is valid` `array``<``int``, 2> a;` `f``(s);` `//`❌ `ambiguous as either user-defined conversion is valid` `void` `g``(``complex``<``double``>) { … }` `void` `g``(``pair``<``float``,` `float``>) { … }` `complex``<``float``> c;` `g``(c);` `//`❌ `ambiguous as either user-defined conversion is valid`

## Impact on the Standard

## Implementation Experience

## Proposed Wording

### [version.syn]

### [tuple.like]

### [views.contiguous]

:::wording

#define __cpp_lib_tuple_like <del>202311L</del><ins>YYYYMML //also in <utility>, <tuple>, <map>, <unordered_map>, <span></ins>

:::

**[DRAFTING NOTE: Adjust the placeholder value as needed to denote the proposal’s date of adoption.]**

**??.??.? Concept** `tuple-like` **[tuple.like]** `template<class T>` `concept` `tuple-like` `=` `see below``; //``exposition only`

:::wording

1 A type T models and satisfies the exposition-only concept tuple-like if remove_cvref_t<T> is a specialization of <del>array, complex</del>, <del>pair, tuple, or ranges::subrange</del>.<ins>:</ins>

:::

:::wording-add

<ins>(1.1)</ins> <ins>— array, complex, pair, tuple, ranges::subrange, or</ins>

:::

:::wording-add

<ins>(1.2)</ins> <ins>— span and remove_cvref_t<T>::extent is not equal to dynamic_extent.</ins>

:::

**??.?.?.? Header** `<span>` **synopsis** **[span.syn]**

// *mostly freestanding* `namespace std {` `…` // `[views.span]`, class template `span` `…` `template<class ElementType, size_t Extent>` `constexpr bool ranges::enable_borrowed_range<span<ElementType, Extent>> = true;`

:::wording-add

<ins>// [span.tuple], tuple interface</ins> <ins>template<class T> struct tuple_size;</ins> <ins>template<size_t I, class T> struct tuple_element;</ins> <ins>template<class ElementType, size_t Extents></ins> <ins>struct tuple_size<span<ElementType, Extents>>;</ins> <ins>template<size_t I, class ElementType, size_t Extents></ins> <ins>struct tuple_element<I, span<ElementType, Extents>>;</ins> <ins>template<size_t I, class ElementType, size_t Extents></ins> <ins>constexpr ElementType& get(span<ElementType, Extents>) noexcept;</ins>

:::

// `[span.objectrep]`, views of object representation `…` `}`

| ??.?.?.? Class template `span` | [views.span] |
| --- | --- |
| …**??.?.?.?.? Iterator support** | **[span.iterators]** |

… `constexpr reverse_iterator rend() const noexcept;`

6 *Effects:* Equivalent to: `return reverse_iterator(begin());`

:::wording-add

<ins>??.?.?.? Tuple interface</ins> <ins>[span.tuple]</ins> <ins>template<class ElementType, size_t Extents></ins> <ins>struct tuple_size<span<ElementType, Extents>> : integral_constant<size_t, Extents> {};</ins>

:::

### [tuple.helper]

## Acknowledgements

:::wording-add

<ins>1</ins> <ins>Constraints: Extents != dynamic_extents is true.</ins>

:::

:::wording-add

<ins>template<size_t I, class ElementType, size_t Extents></ins> <ins>struct tuple_element<I, span<ElementType, Extents>> {</ins> <ins>using type = ElementType&;</ins> <ins>};</ins>

:::

:::wording-add

<ins>2</ins> <ins>Mandates:</ins>

:::

:::wording-add

<ins>(2.1)</ins> <ins>— Extents != dynamic_extents is true, and</ins>

:::

:::wording-add

<ins>(2.2)</ins> <ins>— I < Extents is true.</ins>

:::

:::wording-add

<ins>template<size_t I, class ElementType, size_t Extents></ins> <ins>constexpr ElementType& get(span<ElementType, Extents> s) noexcept;</ins>

:::

:::wording-add

<ins>3</ins> <ins>Mandates:</ins>

:::

:::wording-add

<ins>(3.1)</ins> <ins>— Extents != dynamic_extents is true, and</ins>

:::

:::wording-add

<ins>(3.2)</ins> <ins>— I < Extents is true.</ins>

:::

:::wording-add

<ins>4</ins> <ins>Effects: Equivalent to: return s[I];</ins>

:::

**??.?.?.? Views of object representation** **[span.objectrep]**

**??.?.? Tuple helper classes** **[tuple.helper]**

… `template<class T> struct tuple_size<const T>;`

…

:::wording-add

6 In addition to being available via inclusion of the <tuple> header, the two templates are available when any of the headers <array> ([array.syn]), <complex> ([complex.syn]), <ranges> ([ranges.syn]), <ins><span> ([span.syn]),</ins> or <utility> ([utility.syn]) are included.

:::

```cpp
template<size_t I, class T> struct tuple_element<I, const T>;
```

…

:::wording-add

8 In addition to being available via inclusion of the <tuple> header, the two templates are available when any of the headers <array> ([array.syn]), <complex> ([complex.syn]), <ranges> ([ranges.syn]), <ins><span> ([span.syn]),</ins> or <utility> ([utility.syn]) are included.

:::
