スパンの缶

次回の C++ 委員会で議論される論文が出てきました。このリストには、興味深く物議を醸す論文が多数含まれています。その中には、Herbceptions、多数の並行処理の提案、コルーチン TS の主要な設計変更を求める提案、 03 で Range TS を統合するための 200 ページの長い提案は、簡単に確認できます。 名前空間。

全体で約 140 の論文があり、どれもかなり興味深いものです。

ここ数日の Cpp Slack で最もホットなトピックが 15 であることは不思議ではありません .

待って、なに?

まず、Cpp Slack に参加していない場合は、Cpp Slack に参加することをお勧めします。これは素晴らしいコミュニティです。

次に、22 と聞いたことがあるかもしれません。 前回の会議で C++20 ドラフトに既にマージされていたのに、なぜそれについて話す必要があるのですか? また、ささやかなライブラリの追加によって、仮想インク フローが大幅に増加するのはなぜですか?

または、35 について聞いたことがないかもしれません 42 とは

卵を壊さないようにしようとすると、オブジェクトの連続したシーケンスに対する固定サイズの非所有ラッパーとして説明できると言えます。これにより、そのシーケンス内の個々のアイテムを反復および変更できます .


#include <vector>
#include <gsl/span>
#include <iostream>

int main() {
 std::vector<std::string> greeting = {"hello", "world"};
 gsl::span<std::string> span (greeting);
 for(auto && s : span) {
 s[0] = std::toupper(s[0]);
 }
 for (const auto& word: greeting) {
 std::cout << word << ' ';
 }
}

これは単純に 53 を出力します スパンのコンテンツの可変性を示します。

68 76 を含む任意の連続シーケンスを表すことができます 、 8594108 、またはサブセット、配列、またはベクトル。

もちろん、すべてのコンテナが 117 であるわけではありません 、たとえばどちらも 122 または 136 メモリ内で連続しています。

スパンはビューですか?

どう答えていいのかよくわかりません。プロポーザルは何を言っているのか気になります。それでは、スパンの提案を読みましょう:

スパン タイプは、ビューを提供する抽象化です オブジェクトの連続したシーケンスで、そのストレージは他のオブジェクトによって所有されています。

また、論文のタイトルが「span:bounds-safe views」になっていることにお気付きかもしれません。

(私のものを強調)

したがって、スパンは 140 です . 152 と呼ばれることを除いて .なぜ161だったのか聞いてみた 173 と呼ばれる 、そしてその理由は、委員会がそれを 184 と呼びたがっていたようです あの日。実際、最初にスパン論文が委員会に提出されたとき、それは 197 と呼ばれていました。 .C++ の配列は、メモリ内の連続した要素のシーケンスに類似しています。少なくとも、語彙 201 211 に存在 基本的に同じセマンティックで。

しかし、今度は文字列について話さなければなりません.

つまり、223 について話さなければならないということです。 .すべての意図と目的のために、235 244 です .しかし、人々は文字列を特別なメソッドの束を備えた特別なコンテナを必要とする特別な雪片のように感じています.So 259 262 を持つようになります 276 のためのメソッド 280 程度で、おそらく王女には十分ではありませんでした メソッドと辞書式コンパレータ。

つまり、それは公平です。多くのアプリケーションは、他の種類のデータよりもテキストを処理するため、特別なクラスを使用することは完全に理にかなっています.しかし、基本的に、ベクトルと文字列の唯一の違いは、プログラマーの意図によって伝えられることです.

294 に注意してください。 ( または 302 と他の 310 ) は、ASCII としてエンコードされていないテキストの処理にはまったく適していません。

あなたが英語を話さない地球上の 60 億人の 1 人である場合、326 と考えると、大変なことになるでしょう。 あなたのために何でもできます。せいぜい、それを何らかの方法で変更したり、変に見たりしなければ、どこかに表示するまでには問題ないように見えるかもしれないことを期待できます。これには、辞書式コンパレータと 338 メソッド。テキストで彼らを信用しないでください。

(しばらくお待ちください。C++ 委員会はこれらの問題に懸命に取り組んでいます!)

とりあえず、348 を見ていただくのが一番です バイトの不透明なコンテナーとして。ベクターのように。

悲しいかな 358 はお気に入りの子であり、他の誰よりも 3 年前に独自の非所有ラッパーを持つようになりました。 C++17 では 369 が導入されました .いいえ、実際には 376 です .

385 です 、それは 396 です .両方を混ぜ合わせたAPIです。しかし、それは 402 と呼ばれています .

414 と同じ特別な Snowflakes メソッドがすべて含まれています。

つまり、それらの方法はそれほど悪くはありません。 424 の作者 論文には、彼らについて非常に良いことが書かれていました:

find* メソッドは std::string の欠点と広く見なされているため、すべてのメソッドを削除しない理由を多くの人から尋ねられました。であるため、インターフェイスを std::string にできるだけ似たものにしておくと便利です。

これで、下位互換性の問題が解決しました。

それで、実際に 437 を定義できるかもしれません 448 に関して ?

template <typename CharT>
class basic_string_view : public std::span<CharT> {
 std::size_t length() const {
 return this->size();
 }
};

シンプルで簡単!

除く span とは異なり、459 であるため、これは完全に間違っています。 不変です

つまり、実際にはもっと似ています

template <typename CharT>
class basic_string_view : public std::span<const CharT> {/**/};

467 に戻る 論文で、著者は次のように説明しています:

定数ケースは可変ケースよりも十分に一般的であるため、デフォルトにする必要があります。可変ケースをデフォルトにすると、文字列リテラルを string_view パラメータに渡すことができなくなり、string_view の重要な使用例が無効になります。 LLVM は 2011 年 2 月に ArrayRef クラスを定義しましたが、2012 年 1 月まで、一致する MutableArrayRef の必要性を発見しませんでした。変更可能なバージョンの StringRef はまだ必要ありません。これの考えられる理由の 1 つは、文字列を変更する必要があるほとんどの使用では、その長さを変更できる必要があるためです。これは、string_view の変更可能なバージョンであっても不可能です。

特に今文字列について述べたことを考えると、それについて議論するのは難しい.だから 472 文字列の適切なデフォルトであるため、可変ではありません .

typedef basic_string_view string_view を使用して、同じテンプレートを使用して可変ケースをサポートしながら、不変ケースをデフォルトにすることができます.ユーザーを大幅に助けることなくテンプレートの定義を複雑にするため、私はこの方法を採用していません.

ただし、C++ はデフォルトでミュータブルであり、constness はオプトインです。したがって、型は 485 です。 493 からオプトアウトする方法はありません。 constness.Since 500 always がデフォルトで、言語は 517 を構築する方法を提供しません .

特別なスノーフレーク メソッドは別として、527 の違いはありません と 531 .だから、547 ビュー、558 はスパンであり、両方のクラスは基本的に同じものであり、同じメモリ レイアウトを持ちます。

実際、勇敢な魂がそれらをマージできることを示唆したほど似ています.それは2015年に 560 まだ 572 と呼ばれていました .

残念ながら、今では 586 という用語を考える人もいます どういうわけか不変を意味します。

しかし、そう考える唯一の理由は、599 に要約されます。 語彙タイプをすべて乗っ取ってしまいます。そして、utfX でエンコードされた文字列に対して最後に行うべきことは何だと思いますか?コード単位/バイト境界でビューにランダムにスライスします。

Ranges TS で 、ビューが不変であることを意味するものは何もありません:

View の概念は、一定時間のコピー、移動、代入演算子を持つ Range タイプの要件を指定します。つまり、これらの操作のコストは、View 内の要素の数に比例しません。

TL;DR:ビューとスパン:同じこと。 606 :特別な紛らわしい小さな雪片。

次へ…

スパンは範囲ですか?

C++20 では、範囲は 614 を持つ非常に単純なものです。 そして 629 、したがって 633 これが実際に当てはまることを確認できます:

#include <stl2/detail/range/concepts.hpp> #include <vector>#include <gsl/span>

static_assert(std::experimental::ranges::Range<std::vector<int>>);
static_assert(std::experimental::ranges::Range<gsl::span<int>>);

643 をさらに改良することができます 連続した範囲です :要素がメモリ内で連続している範囲。

現在、658 の概念もありませんが、 または 665 概念は C++20 の一部です。提案があります。奇妙なことに、678 の提案が見つかりませんでした。 1 .幸いなことに、686 で実装されています。 テストできるようにします。

#include <stl2/detail/range/concepts.hpp> #include <gsl/span>

static_assert(std::experimental::ranges::ext::ContiguousRange<gsl::span<int>>);


696 がわかっているとすれば、 は基本的に連続した範囲のラッパーですが、自分で実装できますか?

たとえば、イテレータのペアに砂糖のコーティングを追加できます。


#include <gsl/span>
#include <stl2/detail/range/concepts.hpp>
#include <vector>

template <
 std::experimental::ranges::/*Contiguous*/Iterator B,
 std::experimental::ranges::/*Contiguous*/Iterator E
>
class span : private std::pair<B, E> {
public:
 using std::pair<B, E>::pair;
 auto begin() { return this->first; }

 auto end() { return this->second; }

 auto size() const { return std::count(begin(), end()); }

 template <std::experimental::ranges::ext::ContiguousRange CR>
 span(CR &c)
 : std::pair<B, E>::pair(std::begin(c), std::end(c)) {}
};

template <std::experimental::ranges::ext::ContiguousRange CR>
explicit span(CR &)->span<decltype(std::begin(CR())), decltype(std::end(CR()))>;

template <std::experimental::ranges::/*Contiguous*/Iterator B,
 std::experimental::ranges::/*Contiguous*/Iterator E>
explicit span(B && e, E && b)->span<B, E>;

int main() {
 std::vector<int> v;
 span s(v);
 span s2(std::begin(v), std::end(v));
 for (auto &&e : s) {
 }
}

素敵でダンディじゃないですか?

ええと…もちろん、これは 701 ではありません まったく .それはおかしなことです

span<
 __gnu_cxx::__normal_iterator<int*, std::vector<int>>,
 __gnu_cxx::__normal_iterator<int*, std::vector<int>>
>

まったく無意味ですよね?

716 が考えられます。 と 729 これらすべては基本的に、範囲に対する「テンプレート消去」です。型が基礎となるコンテナに依存するイテレータのペアで範囲を表す代わりに、ビュー/スパンを使用します。

ただし、範囲はスパンではありません。 737 が与えられた場合 - または 741 のペア 750 を構築することはできません .

これはコンパイルされません:

#include <vector>#include <gsl/span>

int main() {
 constexpr int uniform_unitialization_workaround = -1;
 std::vector<int> a = {0, 1, uniform_unitialization_workaround};
 gsl::span<int> span (std::begin(a), std::end(a));
}

一方では 765 は範囲です。一方、範囲ではうまく機能しません。公平を期すために、779 Contiguous Ranges の偉大な論文が提出される前に草案で投票されました.しかし、その論文はその後更新されておらず、Contiguous Ranges は 2014 年以降、string view paper を含めて議論されてきました.

これが 2020 年までに修正されることを祈りましょう!

それまでの間、std アルゴリズムでの span の使用は、私が推測するように行う必要があります。

#include <vector>#include <gsl/span>int main() { std::vector<std::string> names { "Alexender", "Alphonse" "、"バットマン"、"エリック"、"ライナス"、"マリア"、"ゾーイ" };

 auto begin = std::begin(names);
 auto end = std::find_if(begin, std::end(names), [](const std::string &n) {
 return std::toupper(n[0]) > 'A';
 });
 gsl::span<std::string> span {
 &(*begin),
 std::distance(begin, end)
 };
}

これは素晴らしく、安全で、明白です。

連続したメモリについて話しているため、784 のペアの間には同等の関係があります。 ポインタと 792 ポインター + サイズ。

それを踏まえて、スパンクラスを書き直すことができます

#include <gsl/span>#include <stl2/detail/range/concepts.hpp> #include <vector>

template <typename T>
class span : private std::pair<T*, T*> {
public:
 using std::pair<T*, T*>::pair;
 auto begin() { return this->first; }

 auto end() { return this->second; }

 auto size() const { return std::count(begin(), end()); }

 template <std::experimental::ranges::ext::ContiguousRange CR>
 span(CR &c)
 : std::pair<T*, T*>::pair(&(*std::begin(c)), &(*std::end(c))) {}

 template <std::experimental::ranges::/*Contiguous*/Iterator B,
 std::experimental::ranges::/*Contiguous*/Iterator E>
 span(B && b, E && e)
 : std::pair<T*, T*>::pair(&(*b), &(*e)) {}
};

template <std::experimental::ranges::ext::ContiguousRange CR>
explicit span(CR &)->span<typename CR::value_type>;

template <std::experimental::ranges::/*Contiguous*/Iterator B,
 std::experimental::ranges::/*Contiguous*/Iterator E>
explicit span(B && b, E && e)->span<typename B::value_type>;
int main() { std::vector<int> v;スパン s(v); span s2(std::begin(v), std::end(v)); for (auto &&e :s) { }}

これは、概念的には標準の 801 のように動作します。 それでも、理解しやすく、推論しやすいです。

待って、私たちは何について話しているのですか?忘れてた…

template <typename T>
struct {
 T* data;
 std::size_t size;
};

ああ、そうだ 814 !

私のポイントは 827 だと思います 830 の一般的な解決策です . 844 851 についての実装または推論 863 なし ただし、よりトリッキーです。878 スパンのさらなる改良であるため、委員会がより専門的な解決策から始めて、一般的なケースに進んでいることは明らかであり、その後に奇妙な矛盾が残っています.

これまでのところ、887 を確立しました は別の名前のビューであり、扱いにくい範囲です。しかし、実際の問題は何ですか?

スパンに非常に問題があります

私は 892 とまで言っています (そして 903 、同じこと) C++ が壊れます。

標準ライブラリは、型の分類、特に 919 の概念に基づいて構築されています。 type.Barry Revzin が行ったように、その半分を説明するふりをするつもりはありません。そのため、この問題を詳しく説明している素晴らしいブログ投稿を読んでください。

基本的に、標準のジェネリック アルゴリズムは、アルゴリズムが正しいことを保証するために、型に関するいくつかの仮定を行います。これらの型プロパティはコンパイル時に静的にチェックされますが、型定義がその動作と一致しない場合、アルゴリズムはコンパイルされますが、不正確な結果。

幸い、スパンは 923 の定義です タイプ。それを構築し、コピーして比較することができます。そのため、ほとんどの標準アルゴリズムにフィードできます。しかし、比較演算子は実際には 2 つの 934 を比較しません。 、データ 946 を比較します 指す . Barry が説明したように、これはコードの誤りにつながりやすいのです。

Tony Van Eerd は、基本的な真実を抽出するコツを持っています。 非常に正確でした (ただし、960 を処理できるほど正確ではありませんでした) )、その意図は 971 の処理を​​保証することでした オブジェクトは、プログラムの残りの部分に影響を与えるべきではありません。プロキシ オブジェクトであるため、981 その期待に逆らいます。

表の反対側では、STL のユーザーは 991 を合理的に期待できます。 1002 のドロップイン代替品となる .そしてほとんどの場合、それをベクトルと比較し、反復することができます...もちろん、それをコピーするか、その値を変更しようとするまでは、1017 のように動作しなくなります。 .

満たされていない期待

1028 1039 です タイプ。 1045 メモリのチャンクへのポインタです。 1058 値です。 1067 1071 です 、1081 ではありません .1092 ヒマのように見え、ヘビのように噛みますが、実際はカモハシカモノハシであり、分類のあらゆる試みを失敗させる巨大な雑種です。

1100 委員会の半分はアレキサンダー ステパノフの教えになんらかの慰めを見出そうと必死に奮闘する一方で、残りの半分はすべてを錆びたものに書き直す必要があるかもしれないと囁かれています。

叙情的な脚色を止めてくれませんか?

うーん、そうですね。申し訳ありません。

でも本当は 1115 汎用アルゴリズムで適切に動作することでライブラリの作成者を喜ばせ、使いやすい優れた API を提供することでライブラリ以外の作成者を喜ばせようとします。確かに崇高な目標です。

ただし、ケーキを持って食べることはできません。そして、span はコンテナ プロキシであることが悪く、行儀の良い標準 1128 であることが悪いです。 タイプ。その二重の性質により、その API は誤用されやすく、その控えめな外観により、致命的なトラップではなく、罪のないコンテナのようなもののように見えます。API が何らかの方法で 悪用されやすい .そして 1137 控えめな足を吹く核弾頭に他なりません.

つまり、設計目標の一部が正反対であるため、期待に応えられません。具体的には:

  • 基になるデータの内容を比較するポインターのようなオブジェクトです。
  • これはコンテナのようなオブジェクトであり、その割り当てによって基になるデータが実際に変更されることはありません。

固定スパン

そのようなモンスターは飼いならすことさえできますか?

私はそれができると信じていますし、実際にはそれほど多くは必要ありません.

実際、1143 に本質的な問題はありません。 、マスクを落として、その真の性質について率直に言う必要があるだけです.物事に適切な名前を付けることの重要性については、1150まで多くのことが言えます. が懸念されているため、いくつかの名前が間違っています。

開梱しましょう

span::operator==()

物事が「等しい」または比較可能である方法を説明することに専念している数学の全分野があります.キャリアが作成され、本が書かれ、ライブラリがいっぱいになり、理論化され、整理され、研究され、Haskellに移植されました.だからこそ、その無限の知恵の中で、 1160 物事の平等を説明するためにいくつかのトークンを捧げます:

==
eq
===
aqv
=:=
=~=
~~

一方、1174 群論全体を 2 文字に折りたたんでいます。もちろん、2 バイトのトークンに吹き込める意味は限られています。

委員会のメンバーの間で、1180 ID (2 つのスパンが同じ基本データを指しているかどうか) または要素を比較する必要があります。

両方の意味の支持者がいて、どちらも間違っている そうです。そうではありません。間違っていると思います . (その記事でたくさんの友達を作るつもりです...).

議論の両側が他方と同じくらい理にかなっている場合、それは答えがないからです。それは、個人の好みを裏付けるためのでっち上げの議論から始まりますが、通常はこれら 2 つの両極端の間のどこかにあります。

  • 型カテゴリと標準ライブラリの正確性を順守する必要があります。そうしないと、必然的に自滅してしまいます。
  • ユーザーの期待に応えなければなりません。

どちらも非常に適切で賢明な立場であり、これらの両方の観点を尊重する必要があります.

したがって、殺戮を避ける唯一の方法は、すべての比較演算子を完全に削除することです。 .それらを比較できない場合、それらを間違って比較することはできません.

残念ながら、型が比較できない場合、1191 ちょっと動作を停止 - タイプが 1203 でなくなる 具体的には、並べ替えと検索のアルゴリズムは機能しません。

解決策は、いくつかの 1213 に頼ることかもしれません 1229 を作るトリック 標準ライブラリのコンテキストでのみ比較可能です。これは実証できます:


#include <vector>
#include <algorithm>

namespace std {
 class span { };
}

namespace __gnu_cxx::__ops {
 bool operator<(const std::span &a, std::span &b);
}

void compile() {
 std::vector<std::span> s;
 std::sort(s.begin(), s.end());
}

//void do_no_compile() {
// std::span a, b;
// a < b;
//}

それは 1232 になります stl 内で真に規則的であり、人々が間違ったことを比較するのを防ぎます。要素単位の比較は 1247 を通じて行われます。 .

span::operator=()

スパンがポインタと見なされるかコンテナと見なされるかによって、スパン ポインタまたは基になるデータを設定していると見なすことができます。残念ながら、1257 の場合と同じ ADL トリックを使用することはできません。 、他に合理的な解決策が見当たりません。1262 を修正できる別の方法があります。 ただし:span がポインターのように動作することを明確にすることで…

スパンの名前変更

1274 以前は 1286 と呼ばれていました . 1292 は簡単にわかります ポインターとして (範囲 TS のコンテキストではありません)。1305 それがビューであり、したがって非所有であることをより明確にします。

1311 連続したメモリ セグメントへのポインタであることを伝えます。これが C メモリ モデルの配列であるためです。

ええ、それは 1326 を意味します 変更可能で、1339

意味がない。ただし、非常に紛らわしい 1344 を持つよりもはるかに理にかなっています 世界で最も優れた専門家でも、どのように判断すればよいかよくわからないタイプです。

そこで止まらない…

いくつかの論文が公開され、スパンに関するより多くの問題が軽減されました

  • [そのサイズは、何らかの理由で署名されています] (https://wg21.link/p1089)
  • [その API にはいくつかの矛盾があります] (https://wg21.link/p1024)

人を変える?

カモノハシがアヒルであることを人々に教えるべきだと考える人もいますが、それは確かに便利だからです。せいぜい数十年かかり、集合的な知識と知恵が変化し始める頃には、最前線の専門家は人々にまったく新しい一連の期待を持たせる必要があります.

確かに、教育、講演、本に取って代わるものがない場合もあります。ただし、教師は 1350 よりも大きな戦いに集中する必要があります .

ビューと範囲のシンプルなストーリー

1 つのグラフで哺乳類を、他のグラフで鳥類を分類した後、生物学者はムササビを見てかなり腹を立てていたと思います。

しかし、委員会は設計中の既存のタイプを分類しているだけではありません.そして、彼らがキャノピーを飛び越えるのを見るのは楽しいかもしれませんが、私たちは実際に不変ムササビを必要としているのだろうか.

  • 1362 は… 一対のイテレータで表される範囲です。どちらかを所有しています(1371 )、または非所有 (1384 )
  • 1391 は…範囲を超える非所有ビューです。
  • 14051412 たまたまポインターである反復子のペアによって表される範囲のビューの消去を提供します。
  • コンテナ独自のデータ

たぶん、それはあまり正確ではありません。しかし、すべてを統一する理論が必要です。

1424 の簡単な紹介を締めくくります。 、このキリンの写真を残します。

<オール>
  • 私はもともと 1435 と間違って言いました C++ 標準に含めることは提案されていません。これは間違っています ↩︎