[C++]WG21月次提案文書を眺める(2021年05月)

文書の一覧

全部で44本あり、SG22(C/C++相互互換性に関する研究グループ)のCの提案を除くと36本になります。

P0009R11 MDSPAN

P0009R12 MDSPAN

多次元配列に対するstd::spanである、mdspanの提案。

mdspanは、multi dimensional spanの略で、連続したメモリ領域を多次元配列として参照するものです。

int* data = /* ... */

// dataをint要素4つの連続したメモリ領域として参照
auto s = std::span<int, 4>(data);

// dataをint型2x2行列の連続したメモリ領域として参照
auto ms = std::mdspan<int, 2, 2>(data);

// 要素アクセス
ms(1, 1) = 1;
ms(0, 1) = 1;

std::spanと同様に、動的な要素数を指定することもできます。

int* data = /* ... */
int size = /* ... */

auto s = std::span<int, std::dynamic_extent>(data, size);

int rows = /* ... */
int cols = /* ... */
auto ms = std::mdspan<int, std::dynamic_extent, std::dynamic_extent>(data, rows, cols);

mdspanstd::spanよりも柔軟に設計されており、レイアウトマッピングや要素へのアクセス方法などをポリシークラスによって変更することができます。

namespace std {
  template <
    class T,
    class Extents,
    class LayoutPolicy = std::layout_right,
    class Accessor = std::accessor_basic
  >
  class basic_mdspan;

  template <class T, ptrdiff_t... Extents>
  using mdspan = basic_mdspan<T, std::extents<Extents...>>;
}

LayoutPolicyは多次元インデックス(整数値の列i0, i1, ..., in)をメモリ上の一点を指す単一のインデックス(整数値i)に変換するもので、AccessorLayoutPolicyによって得られたインデックスとメモリを指すポインタを要素1つの参照へ変換するものです。

template <
    class T,
    class Extents,
    class LayoutPolicy = std::layout_right,
    class Accessor = std::accessor_basic
  >
  class basic_mdspan {
  public:
    using extents_type = Extents;
    using layout_type = LayoutPolicy;
    using accessor_type = AccessorPolicy;
    using mapping_type = typename layout_type::template mapping_type<extents_type>;

  private:
    accessor_type acc_{};
    mapping_type map_{};
    pointer ptr_{};

  public:

    template<class... SizeTypes>
    constexpr reference operator()(SizeTypes... indices) const noexcept {
      // LayoutPolicyによってインデックス列を連続領域への単一インデックスに変換し
      // Accessorによって、インデックスとポインタから要素を引き当てる
      return acc_.access(ptr_, map_(indices...));
    }
  }

例えばdoubleの2次元配列ならば、LayoutPolicyはインデックスx, yと配列の幅wを用いて、i = y * w + xを返し、Accessorはそれと領域へのポインタptr用いてptr[i]を返すものになります。

また、submdspan()という関数によって、mdspanからsliceを取得することができます。

namespace std {

  // [mdspan.submdspan], submdspan creation
  template<class ElementType, class Extents, class LayoutPolicy,
           class AccessorPolicy, class... SliceSpecifiers>
      constexpr basic_mdspan<see below>
      submdspan(const basic_mdspan<ElementType, Extents, LayoutPolicy, AccessorPolicy>& src, 
                SliceSpecifiers... slices) noexcept;
}

提案文書より、使用例

// メモリ領域へのマッピングを作成(LayoutPolicyの作成)
using Extents3D = extents<3, dynamic_extent, 7>;
layout_right::template mapping<Extents3D> map_right(10);

// メモリ領域確保
int* ptr = new int[3 * 8 * 10];

// mdspanの構築(3x10x7の三次元行列として参照)
basic_mdspan<int,Extents3D,layout_right> a(ptr, map_right);

// mdspnによる領域の初期化
for(int i0 = 0; i0 < a.extent(0); i0++) // i0 = 0 -> 2
  for(int i1 = 0; i1 < a.extent(1); i1++) // i1 = 0 -> 9
    for(int i2 = 0; i2 < a.extent(2); i2++) // i2 = 0 -> 7
      a(i0, i1, i2) = 10000 * i0 + 100 * i1 + i2;

// subspanの取得(あるいは、sliceの取得)
// [1, [4...5], [1...5]]の範囲を参照するmdspanを得る
auto a_sub = submdspan(a, 1, pair<int, int>(4, 6), pair<int, int>(1, 6));

// subspanの内容を出力
for(int i0 = 0; i0 < a_sub.extent(0); i0++) {
  for(int i1 = 0; i1 < a_sub.extent(1); i1++) {
    cout << a_sub(i0, i1) << " ";
  }
  cout << endl;
}

/* Output
10401 10402 10403 10404 10405
10501 10502 10503 10504 10505
*/

この提案は線形代数ライブラリ整備の一環として、Liblary Fundamentals v3に向けて議論されています。現在はLWGにて検討中です。

P0447R14 Introduction of std::colony to the standard library

要素が削除されない限りそのメモリ位置が安定なコンテナであるstd::colonyの提案。

以前の記事を参照

このリビジョンでの変更は、get_iterator_from_pointer()の名前を変更するなど調整したことなどです。

P0493R2 Atomic maximum/minimum

std::atomicに対して、指定した値と現在の値の大小関係によって値を書き換えるmaximum/minimum操作であるfetch_max()/fetch_min()を追加する提案。

以前の記事を参照

このリビジョンでの変更は、各関数の処理前後に値が変わらないような呼び出しをされた場合に、出力操作が行われるかを未規定にしたことです。

P0798R6 Monadic operations for std::optional

std::optionalmonadic interfaceのサポートを追加する提案。

std::optionalvocabulary typeとして、C++プログラミングの色々なところで活用することができ、活用されています。しかし、optionalが無効値を取る可能性のある計算を連鎖させた場合、その有効性をチェックするif文が必要となるため、可読性を著しく低下させてしまいます。

// 画像に移っている猫を可愛くする関数
image get_cute_cat (const image& img) {
  return add_rainbow(
           make_smaller(
             make_eyes_sparkle(
               add_bow_tie(
                 crop_to_cat(img))));
}

そもそも画像に猫が写っていないとき、目を閉じているとき、ネクタイを付けるいい場所がない時、などそれぞれの処理は失敗する可能性があります。そこで、optionalが利用できるわけですが、現在のC++std::optionalだとifが連なる次のようなコードになります。

std::optional<image> get_cute_cat (const image& img) {
  auto cropped = crop_to_cat(img);
  if (!cropped) {
    return std::nullopt;
  }
  auto with_tie = add_bow_tie(*cropped);
  if (!with_tie) {
    return std::nullopt;
  }
  auto with_sparkles = make_eyes_sparkle(*with_tie);
  if (!with_sparkles) {
    return std::nullopt;
  }
  return add_rainbow(make_smaller(*with_sparkles));
}

このようなコードでは、ほぼ同じだけど細部が異なるボイラープレートなコードが増産され、可読性の低下を招き、記述ミスをしやすくなってしまっています。

この提案は、monadic interfacestd::optionalに追加することによって、このような処理のチェーンをより簡易に書けるようにするものです。先程までの例は次のように書き直すことができます

std::optional<image> get_cute_cat (const image& img) {
  return crop_to_cat(img)
         .and_then(add_bow_tie)
         .and_then(make_eyes_sparkle)
         .transform(make_smaller)
         .transform(add_rainbow);
}

先程まで現れていた有効性チェックのif文はand_thentransformの中に隠蔽され、それらはstd::optionalを返すことによってメソッドチェーンによる処理の連鎖が表現できるようになっています。

追加を提案しているのは次の三種類の処理で、すべてメンバ関数です

  • .transform()
  • .and_then()
  • .or_else()

.transform()std::optional<T>std::optional<U>へ変換するもので、有効値を保持している場合にのみ渡された関数を適用して有効値の変換を行います。

.and_then().transform()と同じく有効値を保持している場合にのみ渡された関数を実行してstd::optional<T>std::optional<U>へ変換するものですが、こちらは渡す関数がstd::optional<U>を直接返すことができます。それによって、有効値 → 無効値の変換が可能になります。

.or_else().and_then()と双対的な関係にあるもので、こちらはstd::optional<T>が無効値を保持してる場合にのみ渡された関数を実行するものです。無効値 → 有効値の変換が可能になります。

// 文字列を int に変換し、失敗した場合 std::nullopt を返すような関数
std::optional<int> StoI(string s);

int main() {
  std::optional<string> opts = "abc";
    
  // opts が値を持つ場合、std::optional<size_t>{ opts->size() } を返す。
  // 持たない場合、std::optional<size_t>{ std::nullopt } を返す。
  auto s = opts.transform([](auto&& s){ return s.size(); })

  // opts が値を持つ場合、StoI(*opts) を返す。
  // 持たない場合、std::optional<int>{ std::nullopt } を返す。
  // その後、結果が有効値を保持している場合にのみ出力する
  opts
    .and_then(StoI)
    .and_then([](int n) { std::cout << n << '\n'; return n; });

  // opts が値を持つ場合、自身を返す。
  // 持たない場合、与えられた関数を実行して
  // std::optional<string>{ std::nullopt } を返す。
  // その後、プログラムを終了させる
  opts
    .or_else([]{ cout << "failed.\n"; return std::nullopt; })
    .or_else([]{ std::terminate(); });
}

宣伝

この部分では「ゲーム開発者のための C++11〜C++20 技術書典 10 Ver.」という本から文章やサンプルコードを引用・改変しています。気になった方はぜひお買い求めください。

宣伝2

この記事(not 提案)を書いている人が、同じ目的でより広い対象により多様なmonadic interfaceを提供するC++20のライブラリを書いています。良かったら使ってみてください。

P1018R10 C++ Language Evolution status - pandemic edition - 2021/04

EWG(コア言語への新機能追加についての作業部会)が2021/04に議論した提案やIssueのリストや将来の計画、テレカンファレンスの状況などをまとめた文書。

これらの提案はC++23入りを目指してCWGに転送されました。

P1068R5 Vector API for random number generation

<random>にある既存の分布生成器にイテレータ範囲を乱数で初期化するAPIを追加する提案。

以前の記事を参照

このリビジョンでの変更は、

  • 提案するベクターAPIoperator()からgenerate()という名前の関数に変更
  • コンセプトを使用した制約を行うようにした
  • rangeオブジェクトを受けてその範囲を乱数で初期化するオーバーロードを追加した
  • 以前のuniform_vector_random_bit_generatorを削除し、uniform_bulk_random_bit_generator/uniform_range_random_bit_generatorの二つのコンセプトを追加した
  • パフォーマンスについて議論を追加した

などです。

P1122R4 Proposed Wording for Concurrent Data Structures: Read-Copy-Update (RCU)

標準ライブラリにRead-Copy-Update(RCU)を導入する提案。

以前の記事を参照

このリビジョンでの変更は、LWGのレビューを受けて文言を調整したことなどです。

この提案は2021年6月の全体会議で投票にかけられ、Concurrency TS v2に導入されることが決まりました。標準ライブラリにいつ入るのかは未定です。

P1328R1 Making std::type_info::operator== constexpr

std::type_info==constexprにする提案。

この提案に先んじて、C++20ではtypeidの定数式での実行が許可されていましたが、そのメンバ関数はどれもconstexprではなかったためにstd::type_infoオブジェクトを定数式で使用することはできませんでした。

この提案は、operator==constexprを付加しておくことでstd::type_infoオブジェクトを定数式で使用できるようにするものです。

template<typename T, typename U>
constexpr bool type_eq(const T* t, const U* u) {
  return typeid(t) == typeid(u);  // 現在はここでエラー
}

int main () {
  constexpr int n = 0;
  constexpr long l = 0;
  constexpr bool b = type_eq(&n, &l);
}

この提案は当初C++20入りを目指していたのですが、LWGの議論中に実際に実装可能かどうかについて疑問が呈され、その確認に時間がかかったためC++20に間に合いませんでした。

現在はその疑問は解消されているようで、次の全体会議にてC++23入りの投票にかけられることが決まっています。

P1701R2 Inline Namespaces: Fragility Bites

inline名前空間の名前探索に関するバグを修正する提案。

以前の記事を参照

このリビジョンでの変更は、EWGでの議論で浮かび上がった選択肢について表にまとめ、標準へ提案する文言を追加した事です。

P2013R4 Freestanding Language: Optional ::operator new

フリースタンディング処理系においては、オーバーロード可能なグローバル::operator newを必須ではなくオプションにしようという提案。

以前の記事を参照

このリビジョンでの変更は、フリースタンディング処理系におけるグローバルなnew/deleteの定義が何をするかは実装定義となるようにされたことなどです。

この提案は現在CWGのレビューを終え、ライブラリの部分についてLWGのレビュー待ちをしています。それが終わったら全体投票にかけられる予定です。

P2066R7 Suggested draft TS for C++ Extensions for Minimal Transactional Memory

現在のトランザクショナルメモリTS仕様の一部だけを、軽量トランザクショナルメモリとしてC++へ導入する提案。

以前の記事を参照

このリビジョンの変更点は、標準ライブラリの機能で同期の問題が発生しうるものをさらに除外した事です。例えば、<chrono>の時計型やロケール関連のものです。

この提案はLEWGからCWGへ転送されようとしているところで、とりあえずTransactional Memory TS v2を目指すようです。

P2093R6 Formatted output

std::formatによるフォーマットを使用しながら出力できる新I/Oライブラリstd::printの提案。

前回の記事を参照

このリビジョンでの変更は、配置されるヘッダが<io>から<print>に変更されたこと、ユニコード出力時に無効なエンコーディングを置換するU+FFFDの選択を明確にしたこと、ユニコード出力時にリテラルエンコーディング(実行時エンコーディング)を使用することを明確にしたこと、文字集合を指定するためのANSIエスケープコードはこの提案を実装するためのネイティブシステムAPIとはみなされない事を明確にした、ことなどです。

P2136R3 invoke_r

戻り値型を指定するstd::invokeであるinvoke_rの提案。

以前の記事を参照

このリビジョンでの変更は機能テストマクロを追加した事です。

この提案は2021年6月の全体会議で承認され、C++23入りが決定しました。

P2138R4 Rules of Design<=>Specification engagement

CWGとEWGの間で使用されているwording reviewに関するルールの修正と、それをLWGとLEWGの間でも使用するようにする提案。

以前の記事を参照

このリビジョンでの変更は、Tentatively Readyという言葉を明確にしたことなどです。

P2168R3 generator: A Synchronous Coroutine Generator Compatible With Ranges

Rangeライブラリと連携可能なT型の要素列を生成するコルーチンジェネレータstd::generator<T>の提案。

前回の記事を参照

このリビジョンでの変更はよくわかりません。

P2280R2 Using unknown references in constant expressions

定数式での参照のコピーを許可する提案。

以前の記事を参照

このリビジョンでの変更は、参照と同じことをポインタにも拡張したことです。

template <typename T, size_t N>
constexpr size_t array_size(T (&)[N]) {
  return N;
}

template <typename T, size_t N>
constexpr size_t array_size_p(T (*)[N]) {
  return N;
}

void check(int const (&param)[3]) {
  constexpr auto s1 = array_size(param);    // ok、R0
  constexpr auto s2 = array_size_p(param);  // ok、R2
}

P2291R1 Add Constexpr Modifiers to Functions to_chars and from_chars for Integral Types in Header

std::to_chars, std::from_charsを整数変換に関してconstexprにする提案。

以前の記事を参照

このリビジョンでの変更は、機能テストマクロ__cpp_lib_constexpr_charconvを追加した事です。

P2299R1 mdspan and CTAD

P2299R2 mdspan and CTAD

提案中のstd::mdspanのCTAD対応についての問題を報告する文書。

以前の記事を参照

このリビジョンでの変更は、推論補助を追加することでこの問題に対処することにしたことです。

以前の検討では、推論補助の追加によってこの問題に対処することはできなかったのですが、どうやらそれはエイリアステンプレートでの実引数推論を実装していたGCC/MSVCのバグであったようです。

namespace std {

  template<class ElementType, class Extents, class LayoutPolicy = layout_right,
           class AccessorPolicy = default_accessor<ElementType>>
    class basic_mdspan;

  // この推論補助を追加する
  template <class ElementType, class... IndexTypes>
  explicit basic_mdspan(ElementType*, IndexTypes...)
    -> basic_mdspan<ElementType, extents<[] (auto) constexpr
                                         { return dynamic_extent; }
                                         (identity<IndexTypes>{})...>>;

  template<class T, size_t... Extents>
    using mdspan = basic_mdspan<T, extents<Extents...>>;
}

int main() {
  // 何かメモリ領域
  double* data = ...;

  // 動的サイズのmdspan構築、この提案の後ではOK
  mdspan a2(data, 64, 64);
}

P2314R2 Character sets and encodings

規格文書中の ~ character setという言葉を明確に定義し直す提案。

以前の記事を参照

このリビジョンでの変更は、r-char-sequence(生文字列リテラルの文字列)にユニバーサル文字名が含まれていない事を明確にした事と、文字列リテラルオブジェクトが翻訳フェーズ5で初期化済みと取れる文章の削除、提案する文言の修正などです。

P2325R3 Views should not be required to be default constructible

Viewとみなされる型にデフォルト構築可能性を要求しない様にする提案。

以前の記事を参照

このリビジョンでの変更は、標準に提案する文言を編集した事です。

この提案は2021年6月の本会議で投票にかけられ、C++20に向けて採択されました。歴史修正です。

P2328R1 join_view should join all views of ranges

std::ranges::join_viewの制約を緩和して、prvalueviewではないrangeを平坦化できるようにする提案。

以前の記事を参照

このリビジョンでの変更は、LWGのレビューのフィードバックを受けて、constexprが欠けていたところに追加したことなどを修正したことです。

この提案は2021年6月の本会議で投票にかけられ、C++20に向けて採択されました。歴史修正その2です。

P2334R1 Add support for preprocessing directives elifdef and elifndef

#elifでマクロの定義の有無で条件分岐する糖衣構文となるプリプロセッシングディレクティブである#elifdef/#elifndefの提案。

このリビジョンでの変更はよくわかりません。

この提案は、CWGに転送するためにEWGでの投票待ちをしています。C++23入りを目指しているようです。

P2351R0 Mark all library static cast wrappers as [[nodiscard]]

static_castのラッパであるような標準ライブラリの関数に[[nodiscard]]を付ける提案。

std::moveを始めとする標準ライブラリの型キャストラッパな関数は、コンパイラに対して引数が消費される関数として動作しているため、その戻り値を無視しても警告されません。一方、同等のことをstatic_castを始めとる言語機能のキャストによって行うと、警告が発せられます。

void f(auto T val) {
  // -Wallだと警告が発せられる
  val;
  static_cast<T&&>(val);
  // -Wallでも警告されない
  std::move(val);
}

これらの型キャスラッパな関数はその結果を使用しないと意味がないため、結果が捨てられている場合はほぼバグです。これを検出するために、それらの関数に[[nodiscard]]を付加する事でコンパイル時に警告として発せられるようにしようとする提案です。

提案されている候補は以下のものです。

  • to_integer
  • forward
  • move
  • move_if_noexcept
  • as_const
  • to_underlying
  • identity
  • bit_cast

MSVCではすでにこれらの関数に[[nodiscard]]が付加されており、筆者の方の所属企業(avast)の社内のコードベースにおいていくつかのバグを発見するのに役立っており、他の社員の人からもこのアプローチは好評のようです。

P2367R0 Remove misuses of list-initialization from Clause 24

<ranges>の規定において、誤ったリスト初期化({})の使用を修正する提案。

リスト初期化では、その初期化式の評価順序が決まっており、縮小変換が禁止されています。

<ranges>Range AdopterやCPOなどは、expression-equivalentという言葉によってある式が別の式と同じ効果を持つように規定されています。その効果では、リスト初期化が使用されていたり、カンマによる式が使用されていたりしますが、それら評価順が規定されている式を使用しているために、実装不可能な規定となっている箇所があります。

例えばdrop_viewを見てみると

The name views​::​drop denotes a range adaptor object. Let E and F be expressions, let T be remove_cvref_t<decltype((E))>, and let D be range_difference_t<decltype((E))> (中略) the expression views​::​drop(E, F) is expression-equivalent to:
- If T is a specialization of ranges​::​empty_view, then ((void) F, decay-copy(E)).
- (中略)
- Otherwise, ranges​::​drop_view{E, F}.

この効果の1つ目は((void) F, decay-copy(E))で、2つ目はranges​::​drop_view{E, F}ですが、前者はF -> Eの評価順が規定されている一方で、後者はE -> Fの順の評価順が規定されています。これは矛盾しており、実際にはviews​::​drop(E, F)のように呼び出されてからこれらの効果が選択されるため、効果の中で式の評価順をコントロールすることは実質的に不可能です。

また、リスト初期化においては定数式でその値が受け止められるときにのみ縮小変換を許可するという性質から、CPOは呼び出された定数値を伝播しチェックする必要があります。

template<typename T>
void f(T&& E) {
  // Tのdifference_typeはint32_tとすると

  // これはOKとする
  views::drop(E, int64_t()); 

  int64_t l = 0;
  // こっちはエラーとする
  views::drop(E, l);
}

それによって、このようなハンドリングを要求している事になりますが、どうやらこれは意図されたものではないようです。

この提案では、これらの問題を解消するために{}を用いているところを()に置き換えています。ただしその範囲は限定的であり、明らかにこれらの問題を来している箇所だけに留められています。

さらに、この提案を書いてる最中に発見された、views::singleに関する次の2つの問題についても同時に解決しています。

  • 暗黙の推論補助はCV修飾されたrvalueremove_cvしたり、配列型や関数型をdecayする事ができない。これはRange-v3の仕様と矛盾している
  • views::single(a_single_view)はラップではなくコピーされる

この提案はIssue解決であることもあり、LWGでのレビューが素早く終了していました。そして、2021年6月の全体会議でC++20に向けて採択されました。

P2368R0 2020 Winter Library Evolution Polls

2021年春にLEWGにおける最終投票にかけられる(た)提案のリスト。

これらはLEWGでの議論が完了したためLWGへ送付する確認を取るための投票にかけられます。ここで異論がなければ、LWGにおいて最後のチェックが行われ、ドラフトに反映されます。

このうちP2325R2、P2328R0、P2210R2の3つはC++20へ逆適用しようとしています(その後この3つは2021年6月の全体会議でC++20に向けて承認されました)。

P2372R0 Fixing locale handling in chrono formatters

P2372R1 Fixing locale handling in chrono formatters

<chrono>のフォーマッタがロケール依存でありそれを制御できない問題を修正する提案。

C++20にて<chrono>にカレンダーとタイムゾーンの拡張が入り、<format>の追加とともにそれらに対してのフォーマッタが提供されました。これはそれぞれ別々の提案によって行われていたため、ローケルに関して設計上の問題が見落とされたままになってしまたようです。

// ロシア語のロケールをグローバルロケールに設定
std::locale::global(std::locale("ru_RU"));

std::string s1 = std::format("{}", 4.2);         // s1 == "4.2" (ローカライズされない)
std::string s2 = std::format("{:L}", 4.2);       // s2 == "4,2" (ローカライズされる)

using sec = std::chrono::duration<double>;
std::string s3 = std::format("{:%S}", sec(4.2)); // s3 == "04,200" (ローカライズされる)

このように、std::formatの設計と一貫しなくなってしまっています。この場合にロケール非依存にするためにはフォーマットを手動で行う必要があります。

また、%S %Mなどのchronoのフォーマット指定子はOを付けて%OS %OMとする事でロケール依存の表現を出力するように説明されているため、あたかもロケール非依存であるかのように思わせてくれますが、実際にはこの二つは同じ効果でどちらもロケール依存の表現を出力します。

この提案は、これらの問題を解決するために、chronoのフォーマッタをデフォルトでロケール非依存にし、他の全ての標準フォーマッタがそうであるようにL指定子でロケール依存フォーマットを明示的にオプトインするように変更するものです。

Before After
auto s1 = std::format("{:%S}", sec(4.2));
// s1 == "04,200"

auto s2 = std::format("{:L%S}", sec(4.2));
// throws format_error
auto s1 = std::format("{:%S}", sec(4.2));
// s1 == "04.200"

auto s2 = std::format("{:L%S}", sec(4.2));
// s2 == "04,200"

この提案はSG16, LEWGの議論と投票においてC++23に導入された場合にC++20へのDRとして適用される事に合意が取れています。まだLEWGでの議論の最中ですが、筆者の方がやる気なのでそうなりそうです。

P2374R0 views::cartesian_product

P2374R1 views::cartesian_product

任意個数のシーケンスの直積を取って、その元のシーケンスを生成するcartesian_product_viewの提案。

これが何をするものなのかは、サンプルコードを見ると一目瞭然でしょう。

Before After
std::vector<int> a,b,c;

for (auto&& ea : a) {
  for (auto&& eb : b) {
    for (auto&& ec : c) {
      use(ea,eb,ec);
    }
  }
}
std::vector<int> a,b,c;

for (auto&& [ea,eb,ec] : std::views::cartesian_product(a,b,c)) {
  use(ea,eb,ec);
}

このような異なるシーケンスを総当たりする多重ループを一つのループに纏めることができます。

また、次のようにすると単なる多重ループをひとまとめにできます(使いやすいかはともかく・・・)

constexpr int N = ...;
constexpr int M = ...;
constexpr int L = ...;

for (auto [i, j, k] : std::views::cartesian_product(std::views::iota(0, N), std::views::iota(0, M), std::views::iota(0, L))) {
  // ...
}

これは入力となるシーケンス1つを1つの集合とみなし、それらの間での直積集合を作ってその元(順序対)をイテレートすることに対応しており、直積のことをデカルト積(cartesian product)と呼ぶためこの名前になっています。

cartesian_product_viewの入力となるrangeforward_rangeでなくてはならず、referencepair/tupleのいずれかになり、その要素型はベースのrangeイテレータreferenceとなります。すなわち、ベースのrangeイテレータが参照を返す場合はその要素はコピーされません。

また、cartesian_product_viewはパイプライン演算子による入力をサポートしていません。なぜなら、a | views::cartesian_product(b, c)views::cartesian_product(b, c)の使用を区別できないためです。

P2375R0 Generalisation of nth_element to a range of nths

std::nth_elementを一般化したnth_elementsの提案。

std::nth_elementは入力の範囲について[first, ..., nth, ..., last)の関係にある3つのイテレータを受け取って、この範囲を[first, nth)[nth](nth, last)の3つの範囲を連結したものになるように並べ替えます。

この時、[first, nth)の最大要素をmax(nth, last)の最小要素をminとするとmax < *nth <= min(デフォルトの比較の場合)とはなりますが、範囲全体は必ずしもソートされません。また、[first, nth)(nth, last)内の順序は未規定です。

そして、nthの位置にある要素は、[first, last)をソートした場合にnthの位置に来る要素に一致します。

nth_elementsはそれを一般化し、n番目を指すイテレータnthを取る代わりに[first, ..., nth_first, ..., nth_last, ..., last)の関係にある4つのイテレータを受け取って、この範囲を[first, nth_first)[nth_first, nth_last)(nth_last, last)の3つの範囲を連結したものになるように並べ替えます。この時、(first, nth_first)の最大要素をmax[nth_last, last)の最小要素をminとするとmax < *nth_first < *nth_last <= min(デフォルトの比較の場合)となります。

そして、[nth_first, nth_last)の範囲はソートされており、、[first, last)全体をソートした場合に[nth_first, nth_last)の範囲に来る要素に一致します。それ以外のことはnth_elementと同様になります。

cpprefjpのサンプルコードを少し改変した例

int main() {
  std::vector<int> v1 = {5, 10, 4, 7, 1, 9, 8, 6, 2};

  // 4番目に小さい値より小さい値を前に集める
  std::nth_element(v1.begin(), v1.begin() + 3, v1.end());

  for (auto x : v1) {
    std::cout << x << ", ";
  };
  // 2, 1, 4, 5, 7, 6, 8, 9, 10, 
  
  std::cout << '\n';
  
  std::vector<int> v2 = {5, 10, 4, 7, 1, 9, 8, 6, 2};

  // 3~6番目に小さい値より小さい値を前に集める
  std::ranges::nth_elements(v2, std::ranges::subrange{v2.begin() + 2, v2.begin() + 5});

  for (auto x : v2) {
    std::cout << x << ", ";
  };
  // 2, 1, 4, 5, 6, 7, 8, 9, 10, 
}

[first, last)の長さをN[nth_first, nth_last)の範囲にあるユニークな要素の数をmとすると、この操作はO(N log m)の計算量となるようです。

要するに入力範囲を部分的にソートするものですが、std::partial_sortと異なるのは開始位置が制限されていないことで、範囲の任意の部分範囲についてソートすることができます。

このアルゴリズムはNumPyにおいてnumpy.partitionとして実装されているなど、使用と分析は広く成熟しているため、C++でも利用可能とするために提案されています。

P2376R0 Comments on Simple Statistical Functions (p1708r4): Contracts, Exceptions and Special cases

P1708R4で提案されている統計関数について、例外を投げないようにする提案。

P1708R4では、必要な条件が満たされない場合にstats_errorという例外を投げるようになっています。この提案は、既存の関数(std::log, std::sqrtなど)の振る舞いや他の言語のライブラリなどを参考にして、例外を投げないようにしようとするものです。

P2377R0 [[nodiscard]] in the Standard Library: Clause 23 Iterators library

<iterator>にあるものの一部に[[nodiscard]]を付加する提案。

対象は多岐にわたるため転記しませんが、特筆するところとしては、標準ライブラリにある全てのCPOに対して戻り値型がvoidではない場合にその関数呼び出し演算子[[nodiscard]]が付加することを提案しています。

P2380R0 reference_wrapper Associations

Networking TSで用意されている、associated_allocatorassociated_executorに対して、reference_wrapper<T>の特殊化を追加する提案。

この2つはまとめてassociatorと呼ばれ、名前付き要件ProtoAllocatorExecutorを満たすようなアロケータとエグゼキュータを、非同期操作に指定された完了ハンドラから取得します。

Networking TSの非同期モデルでは、associatorによって非同期処理で使用するアロケータとエグゼキュータを取得します。

// 何かの非同期処理
template<typename Handler>
void async_f(handler h) {
  // ハンドラに関連づけられたアロケータの取得
  auto alloc = std::net::associated_allocator<Handler>::get(h);

  // ハンドラに関連づけられたエグゼキュータの取得
  auto ex = std::net::associated_executor<Handler>::get(h);
}

reference_wrapper<T>はクラスオブジェクトの参照を転送するために使用でき、TCallable(関数呼び出し可能)性を受け継ぎ、関数呼び出しを内部参照にバイパスします。別の言い方をすると、Callableオブジェクトに参照のセマンティクスを与える事ができます。

<algorithm>アルゴリズム関数などもそうですが、標準ライブラリのものはCallableオブジェクトを取る時に値として受け取ります。Networking TSでも同様で、例えばコピーが重いハンドラを渡そうとするときにreference_wrapper<T>を使用したくなるのですが、reference_wrapper<T>にはassociatorのサポートが無いためそのままだとTに関連づけられたアロケータ/エグゼキュータを使ってもらう事ができません。

このような場合にreference_wrapper<T>が使えなければ良いのですが、associatorは特殊化が無い場合にデフォルトのアロケータ/エグゼキュータを使うので一見すると問題なく使えているように見えてしまいます。その際、Tに対するassociatorがユーザによって特殊化されている場合、気づかない間に間違ったコードを書いてしまう事になりかねません。

// オリジナルのハンドラ
struct my_handler {
  // ...
};

// associatorの特殊化 (1)
std::net::associated_allocator<my_handler> {
  // ...
};
std::net::associated_executor<my_handler> {
  // ...
};

int main() {
  my_handler h{};

  // カスタマイズされたassociator (1)を使用する
  async_f(h); // OK

  // デフォルトのassociatorを使用する
  async_f(std::ref(h)); // OK
}

reference_wrapperCallableオブジェクトを参照として転送する際の標準的なソリューションであり、このような使用は自然な用法であるため、associatorのサポートをデフォルトで用意しようという提案です。

P2381R0 Pattern Matching with Exception Handling

パターンマッチングにおいて、例外のキャッチのためのパターンを追加する提案。

現在のパターンマッチングP1371R3では、いくつかのエラーハンドリング方法が用意されていますが、例外に対するパターンは提供されていません。

// expectef<T, E>のようにエラーと正常値をまとめて返す
variant<rete, error_code> e() noexcept;
variant<reto, error_object> o() noexcept;

inspect(e()) {
    <rete> //...
    <error_code> //...
}
inspect(o()) {
    <reto> //...
    <error_object> //...
}

// 例外を投げうる
reta a();// throws b, c, d

// 例外をキャッチするにはinspect式をtry-catchで囲う
try {
    inspect(a()) {
        <reta> //...
    }
} catch(b) {
} catch(c) {
} catch(d) {
}

パターンマッチングは例外ベースのエラー処理をサポートしておらず、戻り値ベースの例外処理を優遇しています。標準ライブラリのほとんどのものは例外ベースのエラー処理機構を採用しており、このことは標準の仕様と一貫していません。

この提案は、パターンマッチングにおいて例外パターンを許可する事で、例外ベースのエラー処理を簡潔に書く事ができるようにするものです。この提案の後では、先ほどのコードは次のように書く事ができるようになります。

// 例外を投げうる
reta a();// throws b, c, d

inspect(a()) {
    <reta> //...
    <b> //...
    <c> //...
    <d> //...
}

エラー処理はプログラミング全般にとって基本的であるためこのような小さな改善が大きな利益をもたらす可能性があります。また、C++におけるエラー処理方法と消費方法の多様性は多くの問題を引き起こしていますが、多くの提案や議論では消費の方法よりもパフォーマンスやエラーの生成について焦点を当てています。この提案は消費に焦点を当て、多様なエラー処理機構に対するその消費処理をなるべく統一的に扱えるようにするものです。

P2382R0 Presentation Slides for P2123R0

P2123R0の解説するためのスライド。

おそらくLEWGのメンバに向けて書かれたものです。

P2123R0は、ABI安定性の向上のために型システムを拡張しよう!という提案で、interface(tag){}のようなブロックでABIバージョンごとに処理を分け、それを使用する時にinterface(tag)を付加することでしようするバージョンを決定できるようにしようとするものです。

おわり