RangeOf:より良いスパン

スパンは嫌いです。

その記事が投稿されてから、委員会は 01 を削除することでスパンをかなり改善しました 15 にする 27 のそれと一致 たくさんの議論の後。 たくさん .

スパンとは:30 秒の復習

N ある場合 30 46 を構築できます。 それらの上。 Span は値の型で、移動したり、コピーしたりできます。しかし、50 以降 要素を所有していないため、基になるデータはそれよりも長く存続する必要があります。

いくつかの問題

  • Span は値型ですが、非所有であるため、ポインターとして見なされるべきであり、おそらく 67 と呼ばれることもあります
  • 非所有であるため、Range 用語でのビューです。その一貫性は浅い .つまり、 78 の基になる要素を変更できます
  • 連続したメモリへのポインタであるため、C 配列、83 のみにスパンを作成できます。 、 91 など。

スパンは何に適していますか?

  • データをコピーせずにサブ範囲を操作できます
  • 型を気にすることなく連続したコンテナを均一に使用でき、多くのテンプレートのインスタンス化を発生させずにそれを行います。

より良いソリューション

次の関数を検討してください

template <typename T>
void f(const std::span<const T> & r);

Ranges と簡潔な構文を C++20 にマージすると、代わりにそのようなものを書くことができます

void f(const std::ContiguousRange auto & r);

呼び出し元の観点から見ると、これらの関数はどちらも同じように動作し、実装も非常に似ています.範囲を取るものを除いて、所有権モデルははるかに理解しやすい

複数のタイプのコンテナーで呼び出された場合、100 ContiguousRange が範囲タイプごとにインスタンス化されるかどうかにかかわらず、バージョンはエレメント タイプごとに 1 回だけインスタンス化されます。 119 をフルに活用できるようにモデル化します 関数、汎用コード、およびコードのインライン化を行うコンパイラの機能。

とにかく、特定のタイプの範囲が必要であることをどのように指定できますか?スパンを使用すると、非常に簡単です:

void f(const std::span<const int> & r);

範囲を指定すると、次のようになります:

template <std::ContiguousRange R>
requires std::is_same_v<std::ranges::iter_value_t<std::ranges::iterator_t<R>>, int>
void f(const R & r);

以上で完了です。簡単ですよね?少し運が良ければ、C++20 でさらに単純化できる可能性があります:

template <std::ContiguousRange R>
requires std::is_same_v<std::ranges::range_value_t<R>, int>
void f(const R & r);

122 を使用すると簡単に使用できます :

int main() {
 auto v = std::vector<int>(42, 0);
 f(v);
 f(v | ranges::view::take(5));
 f(ranges::subrange(v.begin() + 1, v.begin() + 3));
}

シンプルですね。うーん…まだかなり冗長ですね。

書けるといいなと思います

void f(const std::ContiguousRangeOf<int> auto & r);

幸いなことに、概念はパラメータ化できるため、簡単に定義できます:

namespace std {
template <typename R, typename T>
concept ContiguousRangeOf = ContiguousRange<R> &&
 std::is_same_v<ranges::iter_value_t<ranges::iterator_t<R>>, T>;
}

(最初のテンプレート パラメーターは、概念が適用される型です)

所有権に関する限りそのスパンを理解しやすく、newtype を導入しないことに加えて、連続した範囲だけでなく、あらゆる種類の範囲に一般化できるため、あらゆる種類のコンテナやビューで使用できます。

namespace std {
template <typename R, typename T>
concept RangeOf = Range<R> &&
 std::is_same_v<ranges::iter_value_t<ranges::iterator_t<R>>, T>;

template <typename R, typename T>
concept ForwardRangeOf = ForwardRange<R> &&
 std::is_same_v<ranges::iter_value_t<ranges::iterator_t<R>>, T>;

template <typename R, typename T>
concept BidirectionalRangeOf = BidirectionalRange<R> &&
 std::is_same_v<ranges::iter_value_t<ranges::iterator_t<R>>, T>;

template <typename R, typename T>
concept RandomAccessRangeOf = RandomAccessRange<R> &&
 std::is_same_v<ranges::iter_value_t<ranges::iterator_t<R>>, T>;

template <typename R, typename T>
concept ContiguousRangeOf = ContiguousRange<R> &&
 std::is_same_v<ranges::iter_value_t<ranges::iterator_t<R>>, T>;
}

たとえば、次のように記述できます。

void f(const std::RangeOf<std::string> auto & r);

コンセプト テンプレート

残念ながら、概念をテンプレート パラメーターとして使用することはできません (まだ ?)、たとえば 132 を定義することはできません .この制限が C+23 までに解除されることを願っています。

結論

146 の間 特に組み込みプラットフォームでは、コンパイル時間がわずかに短縮されることを期待してテンプレートや概念を敬遠するため、誤用しやすく、C++ 型システムにうまく適合しない型を扱う必要があります。

代わりに、範囲と簡潔な構文を使用すると、同じアイデアをよりシンプルでわかりやすい方法で表現するための驚くべきツールが少なくなります。

165だと思いますか は、標準ライブラリに追加するのに十分役立つでしょうか?それまでの間、コンパイラ エクスプローラで操作できます。