# テンプレート勉強会Part2 [テンプレート勉強会Part1](https://github.com/elkurin/elkurin-daily-notes/blob/main/docs/day69.md)が開催されてから半年くらい経ったが第2回を開催する。 今回読むのは[base::span](https://source.chromium.org/chromium/chromium/src/+/main:base/containers/span.h). ## その前に:spanとは spanとは標準ライブラリにもC++20から導入されている[std::span](https://en.cppreference.com/w/cpp/container/span)と同様の型のChromium自前実装版。 vectorやarrayなどのコンテナの部分シーケンスを参照するクラスで、所有権は保持せず変更もできない。 [std::string_view](https://en.cppreference.com/w/cpp/string/basic_string_view)の拡張版で、メモリ連続性持つあらゆるコンテナに対して使える。 サイズが動的に決定できるものと静的なものと2つの宣言方法がある。 動的なものは`std::span<int>`のように要素の型だけで書き、静的なものは`std::span<int, 3>`のように要素数をテンプレート引数に追加する。 spanの中の要素は好きに参照することができ、連続するいくつかの要素を参照するAPIもある。 ```cpp= // 最初からsize個をとる。 span.first<size>(); span.first(size); // 最後からsize個をとる。 span.last<size>(); span.last(size); // `offset`から`size`個の部分をとる。 span.subspan(offset, size); ``` ## base::spanについて 前述の通りstd::spanはあるが、まだChromiumの中では許可されていない。 その代わりに自前実装の[base::span](https://source.chromium.org/chromium/chromium/src/+/main:base/containers/span.h)がある。 以下のノートはbase::spanについて。 spanのテンプレートは2種類ある。 ```cpp= template <typename T, size_t N, typename InternalPtrType> class GSL_POINTER span { ... } template <typename T, typename InternalPtrType> class GSL_POINTER span<T, dynamic_extent, InternalPtrType> { ... } ``` これはそれぞれ静的なspan、動的なspanに対応する。 型`T`は実際に格納する要素の型。例えば`std::vector<int>`型への参照を持ちたい場合は`T = int`となる。 上の方では`size_t N`としてコンスタントなサイズをとっている。 一方で2つ目の方では`N`に対応する引数がない。内部では`N = dynamic_extent`を代入した`template <typename T, size_t N, typename InternalPtrType>`を定義する形になっている。Nがdynamic_extentになっている場合の特殊化。 なお[`dynamic_extent`](https://source.chromium.org/chromium/chromium/src/+/main:base/containers/span.h;l=28;drc=67d90538f11c6b232dbfd716075db52aeb34fd15)は ```cpp= constexpr size_t dynamic_extent = std::numeric_limits<size_t>::max(); ``` `size_t`の最大値を持っている定数。 これはtagで、実際に代入されている値は他とかぶらないくらいの意味しか持たない。 動的に変わるデータの場合`N`を`dynamic_extent`というタグに置き換えることで読みやすくしている。 これは標準ライブラリでも使われており、[`std::dynamic_extent`](https://en.cppreference.com/w/cpp/container/span/dynamic_extent)も同様に`std::numeric_limits<std::size_t>::max()`と定義されている。 これは現在std::spanのみに使われる特殊な定数となっている。 base::spanが持つ値はたかだか2つ。 1つは`InternalPtrType data_`。参照しているコンテナの先頭ポインタを覚えている。 もう1つは`size_t size_`。これは動的なspanのみが持つ。静的な方はテンプレート引数としてサイズが指定されており、型として持っているので値としては保持する必要がない。 base::spanの要素にアクセスするには配列と同じ`[]`の構文が使える。 ```cpp= constexpr T& operator[](size_t idx) const noexcept { CHECK_LT(idx, size()); return data()[idx]; } ``` ### `std::numeric_limits<std::size_t>::max()` ちなみに`size_t`はunsignedなので、max()は実質`-1`と同じ。 ```cpp= inline constexpr std::size_t dynamic_extent = -1; ``` ## テンプレートコンセプト spanの中にはたくさんの[コンセプト](https://cpprefjp.github.io/lang/cpp20/concepts.html)がある。 ```cpp= constexpr span() noexcept requires(N == 0) = default; ``` テンプレートのコンセプトは今まで[std::enable_if](https://en.cppreference.com/w/cpp/types/enable_if)やSFINAEを用いて頑張ってパターンマッチしていたきもきもコードを読みやすくするために導入された。 テンプレート引数の制約を`requires`の中に記述することができ、またこの場合コンパイルエラーも"テンプレートパラメータTがコンセプトXの要件を満たしません"のような人間様にも分かる感じになる。 上記のコードは要素数を表すテンプレート引数`N`が`0`の場合はこのオーバーロードを使うという意味。 こんな感じでいくつかのspanコンストラクタが定義されている。 他の例としてsubspanを見てみる。 ```cpp= template <size_t Offset, size_t Count = dynamic_extent> constexpr auto subspan() const noexcept requires(Offset <= N && (Count == dynamic_extent || Count <= N - Offset)) { constexpr size_t kExtent = Count != dynamic_extent ? Count : N - Offset; return span<T, kExtent>(data() + Offset, kExtent); } ``` subspanは特定の連続する領域を抽出するAPI。 静的に取る方ではoffsetとsizeがテンプレート引数として与えられる。 なので`Offset <= N`と`Count == dynamic_extent || Count <= N - Offset` というように、正しい領域を確保しようとしているかをrequires節の中で確認している。 また、参照先が動的なコンテナの場合はコンパイル時にそこまでチェックすることができないので、特に条件なくオーバーロードできるようにし、実行時に走るCHECK構文を使うことになる。 ```cpp= constexpr span<T, dynamic_extent> subspan( size_t offset, size_t count = dynamic_extent) const noexcept { CHECK_LE(offset, size()); CHECK(count == dynamic_extent || count <= size() - offset); return {data() + offset, count != dynamic_extent ? count : size() - offset}; } ``` なお、読み進めていく上の注意点:動的・静的の区分はいま2箇所にある。span自身の型と、参照先の型。参照先もbase::spanの場合は、そちらも`dynamic_extent`になりうる。 2つ目の関数は、spanの型定義に当たるテンプレート引数の方は`dynamic_extent`になっているので、つまり作っている方は動的なspan。一方抽出するサイズを指定している`count`はどちらでもよい。指定しない場合は`dynamic_extent`にフォールバックする・ ### 上級編:条件付きexplicit 以下の例を見てみる。 ```cpp= template <typename R, size_t X = internal::ExtentV<R>> requires(internal::CompatibleRange<T, R> && (X == N || X == dynamic_extent)) // NOLINTNEXTLINE(google-explicit-constructor) explicit(X == dynamic_extent) constexpr span(R&& range) noexcept : span(std::ranges::data(range), std::ranges::size(range)) {} ``` 最初の2行は先程と同様に読み解ける。 spanが持つ要素の型`T`が参照しようとしているもののコンテナの中身にcompatibleで、かつ参照するサイズが`N`と一致しているまたは動的ならばrequiresを満たす。 [CompatibleRange](https://source.chromium.org/chromium/chromium/src/+/main:base/containers/span.h;l=47;drc=67d90538f11c6b232dbfd716075db52aeb34fd15)は以下。 ```cpp= template <typename T, typename R> concept CompatibleRange = std::ranges::contiguous_range<R> && std::ranges::sized_range<R> && LegalDataConversion<std::ranges::range_reference_t<R>, T> && (std::ranges::borrowed_range<R> || std::is_const_v<T>); ``` `concept`というキーワードで型に対する制約の集合を定義することができる。 問題は後半。 ```cpp= explicit(X == dynamic_extent) ``` これは何? これは`X`が`dynamic_extent`と同じならexplicitになり、そうでなければimplicitになるということ。 requiresとは異なり、`X == dynamic_extent`を要求しているわけではなく、その条件を満たすときのみ`explicit`キーワードが有効になるという構文。 これはconceptの構文ではなく、[explicit](https://en.cppreference.com/w/cpp/language/explicit)側のシンタックス。 C++20から `explicit ( expression )` というシンタックスが導入されていることがcppreferenceを見ると分かる。 > The explicit specifier may be used with a constant expression. The function is explicit if and only if that constant expression evaluates to true. (since C++20) つまり、このテンプレートはまとめると、以下の2つの集合体である。 ```cpp= template <typename R, size_t X = internal::ExtentV<R>> requires(internal::CompatibleRange<T, R> && X == N) // NOLINTNEXTLINE(google-explicit-constructor) constexpr span(R&& range) noexcept : span(std::ranges::data(range), std::ranges::size(range)) {} template <typename R, size_t X = internal::ExtentV<R>> requires(internal::CompatibleRange<T, R> && X == dynamic_extent) // NOLINTNEXTLINE(google-explicit-constructor) explicit constexpr span(R&& range) noexcept : span(std::ranges::data(range), std::ranges::size(range)) {} ``` ...別に2個書いた方がよくね?