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

文書の一覧

全部で106本あります。

もくじ

N4955 WG21 2023-06 Admin telecon minutes

2023年6月に行われた、WG21管理者ミーティングの議事録

N4957 WG21 February 2023 Minutes of Meeting

2023年6月に行われた、WG21全体会議の議事録

P0260R6 C++ Concurrent Queues

P0260R7 C++ Concurrent Queues

標準ライブラリに並行キューを追加するための設計を練る提案。

以前の記事を参照

R6での変更は

  • ターゲットとなるConcurrency TSのリビジョンなどについて追記
  • TSに入れると仮定すると浮かぶ疑問について追記
  • 非同期インターフェースを追加

などです。

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

  • LEWGのフィードバックを反映
    • system_errorからconqueue_errcを導出する
    • Rangeコンストラクタの追加
  • capacity()の追加
  • 既存の実装例としてTBBのconcurrent_bounded_queueを追加
  • pop()APIに関する議論を別の提案に分離

などです。

R6で追加された非同期インターフェースはsenderを返す次のようなものです

sender auto queue::async_push(T x);
sender auto queue::async_pop();

これらは要素をpush/popする操作を表すsenderを返し、そのsenderが実行されるまでは実際にpush/popは行われません。また、このsenderはキャンセルをサポートしています。

P0543R3 Saturation arithmetic

整数の飽和演算を行うライブラリ機能の提案。

以前の記事を参照

このリビジョンでの変更は、LWGのレビューを受けて、事前条件に違反した場合に定数式ではないという指定に関するコメントをdiv_satに追加したことです。

この提案はすでにLWGのレビューを終えており、C++26に向けて次の全体会議で投票にかけられる予定です。

P0843R7 inplace_vector

P0843R8 inplace_vector

静的な最大キャパシティを持ちヒープ領域を使用しないstd::vectorであるinplace_vectorの提案。

以前の記事を参照

R7での変更は

  • 名前をstatic_vectorからinplace_vectorへ変更
  • try_push_back()T*を返すように変更
  • push_back()が条件付きでstd::bad_allocをスローするように変更
  • value_typeがtrivially-copyableであればinplace_vectorも trivially-copyableとなることを明記
  • inplace_vectorをどのヘッダに配置すべきかについてのLEWGの投票をリクエス
  • push_back()が参照を返すように変更

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

  • <inplace_vector>に配置することにLEWGの合意が取れた事を追記
  • 機能テストマクロの追加
  • try_push_back()/unchecked_push_back()を提案する文言に追加
  • Rangeコンストラクタと代入演算子を追加
  • reserve()capacity()を超えた場合に例外をスローするように変更
  • shrink_to_fit()(なにもしない)を追加
  • insert_range()を追加
  • ムーブコンストラクタとデストラクタのトリビアル性について追記(Tトリビアルならトリビアルになる)
  • capacity()を推定できないため推論補助を削除
  • erase()/erase_if()を追加
  • 設計選択時のLEWGにおける投票結果を追記
  • operator==/operator<=>をHidden friendsに変更
  • <inplace_vector>のフリースタンディング指定を解除(それに関しては別の提案で議論する)

などです。

P0901R11 Size feedback in operator new

::operator newが実際に確保したメモリのサイズを知ることができるオーバーロードを追加する提案。

以前の記事を参照

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

  • new式のサポートを削除
  • return_size_tvoid*メンバをもつ非テンプレートクラスに変更
  • この機能がstd::allocatorの詳細やフリー関数として提供するのではなく、::operator newとして提供する必要がある理由について追記
  • 実装と導入の経験、および動的確保への影響にかんするレポートを追加

などです。

このリビジョンでの変更によって、new式から直接return_size_tを取るオーバーロードを呼び出せなくなりました。

#include <new>

int main() {
  auto [ptr, size] = new(std::return_size) int[5];                        // ng
  auto [ptr, size] = ::operator new(decltype(int[5]), std::return_size);  // ok
}

new式はメモリを確保した後その領域に指定された型のオブジェクトを構築し、対応するdelete式はオブジェクトを破棄した後メモリを開放しますが、それらの仕組みはreturn_size_tを取るオーバーロードによって得られる余剰サイズの領域にあるものについて感知しません。

メモリの解放は自動で行われますが、オブジェクトの構築と破棄の責任はプログラマにありそれは手動で行わなければならなくなります。

// 以前のリビジョンにおけるnew式の使用例

// メモリの確保
// T[5]の領域にはオブジェクトが構築される
auto [p, sz] = new (std::return_size) T[5];

// 余剰領域のオブジェクトを構築
for (int i = 5; i < sz / sizeof(T); i++) {
  new (p[i]) T;
}

// 確保した領域全体にあるオブジェクトを使用
for (int i = 0; i < sz / sizeof(T); i++) {
  p[i].DoStuff();
}

// 余剰領域のオブジェクトを破棄
for (int i = 5; i < sz / sizeof(T); i++) {
  p[i].~T();
}

// メモリの解放
// T[5]の領域にあるオブジェクトは破棄される
delete[] p;

これは冗長となり間違えやすいため、このリビジョンでnew式を使用してreturn_size_tを取る::operator newを呼び出せなくなりました。

::operator newを使用する場合はメモリの確保だけが行われ、オブジェクトの構築・破棄は全てプログラマの責任となるため、手動でそれを管理する必要があるにしても領域によって場合分けする必要はありません。

P1030R6 std::filesystem::path_view

パス文字列を所有せず参照するstd::filesystem::path_viewの提案。

以前の記事を参照

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

  • ロケールを取る全てのオーバーロードを削除
  • 一貫していないostreamのフォーマッタを修正
  • .render()を削除
  • render_zero_terminated()がフリー関数であるように修正
  • rendered_path()の生存期間のセマンティクスを明確化
  • 名前付き要件ではなくpath-view-like型によってpath_viewと同様の型を受け取るオーバーロードを指定
  • LEWGのメモを一部を除いて削除
  • 機能テストマクロの更新
  • render_null_terminated/render_unterminatedから誤った引数を削除
  • render_null_terminated/render_unterminatedの文言を追加
  • 必要に応じて‘implementation defined’を‘see later normative wording’に置き換え
  • 現在const path&を取っているオーバーロードを参考にnoexceptを付加
  • path(path_view)path_view::operator<<のセマンティクスを指定
  • path_view_fragmentにハッシュサポートを追加

などです。

P1324R1 RE: Yet another approach for constrained declarations

制約付きのautoによる関数宣言構文において、そのテンプレートパラメータ名を直接導入できる構文の提案。

この提案のモチベーションは以前に紹介したP2677R0と共通なので、以前の記事を参照

P2677ではauto:Tのような構文でテンプレートパラメータ名を導入しようとしていましたが、この提案はvoid sort(Sortable auto& c);void sort(Sortable S& c);のように書けるようにすることで、autoの代わりに直接テンプレートパラメータ名を導入しようとするものです。

namespace present {
  // 現在の制約付きauto関数テンプレート
  void f(Sortable auto&& x) {
    using S = decltype(x); // テンプレートパラメータ名Sを取り出す
    // use S
  }
}

namespace p2677 {
  // P2677提案の制約付き関数テンプレート
  void f(Sortable auto:S&& x) // テンプレートパラメータ名Sが導入される
  {
    // use S
  }
}

namespace p1324 {
  // この提案による制約付き関数テンプレート
  void f(Sortable S&& x) // テンプレートパラメータ名Sが導入される
  {
    // use S
  }
}

この提案ではさらに、この構文を戻り値型制約や変数宣言に対する制約にまで広げています

// 現在
void f(Sortable auto x);
Sortable auto f(); 
Sortable auto x = f(); 
template <Sortable auto N>void f();

// この提案
void f(Sortable S x);             // 関数引数のテンプレートパラメータ導入
Sortable S f();                   // 関数戻り値型のテンプレートパラメータ導入
Sortable S x = f();               // 変数宣言時の型名エイリアス導入
template <Sortable S N> void f(); // NTTP宣言時のテンプレートパラメータ導入

ただし、コンセプト パラメータ名 変数名、のような宣言であるため、変数名を省略するとコンパイルエラーになります。

// Numberはコンセプトとする

void f(Number N);     // ng、Numberが型名ではない
void f(Number auto);  // ok、テンプレートパラメータ名も変数名も導入されない
void f(Number N __);  // 別の提案(P1110)で提案されていたプレースホルダ

この制限によって、通常の関数とジェネリックな関数が混同されることが無くなります

void f(Foo V)     // 2id: 常に通常の関数宣言
void f(Foo F V)   // 3id: ジェネリック関数(この提案)
void f(Foo auto)  // 1id + auto: ジェネリック関数

この宣言によって導入されたテンプレートパラメータ名は以降の引数宣言のために使用できるほか、別のコンセプトのために使用することもできます

// Number, Concept, AnotherConceptは何かコンセプトとする

// 1つのテンプレートパラメータで2つの引数を宣言する
void f(Number N x, N y) { }

// 複数のコンセプトでそれぞれ変数を宣言し、テンプレートパラメータ名を別のコンセプトで使用する
Concept R f(Number N a, AnotherConcept<R> U b); 

P1383R2 More constexpr for <cmath> and <complex>

<cmath><complex>の数学関数をconstexprにする提案。

以前の記事を参照

このリビジョンでの変更は、ベースとなるワーキングドラフトを更新したことと、<complex>関連の見落としを追加したことなどです。

この提案は2023年6月の全体会議で承認され、C++26WD入りしています。

P1729R2 Text Parsing

std::formatの対となるテキストスキャン機能の提案。

C++20でstd::formatが追加されたことで、いくつか問題を抱えていた従来のテキスト書式付き出力機能であるstd::ostreamprintf等に代わるものをC++は手に入れました。しかし、テキスト入力面では改善はなく、同様に問題を抱えている従来のstd::istreamscanf等を利用するしかありません。std::formatの対となるものを欠いているということでもあり、このことは標準ライブラリの一貫性を損ねてもいます。

この提案は、std::formatの対となる書式付きテキスト入力機能std::scanを提供し、その欠けている部分を補おうとするものです。

基本的な使用法

if (auto result = std::scan<std::string, int>("answer = 42", "{} = {}")) {
  //                        ~~~~~~~~~~~~~~~~   ~~~~~~~~~~~    ~~~~~~~
  //                          output types        input        format
  //                                                           string

  const auto& [key, value] = result->values();
  //           ~~~~~~~~~~
  //           読み取られた値

  // result == true
  // result.begin() points to the end of the given input (the null terminator)
  // key == "answer"
  // value == 42
} else {
  // エラーが起きた場合
  // result.error()からエラー情報を取得できる
}

基本的には、std::scan<Types...>(input, format)のようにして、入力文字列inputに対するフォーマット文字列をformat、型指定をTypesに渡して使用します。フォーマット文字列の構文はほぼstd::formatのものと共通しており、入力文字列とフォーマット文字列をマッチして、フォーマット文字列中の置換フィールド{}のある位置に対応する入力文字列中の文字列をTypesの対応する位置にある型の値として読み取ります。

auto input = "25 54.32E-1 Thompson 56789 0123";

auto result = std::scan<int, float, string_view, int, float, int>(
  input, "{:d}{:f}{:9}{:2i}{:g}{:o}");

// resultはstd::expected、 operator->は失敗時に例外をスローする
auto [i, x, str, j, y, k] = result->values();

// i == 25
// x == 54.32e-1
// str == "Thompson"
// j == 56
// y == 789.0
// k == 0123

std::scan<Types...>(...)の結果はstd::expectedで返されており、成功時はstd::scan_resultという型の値に対して.value()関数を呼ぶことでスキャン結果をstd::tuple<Types...>オブジェクトとして得ることができます。

スキャン対象の入力は文字列に限らず、スキャン可能な範囲をとることができます。この要件はscannable_rangeコンセプトで表現されています。

// scannable_rangeの定義例
template <class Range, class CharT>
concept scannable_range =
  ranges::forward_range<Range> && same_as<ranges::range_value_t<Range>, CharT>;

forward_rangeでありその要素型がCharT(文字型)であるような範囲であれば読み取ることができ、文字列の範囲となっている多くのものを対象にしています。

// views::reverseからの読み取り例

std::string input{"123 456"};
if (auto result = std::scan<int>(std::views::reverse(input), "{}")) {
  // 読み取り対象の値が1つなら、result->value()はそのオブジェクトを直接返す
  // result->value() == 654
}

std::scanにおいてはscannable_rangeに与えられるCharTはフォーマット文字列の文字型から取得されるため、入力文字列とフォーマット文字列の文字型は一致している必要があります。

std::scan<int>("42", "{}");   // OK
std::scan<int>(L"42", L"{}"); // OK
std::scan<int>(L"42", "{}");  // Error: wchar_t[N] is not a scannable_range<char>

エラー時はscan_errorという専用のエラー型(列挙型ではない)の値が得られ、.code()からエラーコードが取得できるほか.msg()からエラーメッセージを取得することができます。

if (auto result = std::scan<std::string, int>("answer = 42", "{} = {}")) {
  ...
} else {
  // エラーが起きた場合
  auto err = result.error();

  auto ec = err.code(); // エラーコードの取得
  std::println("Error! : {:s}", err.msg()); // エラーメッセージの出力
}

フォーマット文字列はstd::formatにほとんど準じていますが、数値型に対する次の一部のオプションは無効化されます

これらのオプションは読み取り時には意味がなく、スキャン時は全ての可能性を考慮しどれかの形式をデフォルトにしたり無効化することを回避しています。

そのほかのオプションは利用可能となりますが、出力ではなく読み取りに使用するものであるためstd::formatからその意味が少し変わっています。

また、std::scanでのみ利用できるオプションも追加されています

  • i : プリフィックスから基数を検出する(デフォルトは10進数)
    • 整数型のみ
  • u : -を考慮しない10進整数値
    • 整数型のみ
  • c : 入力文字(列)をそのままコピーする
    • 文字列型/文字型/数値型で有効

std::formatterと同様にstd::scannerクラステンプレートを特殊化することで、ユーザー定義型をスキャン可能な型として登録することができます。

// tmのスキャンを有効化する際の宣言の例

template <>
struct std::scanner<tm, char> {
  constexpr auto parse(scan_parse_context& ctx)
    -> expected<scan_parse_context::iterator, scan_error>;

  template <class ScanContext>
  auto scan(tm& t, ScanContext& ctx) const
    -> expected<typename ScanContext::iterator, scan_error>;
};

ロケールはデフォルトでは考慮せず、Lオプションとともにstd::localeオブジェクトを渡すことでロケール依存の読み取りを行うことができます。その際、ロケールオブジェクトは引数の先頭で渡します。

この提案の内容は、scnlib(特にdevブランチ)および{fmt}ライブラリにおいて試験実装されているようです。

P1928R5 std::simd - Merge data-parallel types from the Parallelism TS 2

P1928R6 std::simd - Merge data-parallel types from the Parallelism TS 2

std::simd<T>をParallelism TS v2から標準ライブラリへ移す提案。

以前の記事を参照

R5での変更はsimd_selectに関する議論を追記した事です。

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

  • LEWGのレビューを受けての文言の修正
  • simd_catsimd_splitをリネーム
  • simd_cat(array)オーバーロードを削除
  • simd_splitをP1928R4で提案されているように修正
  • output_iteratorの代わりにindirectly_writableを使用
  • ほとんどのsize_t, intの使用をsimd-size-type(符号付整数型)に置き換え
  • simd_abiに関するものをすべて削除
  • 説明専用のABIタグを用いてABIタグのセクションを置き換え
  • ジェネレータコンストラクタは、インデックスにつき1度だけcallableを呼び出すことを保証
  • ブロードキャスティングコンストラクタの変換規則から、int/unsigned intを例外としているのを削除
  • loadstore_flagssimd_flagsにリネーム
  • simd_flags::operator|constevalに変更
  • 最小SIMD幅を64に増加
  • hmin/hmaxreduce_min/reduce_maxにリネーム
  • simd_mask<T, Abi>basic_simd_mask<Bytes, Abi>リファクタリングし、それに応じ使用箇所を置き換え
  • simd<T, Abi>basic_simd<Bytes, Abi>リファクタリングし、それに応じ使用箇所を置き換え
  • ベクトル化可能な型からlong doubleを削除
  • is_abi_tag, is_simd, is_simd_maskを削除
  • simd_sizeを説明専用にした

などです。

この提案はLEWGでのレビューをいったん終えて、LWGに転送するための投票待ちをしています。

P2169R4 A Nice Placeholder With No Name

宣言以降使用されず追加情報を提供するための名前をつける必要もない変数を表すために_を言語サポート付きで使用できる様にする提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言の改善のみです。

この提案は6月の全体会議で投票にかけられ、C++26WDに導入されています。

P2407R4 Freestanding Library: Partial Classes

一部の有用な標準ライブラリのクラス型をフリースタンディング処理系で使用可能とする提案。

以前の記事を参照

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

  • <algorithm>からfill_nswap_rangesをフリースタンディングとして追加
  • optionalにおいて、.value()の使用を**thisで置き換え
  • variantにおいて、getの使用をget_ifで置き換え
  • string_viewfindおよび検索系関数において、at()の使用をdata_で置き換え
  • 機能テストマクロへの// freestandingを追記
  • "synopsis"と"header synopsis"の使い分けをより慎重に判断
  • freestanding-deletedfreestanding-partialは要件が変更され、それぞれ異なるフリースタンディング指定となった
  • // hosted// freestanding-deletedオーバーロード解決への影響の違いについて追記
  • フリースタンディング機能をより明確化

などです。

この提案は既にLWGのレビューを終え、次の全体会議で投票にかけられる予定です。

P2487R1 Is attribute-like syntax adequate for contract annotations?

契約プログラミングの構文について、属性likeな構文は契約の指定に適しているかを考察する文書。

以前の記事を参照

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

  • P2552R2および最新のWDに基づいて、属性の無視可能性に関する議論を更新
  • 属性の式に関する説明を追記
  • P2552R2で提示された意味論の無視可能性基準に関する議論を追加

などです。

P2521R4 Contract support -- Record of SG21 consensus

C++に最小の契約プログラミングサポートを追加する提案。

以前の記事を参照

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

  • 複数の仮想関数を異なる契約アノテーションでオーバーライドする方法に発見されたバグに関して追記
  • ビルドモードは廃止され、各契約条件ごとに何が起こるかは実装定義となった
  • longjmpなど通常とは異なる方法で終了する契約条件式で何が起こるかを指定
  • 違反ハンドラのセマンティクスを修正
  • 例外を投げる契約条件式の意味論を指定
  • トリビアルな関数に対する契約アノテーションに関するイシューを追記

などです。

P2542R3 views::concat

同じ要素型を持つ異なる型の範囲を連結するRangeファクトリ、views::concatの提案。

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

  • iter_swapの再設計
  • random_access_range制約の緩和
  • 異なる型の変換を修正
  • 提案する文言の修正

などです。

P2546R5 Debugging Support

標準ライブラリにデバッグサポートの為のユーティリティを追加する提案。

以前の記事を参照

このリビジョンでの変更は、LWGのレビューに伴う文言の修正と投票結果を追記した事です。

この提案は既にLWGのレビューを終えて次の全体会議で投票にかけられる予定です。

P2548R6 copyable_function

std::move_only_functionに対して、コピー可能なCallableラッパであるcopyable_functionの提案。

以前の記事を参照

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

  • 型消去Callableラッパの2重ラッピングに関する文言を追加
  • 提案する文言の修正

などです。

この提案は2023年6月の全体会議でC++26に採択されています。

P2552R3 On the ignorability of standard attributes

属性を無視できるという概念について、定義し直す提案。

以前の記事を参照

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

  • CWG/EWGのフィードバックを反映して、__has_cpp_attributeの文言を修正
  • 理論的根拠としてcarries_dependencyに関するセクションを追加

などです。

この提案は2023年6月の全体会議でC++26に採択されています。

P2591R4 Concatenation of strings and string views

std::stringstd::string_view+で結合できるようにする提案。

以前の記事を参照

このリビジョンでの変更

  • LEWGのフィードバックを反映
  • Hidden friendsの導入する非対称性を回避するために、フリー関数テンプレートに戻した
  • 実装中のプロトタイプに要求されたテスト(filesystem::pathおよび曖昧さを導入するテスト)を追加
  • 最新のWDに追随

などです。

この提案はLEWGのレビューを終えてLWGに転送するための投票待ちをしています。

P2630R4 Submdspan

std::mdspanの部分スライスを取得する関数submdspan()の提案。

以前の記事を参照

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

  • LWGのフィードバックを反映
  • submdspan_mappingをHidden friendsに変更
  • submdspan_mappingがADL経由で呼び出されることを規定
  • 説明専用の実装詳細関数をHidden frinedsとした
  • is-strided-sliceを削除し、strided_sliceの特殊化であるS_kに置き換え
  • [[no_unique_address]]とデフォルト初期化をstrided_slicesubmdspan_mapping_resultメンバに追加
  • first_, last_のテンプレートパラメータにkを追加
  • LWGのフィードバックを反映

などです。

この提案は2023年6月の全体会議でC++26に採択されています。

P2637R3 Member visit

std::visitやなどをメンバ関数として追加する提案。

以前の記事を参照

このリビジョンでの変更は、文言の修正と機能テストマクロに関するメモを追記した事です。

この提案は2023年6月の全体会議でC++26に向けて採択されています。

P2641R4 Checking if a union alternative is active

定数式において、あるオブジェクトが生存期間内にあるかを調べるためのstd::is_within_lifetime()の提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言の改善のみです。

この提案は2023年6月の全体会議でC++26に向けて採択されています。

P2642R3 Padded mdspan layouts

std::mdspanpadding strideをサポートするためのレイアウト指定クラスを追加する提案。

以前の記事を参照

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

  • P2630R3(submdspan)の変更に追随
  • P2897(aligned_accessor)のリファレンスを追加
  • 既存のレイアウトマッピング型に合わせて、.extents()の戻り値型をextents_typeからconst extents_type&へ変更
  • LEWGの投票結果を受けた設計に関する議論を追加
  • rank 1のpadded layoutのrequired_span_size()の設計に関する議論を追加
  • 実装経験リンクの更新
  • layout_{left,right}_paddedマッピングクラスからの変換コンストラクタと、layout_{left,right}_paddedマッピングクラスとのoperator==
  • 共著者の追加

などです。

P2662R2 Pack Indexing

以前の記事を参照

パラメータパックにインデックスアクセスできるようにする提案。

以前の記事を参照

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

  • CWGレビューを受けての文言の改善
  • index-type-specifierから型を推論する方法に関するセクションを追加
  • 将来的な機能拡張に関するセクションを拡充

などです。

P2689R2 atomic_accessor

アトミック操作を適用した参照を返すmdspanのアクセッサである、atomic_accessorの提案。

以前の記事を参照

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

  • atomic-ref-boundedatomic-ref-boundにリネーム
  • atomic-ref-unboundedatomic-ref-unboundにリネーム
  • 提案する文言の修正
  • P2616が採択された場合、同様の変更をこちらにも適用する必要があることについて追記

などです。

P2697R1 Interfacing bitset with string_view

std::bitsetstd::string_viewを受け取るコンストラクタを追加する提案。

以前の記事を参照

このリビジョンでの変更は、LWGのレビューを受けての提案する文言の修正です。

この提案は2023年6月の全体会議でC++26に向けて採択されています。

P2714R1 Bind front and back to NTTP callables

std::bind_frontstd::bind_backにNTTPとして呼び出し可能なものを渡すオーバーロードを追加する提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言の改善です。

この提案は2023年6月の全体会議でC++26に向けて採択されています。

P2717R2 Tool Introspection

C++周辺ツールが、Ecosystem ISにどれほど準拠しているのかを互いに通信する手段を標準化する提案。

以前の記事を参照

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

  • Ecosystem ISの最新の変更に追随
  • 必要に応じてリテラルユニコードを使用
  • セマンティックバージョニングとJSON、及び先行0を許可しない仕様に従うようにバージョン番号を修正
  • フルレベルサポートにバージョン範囲の配列を追加し、サポートの不一致を報告できるようにした
  • JSONスキーマの修正
  • capability名で数字を使用できるように修正
  • コマンドオプションの処理と使用ファイルに関する説明を追加
  • 問い合わせのために使用するユーザーインタフェースに関する選択肢を追加し、現在推奨されている選択肢について説明

などです。

P2727R3 std::iterator_interface

イテレータを簡単に書くためのヘルパクラスの提案。

以前の記事を参照

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

  • CRTPの代わりにdeducing thisを使用
  • 以前にHidden friendsだったものをそうではなくした

などです。

P2728R4 Unicode in the Library, Part 1: UTF Transcoding

P2728R5 Unicode in the Library, Part 1: UTF Transcoding

標準ライブラリにユニコード文字列の相互変換サポートを追加する提案。

以前の記事を参照

R4での変更は

  • code_unitコンセプトの定義を変更し、as_charN_tアダプタを追加
  • replacement_characterを除くユーティリティとユニコード関連の定数を削除
  • utf_iteratorの制約をわずかに変更
  • null_sentinel_tユニコード固有のものに戻した

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

  • unpacking_owning_viewunpacking_viewに置き換え
    • アダプタでアンパッキングを行うのではなく、アンパッキングを行うために使用する
  • 提案するすべてのviewbegin/endconst/非constオーバーロードを追加
  • null_sentinel_tstd名前空間に移動し、その.base()を削除。単なるポインタ以上の用途に使用できるようにした

などです。

P2741R3 user-generated static_assert messages

static_assertの診断メッセージ(第二引数)に、コンパイル時に生成した文字列を指定できるようにする提案。

以前の記事を参照

このリビジョンでの変更は、設計セクションを拡張してインスタンス化と再帰がどのように機能するかについて追記したことです。

P2752R3 Static storage for braced initializers

std::initializer_listの暗黙の配列がスタックではなく静的ストレージに配置されるようにする提案。

以前の記事を参照

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

  • この提案は記憶域期間を変更しないことを確認するEWGの投票の結果を受けて、Annex Cにそれを追記
  • mutableメンバの説明を追記
  • 例を追加
  • 「§5.1 However...」セクションを追加

この提案は2023年6月の全体会議でC++26に向けて承認されています。

P2757R3 Type checking format args

std::format()のフォーマット文字列構文について、幅/精度の動的な指定時の型の検証をコンパイル時に行うようにする提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言の改善と機能テストマクロについて注記を追加したことです。

この提案は2023年6月の全体会議でC++26に向けて承認されています。

P2767R1 flat_map/flat_set omnibus

flat_map/flat_setの仕様にあるいくつかの問題点とその解決策について報告する提案。

以前の記事を参照

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

  • アロケータを受け取るコンストラクタの調整については編集上の修正として受け入れられたため提案から分離された
  • LWGのレビュー受けての提案する文言の更新と、関連する根拠の追記
  • LEWGのレビューが必要なため、Heterogeneous insertに関して§7の残りのものと分離(§13へ)
  • 注意を引くために非explicitコンテナコンストラクタに関してを§12から分離
    • これは2つのコンテナからの非explicitコンテナコンストラクタとして§14へ移動

などです。

P2776R0 2023-05 Library Evolution Poll Outcomes

2023年5月に行われたLEWGの全体投票の結果を報告する文書。

次の13本の提案が投票にかけられ、否決されたものはありませんでした。

全て、C++26に向けてLWGに転送するための投票です。これらのうちのいくつかは、2023年6月の全体会議でC++26 WDに導入されています。

P2779R1 Make basic_string_view's range construction conditionally explicit

std::string_viewrangeコンストラクタのexplicitを条件付きに緩和する提案。

以前の記事を参照

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

  • ベースとなるドラフトの更新
  • オプション1で提案する型特性の名前を変更し、2つのサブオプションに分割

などです。

分割された代替案は、変換しようとしているstring_view-likeな型の内部traitsの(std::char_traitsとの)互換性をチェックを行うかどうかによる、次の2つです

  1. 内部traitsの互換性を無視する
  2. 内部traitsの互換性を考慮するものとしないものの2つを用意する

(提案の現在の記述では、2つ目のサブオプションがどう使用されるのかわかりませんでした・・・)

SG16でのレビューではこの提案の問題としているところの解決に積極的な賛同は得られなかったようですが、解決するのであれば明示的なオプトイン(コンセプトによるチェックによる自動判定ではなく)によるものが望ましいというコンセンサスは得られています。この提案を進めるかどうかはLEWGに委ねられています。

P2781R3 std::constexpr_v

コンパイル時定数オブジェクトを生成するクラスの提案。

以前の記事を参照

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

  • 後置戻り値型の不必要な使用の修正
  • 代替トークン(and or not)を使用しないように修正
  • constexpr-param(説明専用)コンセプトの要件の修正
  • constexpr_vのテンプレートパラメータTがあることによって与えられるADLサポートの不完全な側面について追記

などです。

P2785R0 Relocating prvalues

P2785R1 Relocating prvalues

P2785R2 Relocating prvalues

P2785R3 Relocating prvalues

prvalueからのリロケーションを可能とするための機能を導入する提案。

リロケーションについてと提案のモチベーションは以前の同種提案と共通しているのでそちらを参照

この提案では特に、構築後に動かせなくなるオブジェクトの扱いを改善するためにもリロケーション操作が有効であると述べています。

例えば、gsl::not_nullというクラスはnullptr状態を取り得るクラスのムーブコンストラクタ等を無効化することでそのクラスがnullptrではないことを保証するクラスです。gsl::not_null<std::unique_ptr<T>>のように使用する場合、この型のオブジェクトはムーブもコピーもできなくなります。

このようなオブジェクトはnullptrではないことが保証されていることからコードの正確性やパフォーマンスを向上させるために有効ですが、現在のC++コードでは扱うのが現実的ではありません。コピーもムーブもできないため構築後にメモリ上を移動できなくなり、関数に渡したりコンテナに保存したりすることやクラスのメンバとなることを妨げます。

しかし、リロケーション操作が可能になればそのようなオブジェクトは、そのクラスの不変条件を保ったままリロケーションによってメモリ上を移動することができるようになり、その取り扱いのしやすさが改善します。

同様の問題は定数(const)オブジェクトにもあります。定数オブジェクトはその生存期間を通じて変化しないため、人間にとっても機械(コンパイラ)にとってもプログラムの状態に関する推論がしやすくなります。そのため、自動変数はさまざまなガイドラインで可能ならconstとすることが推奨されています。

しかし、constオブジェクトはムーブすることができず、そのため後でムーブすることを意図するオブジェクトはconstにすることができません。生存期間中はconstでありながらも、その終わりに所有するリソースを手放すことができれば、より安全で読みやすいコードが可能になります。

この提案のリロケーション操作はこれらの2点を改善することを主目的としており、その点が以前の提案と大きく異なる部分です。

この提案が導入しようとしているのは次のものです

  • 2つの特殊メンバ関数
    • リロケーションコンストラクタ : T(T)
    • リロケーション代入演算子 : T& operator=(T)
  • 新しいキーワードreloc
  • オーバーロード解決ルールの若干の変更
  • 一部の関数にABI破壊が生じる可能性がある
  • リロケーション操作のサポートのためのライブラリ関数

この提案では、リロケーションのために新しい型を導入することを避け、代わりにprvalue(修飾なしの素の型)をそのために利用しようとしています。

提案より、サンプルコード

void foo(std::string str);
auto get_string() -> std::string;
auto get_strings() -> std::pair<std::string, std::string>;

std::string gStr = "static string";

void bar(void) {
    std::string str = "test string";
    foo(reloc str);   // OK: std::stringにリロケーションコンストラクタがあればリロケーションされる
    foo(reloc gStr);  // ill-formed: gStrはローカル変数ではない

    std::pair p{std::string{}, std::string{}};
    foo(reloc p.first); // ill-formed: p.firstは完全なオブジェクトではなく、変数名でもない

    foo(reloc get_string());        // ill-formed: 変数名ではない
    foo(reloc get_strings().first); // ill-formed: 完全なオブジェクトではなく、変数名でもない

  foo(auto(str));   // ill-formed: リロケーション後の変数名は使用できない
}

void foobar(const std::string& str) {
    foo(reloc str); // OK: 参照をリロケーションする
                  // strの参照先オブジェクトの生存期間は影響を受けない
}

void foobar(std::string* str) {
    foo(reloc *str); // ill-formed: *strは変数名ではない
}

void foobar2(std::string* str) {
    foobar(reloc str); // OK, ポインタをリロケーションする
                     // strの参照先オブジェクトの生存期間は影響を受けない
} 

class A {
    std::string _str;
public:
    void bar() {
        foo(reloc _str); // ill-formed: _strは完全なオブジェクトではなく、ローカル変数でもない
    }
};

P2786R2 Trivial relocatability options

trivially relocatableをサポートするための提案。

以前の記事を参照

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

  • P1144への参照を最新のもの(5月公開分)に更新
  • 用語と定義の明確化のためにセクションを追加
  • 新しい構文について欠けていた例を追加
  • 基礎的ではない関数をライブラリ関数とした

などです。

この提案はP1144とともにEWGIでレビューされ、今後も議論を続けていくことに合意がとれています。また、この提案単体で既にEWGにおけるレビュー中です(とはいえP1144と強調して、より論点を明確にすることなどが求められています)。

P2795R0 Correct and incorrect code, and "erroneous behaviour"

P2795R1 Erroneous behaviour for uninitialized reads

P2795R2 Erroneous behaviour for uninitialized reads

未初期化変数の読み取りに関して、Erroneous Behaviourという振る舞いの規定を追加する提案。

未初期化変数の読み取りに関するErroneous BehaviourとはP2754R0で導入された概念で、未初期化変数を実装定義の値に初期化した上でその値の(ユーザーが書き込む前の)読み取りに対して指定される標準で定義された動作の1つです。

P2754R0については以前の記事を参照

Erroneous Behaviour(誤った動作、EB)は未定義動作ではなく、実装はユーザーによって初期化されていない変数を特定の値で変数を初期化しているためそれが起きたとしても安全であり、初期化する値を調整することでテストやデバッグに役立てることができます。また、EBを未定義動作として扱えば現状維持になり、EB/UBのまま維持しておくことで将来のさらなる大胆な改善(デフォルト初期化仕様の値初期化への完全な置き換えなど)のための門戸を開いておくこともできます。

未初期化変数の読み取りという問題の解決策としてEBの導入は最善(実現可能性、後方互換性、表現力の保護の観点で最善)であると認識されていますが、現在のC++標準ではEBという概念は定義されておらず、その導入そのものがハードルだとされていました。

この提案は、そのErroneous Behaviourという概念を標準に導入し、未初期化変数読み取り問題の解決を図るものです。

この提案では、自動変数のデフォルト初期化を次のように変更することを提案しています

自動変数のデフォルト初期化は、実装によって定義された固定値で変数を初期化する。
実装はこのエラーを診断することが許可されており、また推奨されているが、エラーを無視して読み取りを有効なものとして扱うことも許可されている。

Erroneous Behaviourというワードが直接出現するわけではありませんが、この文章の後半部分(2行目)がそれを意図しています。すなわち、初期化されていない値を読むことは意図したものではなく間違いなくバグではあり修正する必要があるものの、それを含むコードはwell-definedでありその点について診断されないとしてもプログラムは予測可能である(未定義動作ではない)、とするものです。

言い換えると、未初期化の値を読み取ることは誤りではあるものの、実装がそれを止めなければプログラムはその読み取りの結果として(未定義ではない)何らかの特定の値を得ることになります。実装は診断をしてもいいが、してもしなくてもそれについてwell-definedであることを保証する必要があり、誤った振る舞いが実行された場合に未定義動作及びそれに起因する結果(ソースコードからは予測できない命令の実行、タイムトラベルなど)をもたらすことはありません。

extern void f(int);

int main() {
  int x;     // default-initialized, value of x is indeterminate
  f(x);      // glvalue-to-prvalue conversion has undefined behaviour
}

この現在UBとなるコードについて、現在及びP2723R1(強制ゼロ初期化)とこの提案によるコンパイル結果の違いは次のようになります

動作について C++23 P2723R1 この提案
未定義動作? UB well-defined EB
それはバグ? 確実にバグ 意図的に0初期化しているのか忘れているのかわからない 確実にバグ
コンパイラの診断は可能か? コンパイラはこれを拒否することが許可されている 適合するコンパイラは診断できない 適合するコンパイラは受け入れなければならないが、QoIによって拒否することが許可されている

この提案の下での動作においては、xの値は何か特定の値に初期化されていますがその値は必ずしも0ではありません。これは、デバッグやテストに役立てるために特定の値を設定することを許可することと、特定の固定値にプログラマが依存してしまうことを回避することを意図しています。

この提案による標準の他の部分への影響は次のようなものが想定されます

  • 自動変数は全て何かしら初期化されるため、パフォーマンスに影響を与える可能性がある
    • このコストに関する分析はP2723R1でなされている
    • また、このコストは非クラス型だけではなく、パディングを持たずデフォルトコンストラクタが全てのメンバを初期化するようなクラス型にもかかってくる
  • 特に、共用体は完全に初期化される
    • 一般に、共用体をコピーしても誤りではなくパディングビットをコピーしても誤りではない
    • これは、誤りのある値のglvalueからprvalueへの変換自体は誤りではないものの、その値をコピー以外のことに使用するのは誤りであることを意味する
  • この提案は初期化のセマンティクスのみに影響し、不定値の使用の全般に影響するわけではない
    • 例えば、初期化された変数に不定値をコピーすることができ、その値を読み取ると未定義動作となる可能性がある
  • この提案は単一操作としての自動変数のデフォルト初期化にのみ影響する
    • 自動変数をplacement newでデフォルト初期化するような場合はこの提案の保証はない

この提案の実装に関しては、ほぼ同様のことを行うことがgcc/clangにおいてftrivial-auto-var-init=zeroというオプションによって利用可能となっています(ただしこれは診断を意図したものではないようです)。この提案の変更は主に標準内の動作仕様に関する変更であり、実装の負担は軽微だと思われます。

この提案の採択によって未初期化変数読み取りに関する未定義動作が誤った動作に変更される場合、コードベースに対する影響は次のようになるでしょう

  • 今日の正しいコード : パフォーマンスが低下する可能性がある他は観測可能な変化はない
  • 今日の正しくないコード : UBをEBに変更した場合(診断しない場合)、そのコードは依然として正しくはないもののその振る舞いは未定義動作ではなくなり、特定の振る舞いするようになる

また、EBはこの提案では未初期化変数の読み取りのために導入しようとしていますが、同様に現在未定義動作となっているもののバグと意図的なものを弁別でき、バグについてのみ診断が可能な未定義動作についてもこれと同様にEBとして指定することで未定義動作やそれに伴う意図しない動作を回避しながら安全な動作をさせるようにすることができる可能性があり、その候補リストも示されています。

P2809R1 Trivial infinite loops are not Undefined Behavior

自明な無限ループを未定義動作ではなくする提案。

以前の記事を参照

このリビジョンでの変更は、提案する内容のC言語におけるセマンティクスとの一致についてSG1のフィードバックを追記したことです。

現在のCのセマンティクスでは次の2つのループは同じものとみなされません

// 少なくとも定数ではない変数
extern bool cond;

void f1() {
  while (cond) {
    // ...
  }
}

void f2() {
  while (true) {
    if (!cond)
      break;
    // ...
  }
}

この2つのループは意味的には等価なものに見えますが、f1()の方のループの継続条件は定数式ではないためこの問題の下では異なります。Cのセマンティクスと一致するようにこの問題を解決すると、f1()のループは未定義のままですがf2()のループは未定義ではなくなります。

このことの問題点は、f2()のループに対してコンパイラはその終了を仮定することができない(プログラマの意図的なものとして処理せざるを得ない)と言うことです。なぜそれが問題なのかというと、逆に意図的に並行処理の進行保証を活用して無限ループを書いている場合に、そのループが終了すると仮定した振る舞いや最適化(ループの削除やマージ、並べ替え)が行えなくなるためです。同様の問題は、f2のようなループ内にreturnthrow(スレッドの終了処理とみなされる)がある場合にも起こります。

実際に、一部のGPUではワープの1つが無限ループに陥った場合に同じスレッドブロック内のすべてのワープの進行を停止するものがあるようです。この振る舞いは、無限ループが未定義動作である場合は許容されますが、そうでない場合は規格違反となります。f2のようなループによってそれが記述されている場合、現在は規格に適合した振る舞いですが、この提案の後(あるいはCのセマンティクスの下)では規格違反となります。

回避策として、f2のようなループ中にstd::this_thread::yield()を置いておくと言う方法が考えられます(他のスレッドに実行を委譲し続けることで上記動作を正当化する)。しかし、これは無限ループの中断という振る舞いを正当化するだけで、ループを最適化できないという問題は解決できません。

つまるところ、ループの継続条件が定数式であるかどうかという判定方法では不十分だということです。そのため、このリビジョンではCのセマンティクスとは一致しない解決を提案し、それをCにも適用することを提案しています。

提案ではまず、std::this_thread::halt()というライブラリ関数を追加します。これは、その呼び出しがwhile(true) std::this_thread::yield();の呼び出しと等価な関数であり、呼び出して使用するものというよりはある種のループをこれに置換する(後述)ためのものです。

次に、スレッドが最終的に行うと仮定して良い処理(これがあると無限ループは未定義ではなくなる処理)のリストに次の2つを追加します

  • std::this_thread::yield()の呼び出し
  • [[noreturn]]関数の呼び出し

これはこの提案の主題とはあまり関係なく、議論の過程でこの2つがスレッドにおける進行保証を担うのに有効なもので見落とされていることが分かったために追加されています。

最後に、次に該当する無限ループをstd::this_thread::halt()と等価なものであるとして、その呼び出しとして実装することを規定します(しても良いではなくしなければならない)。ここでは2つのオプションが提案されています

  1. オプション1 : 次の全てを満たすループ
    • スレッドが最終的に行うと仮定して良い処理を何もしていない
    • ループ継続条件が定数式でtrueとなる
    • ループは次のいずれも含んでいない
      • 直接のbreak;
      • ループの外側に抜けるgoto
      • return
      • co_return
      • co_await
      • co_yield
    • ループは次のどちらも含まない
      • 例外を投げうる処理
      • std::longjmp()の呼び出し
  2. オプション2 : 次の全てを満たすループ
    • スレッドが最終的に行うと仮定して良い処理を何もしていない
    • ループ継続条件が定数式でtrueとなる
    • ループ本文が空

どちらのオプションでも、この条件を満たすループはスレッドが最終的に行うと仮定して良い処理をしていなくても未定義動作にはならず、プログラマの意図的なものとみなされます(つまり、最適化の対象となりません)。どちらにしても、以前(現在のC)のセマンティクスであるループ継続条件が定数式(でtrueとなる)という条件に制約をいくつか加えた形になっています。また、そのようなループをstd::this_thread::halt()の呼び出しに置換してしまうことで無限ループは意図的なものであるとして終了しないが実行もしないという実装が可能になり、上記の一部のGPUが行なっているような動作(無限ループに突入すると実行中断)を正当化することができます。

P2810R1 is_debugger_present is_replaceable

P2546で提案されているis_debugger_present()をユーザーが置換可能にする提案。

以前の記事を参照

このリビジョンでの変更はP2546の最新リビジョン(R5)の内容を適用したことです。

P2811R5 Contract-Violation Handlers

P2811R6 Contract-Violation Handlers

P2811R7 Contract-Violation Handlers

契約プログラミングに、ユーザー提供の違反ハンドラを許可する提案。

以前の記事を参照

R5での変更は

  • 契約違反ハンドラ提案の簡単な沿革を追加
  • すべての形式の契約チェックアノテーションに対して、期待される評価地点を明確化
  • デストラクタの事前条件に関する契約違反の特定に関して注記を追加

R6での変更は

  • ヘッダ名を<contract>から<contracts>へ変更
  • contract_semantic列挙型にobserve列挙子を追加(P2877R0から)

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

  • 文字列プロパティの期待されるエンコーディングについて注記
  • 契約機能を使用するのに<contracts>のインクルードが必要ないことを明記
  • 契約違反ハンドラの呼び出し元特定とテストに関するセクションを追加
  • 適合実装がlocation()comment()に何を提供できるかを明確化

などです。

P2814R1 Trivial Relocatability --- Comparing P1144 with P2786

オブジェクトの再配置(relocation)という操作に関する2つの提案を比較する文書。

以前の記事を参照

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

この提案、特にtrivially relocationについてさらに時間をかけて議論していくことがEWGにおいて合意されています。relocation全体よりもまずtrivially relocationを優先し、なおかつ言語機能とする方向性が選好されているようです。

P2819R1 Add tuple protocol to complex

std::complextupleインターフェースを追加する提案。

以前の記事を参照

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

  • get()オーバーロードをHidden friendにした
  • tuple-likeコンセプトをタプルベースRangeアルゴリズムをサポートするように拡張
  • Annex Cへ追加する文言を修正

などです。

P2821R2 span.at()

P2821R3 span.at()

std::span.at()メンバ関数を追加する提案。

以前の記事を参照

R2での変更は、機能テストマクロの修正とP1024へのリンクを追加したことです。

このリビジョンでの変更は、typoの修正のみです。

P2828R2 Copy elision for direct-initialization with a conversion function (Core issue 2327)

型変換時のコピー省略のためのルールを明確化する提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言の多岐にわたる修正のみです。

P2834R1 Semantic Stability Across Contract-Checking Build Modes

契約プログラミングにおいて、契約述語の存在がビルドモードによって異なる影響を他の言語機能に及ぼさないようにする提案。

以前の記事を参照

このリビジョンでの変更は、Appendixの各節はその問題に関する推奨事項を提案する形で終了するようにしたことです。

P2835R1 Expose std::atomic_ref's object address

std::atomic_refが参照しているオブジェクトのアドレスを取得できるようにする提案。

以前の記事を参照

このリビジョンでの変更は、代替となるAPIの提案を追記したことです。

この提案のメインはstd::atomic_refにその参照先オブジェクトのアドレスを取得するための.data()関数を追加することですが、これは別にアトミックなポインタではないのでここから取得したポインタ経由でアクセスすることができてしまい、これを間違って使用されるとデータ競合によるUBを起こしやすくなってしまいます。

そのため、この提案では代替設計として、.data()const void*を返すようにするか、uintptr_tを返す別の関数を追加するものの2つを挙げています。

P2836R1 std::basic_const_iterator should follow its underlying type's convertibility

std::basic_const_iterator<I>Iに対応するconst_iteratorに変換できるようにする提案。

以前の記事を参照

このリビジョンでは、以前の解決策が別の問題を引き起こすことなどから取り下げ、R0で問題としていたことは別のIssueとして分離し、問題の範囲を限定しています。

このリビジョンでは、ある定数イテレータIに対してstd::basic_const_itertor<I>が得られる場合に、それがIに変換できないことを問題視しています。

void f(std::vector<int>::const_iterator i) {}

auto v = std::vector<int>();
{
  auto i1 = std::ranges::cbegin(v); // returns vector<T>::const_iterator
  f(i1); // ok
}

auto t = v | stdv::take_while([](int const x) { return x < 100; });
{
  auto i2 = std::ranges::cbegin(t); // returns basic_const_iterator<vector<T>::iterator>
  f(i2); // error(現在のC++23では
}

std::basic_const_itertor<std::vector<T>::iterator>std::vector<T>::const_iteratorに変換可能ではない(.base()を取れば可能ではある)ため、2つ目のf()の呼び出しはエラーになります。ただ実は、これはC++20ではranges::cbegin()vector<T>::iteratorを返していたため通っていました(結果的には問題なかっただけで間違った振る舞いです)。

コンテナ型Cに対して、std::basic_const_itertor<C::iterator>は意味的にはC::const_iteratorと同一のものであり、同等の扱いが可能であることが望ましく、それが期待と一致する振る舞いとなります。

このリビジョンでは、C++20の振る舞いを引継ぎつつも改善するために、std::basic_const_itertor<I>Iから変換可能な定数イテレータ型に同様に変換可能となるようにすることを提案しています。

namespace std {
  template<input_iterator Iterator>
  class basic_const_iterator {
    ...

    // 提案している変換演算子の宣言例

    template<not-a-const-iterator CI>
      requires constant-iterator<CI> && convertible_to<Iterator const&, CI>
    constexpr operator CI() const &;

    template<not-a-const-iterator CI>
      requires constant-iterator<CI> && convertible_to<Iterator, CI>
    constexpr operator CI() &&;

    ...
  }
}

変換先のイテレータCIbasic_const_iteratorではなく(not-a-const-iterator)、かつすでに定数イテレータである必要があります。これによって、const性を破壊するような変換を行うことはできません。

なお、std::const_iterator_tranges::cbegin()が一致しないことがある問題については、LWG Issue 3946にてstd::const_iterator_tの定義を変更することで解決が図られます。

namespace std {
  template<range R>
  //using const_iterator_t = const_iterator<iterator_t<R>>;
  using const_iterator_t = decltype(ranges::cbegin(declval<R&>()));

  template<range R>
  //using const_sentinel_t = const_sentinel<sentinel_t<R>>;
  using const_sentinel_t = decltype(ranges::cend(declval<R&>()));
}

おそらくこの変更はC++23に適用されます。この提案自体はC++26ターゲットですがC++23へバックポートすることを実装者への推奨事項とすることを提案しています。

なお、LEWGのレビューではこれをC++23のDRとすることに合意されています。

P2845R1 Formatting of std::filesystem::path

std::filesystem::pathstd::format()でフォーマット可能にする提案。

以前の記事を参照

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

  • fill-and-alignwidthについて既存もフォーマット文字列へのリンクを追加
  • range-format-specpath-format-specで置き換え
  • 欠落していた文字コード変換についての規定を追加

などです。

P2865R1 Remove Deprecated Array Comparisons from C++26

C++20の一貫比較仕様に伴って非推奨とされた、配列間の比較を削除する提案。

以前の記事を参照

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

  • オーディエンスにSG22を追加
  • gccの警告表示について追記
  • 提案する文言を最新のドラフトに適合させた
  • オペランドと配列からポインタへのdecayを説明する文言の誤りを修正
  • 文言レビューの結果を受けての修正

などです。

P2872R1 Remove wstring_convert From C++26

C++17で非推奨とされたwstring_convertC++26で削除する提案。

以前の記事を参照

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

  • コピペミスの修正
  • 最初のレビュワーとしてSG16をアサイ
  • 提案する文言を最新のドラフトに適合させた
  • 標準ライブラリ実装がこれに関して警告を発し始めた時期の記録
  • SG16レビューの概要と結果を追記

などです。

P2874R1 Mandating Annex D

P2874R2 Mandating Annex D

Annex Dセクションにある機能の規定について、標準の他の部分と記法を合わせる提案。

以前の記事を参照

R1での変更は

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

  • POD型/PODクラスの定義を斜体で表示する
  • is_podのMandatesをPreconditionsに切り替え
  • “shall (not) be” を “is (not)”へ修正
  • 隣接するPreconditionsの統合
  • 元のテキストから欠落していたnullを追加
  • D.24の番号変更
  • 半開区間を示す際のマークアップが壊れていたのを修正

などです。

この提案はすでに、2023年6月の全体会議でC++26に適用されることが決まっています。

P2877R0 Contract Build Modes and Semantics

現在の契約プログラミングの仕様を、現在明らかなユースケースをサポートし将来の拡張に対して開いているようにする提案。

現在SG21で議論されている契約プログラミング仕様では、No_evalとEval_and_abortの2つのビルドモードのみが提供されており、翻訳単位間でのビルドモードの混合は実装定義のセマンティクスによって条件付きでサポートされています。

No_evalビルドモードでは全ての契約アノテーション(1つの関数に指定されている契約条件の全体)はignoreセマンティクスを持ち、契約アノテーションに含まれる各条件式は評価されず契約違反が起こることはありません。そのため、このビルドモードでは他のセマンティクスに影響を与えません。

Eval_and_abortビルドモードでは全ての契約アノテーションenforceセマンティクスを持ち、契約アノテーションに含まれる各条件式が評価されそれがtrueを返さない場合は契約違反処理プロセスが発生し、その終了後にプログラムは終了されます。

これによってC++の実装には制限が課されており、2つのビルドモードのプロパティはすなわちC++コントラクト機能のプロパティであるとみなされ、プラットフォーム固有のビルドモードまたは将来のビルドモードの追加によってこの2つのビルドモードのプロパティや前提に反するようなセマンティクスを持つビルドモードを追加することが妨げられます。これらのプロパティの重要な特徴は、翻訳単位内の全ての契約アノテーションが同じセマンティクスを持つことです。

この提案は、契約アノテーションのセマンティクスとビルドモードの制限を取り払い実装の自由度を最大化することを目指し、それによってC++契約プログラミング機能が満たすべき現在明らかなニーズを満足しつつ、将来のニーズに適応するための拡張の余地を残しておくようにしようとするものです。

提案で挙げられている現在明らかなニーズとは次のようなものです

  • パッケージマネージャ
    • 現在利用可能なパッケージマネージャの多くは、各パッケージについて単一のビルドのみを提供する。つまり、デバッグとリリースの両方を提供したりしない
    • 契約アノテーションのセマンティクスがビルド時に決定しなければならない設計は、パッケージマネージャにとって大きな負担となる
    • パッケージマネージャの管理者が契約機能のビルドモードのどちらを選択するにせよ、その決定はパッケージの作成者の契約機能の使用意欲にネガティブな影響を与える可能性がある
    • パッケージマネージャの管理者がビルドモードの決定を各プロジェクトのビルドモードまで遅延する場合、エコシステム内での契約機能の使用は一貫性がなくなる可能性がある
      • ことなるオブジェクトファイルが異なるビルドモードでビルドされている場合、条件付きサポートとなる
  • パッケージとして配布されるソフトウェア
    • 歴史的に、一部のパッケージ提供者はアサーションを有効にしたパッケージを配布することを選択し、そうではない提供者はアサーションを含むテストをパスしていることを根拠として品質とサポートの目標を達成しているとみなしてアサーションを無効化したパッケージを配布することを選択している
    • C++契約機能はこのような選択肢をサポートし続ける必要がある。すなわち、契約アノテーションが評価されないことを許可しない設計は実行可能ではない
  • 契約の解除
    • 運用環境で契約チェックを利用する際の最も難しい問題の1つは、既存のコードに契約チェックを導入すること。
    • 契約違反によるプログラムの終了のコストが高くつくため、契約違反によってプログラムが終了する場合、現在動作しているプログラムに契約チェックを導入する意欲が失われる
    • 現在の2つのセマンティクス(ビルドモード)だけではこの問題を解消できない
  • REPL
    • REPL環境ではプログラムはコンパイルされないため、ビルドモードが存在しない
    • 契約の評価が有効かどうか、及び違反時の振る舞いについては、ユーザーがいつでも変更できる動的なプロパティとなる
  • デバッグ
    • 契約条件式はプログラムの本質的な動作に影響を与える副作用を持つべきではないが、それを検出するメカニズムが存在しないため、副作用が混入する可能性を受け入れなければならない
    • 副作用が発生しているかを判断するテクニックの一つは、契約アノテーションの評価を無効化した時と有効化した時の間で同じプログラムの動作を比較すること
    • 現在の2つのビルドモードではこれをサポートできない(この場合、契約違反を検出したいわけではないため)

これらのニーズは全て、現在のC++契約プログラミング仕様ではサポートできていません。これらのニーズを満たすには少なくとも次の要件を満たしている必要があります

  • 標準に準拠した契約実装では、リビルドを必要とせずに契約アノテーションの評価の有効/無効を切り替えられなければらない
  • 標準に準拠した契約実装では、ユーザーが契約アノテーションごとに、もしくは違反ハンドラの動作を通して、契約違反後に実行を継続するかを選択可能である必要がある

また、これらの要件がユーザーベースに適さない場合、実装はこれらの要件をサポートしないことも選択できるべきです。この提案の意図は、全てのC++実装や実行環境が全て同じ機能セットをサポートする必要があることではなく、それらの実装が全て標準に実装しwell-definedであり、他のC++プログラムに適用可能な動作に関するトレーニングと推論の対象になり続けるように維持することにあります。

その上でこの提案の変更は、大きく分けて次の2つです

  1. 契約アノテーションが評価される場合、その評価はignoreobserveenforceのいずれかのセマンティクスを持つ
    • ignore : 契約アノテーションは各契約条件を評価しないため、契約違反を起こさない
    • observe : 契約アノテーションは各契約条件を評価し、そのいずれかがtrueを返さない場合は契約違反処理プロセスが発生する
    • enforce : 契約アノテーションは各契約条件を評価し、そのいずれかがtrueを返さない場合は契約違反処理プロセスが発生する。契約違反処理プロセスの終了後、プログラムは実装定義の方法で終了する
  2. 契約アノテーションの個々の評価において、それがどのようなセマンティクスを持つかは実装定義とする
    • プログラム内の全てのアノテーションが同じセマンティクスを持つように強制される場合がある
    • 異なる評価で異なるセマンティクスを持つことにより、同じ関数の異なるインライン版で異なるセマンティクスをコンパイル時に選択することができ、それはODR違反ではなくなる
    • 実装は、その選択をどのように行うかを指定する仕組みがユーザーに公開されていれば、契約アノテーションのセマンティクスをコンパイル時・リンク時・実行時のいずれかのタイミングで選択できる

この提案は、(ビルドモードの)セマンティクスとしてobserveを1つ追加するとともに、契約アノテーションの評価のセマンティクスを契約アノテーション全体からその個々のプロパティとすることで、契約アノテーションを持つもののコンパイル時プロパティが契約アノテーションのセマンティクスに依存しない(できない)ようにするものです。

これによって、P2834R0で提案されている契約アノテーションに関する三原則の1つ目(契約アノテーションがビルドモードによって他の言語機能に影響を与えないこと)が満たされ、翻訳単位間で契約アノテーションのセマンティクスが混合していることも許可されます。

この提案の後でも、実装は現在の2つのビルドモードだけをサポートすることを選択することができ、また、コンパイラはリンク時やコンパイル時、実行時で契約アノテーションのセマンティクスを選択できるようなビルドオプションを提供することもできます。

この提案の内容はどうやらSG21の合意のもとで契約仕様に導入(Contratcts MVPにマージ)されたようで、P2811R6の採用と相まって、C++契約プログラミング仕様はビルドモードの概念から解き放たれることになります。そこでは、この提案で解説されているように、契約条件のセマンティクス(評価されるかどうか、評価された時にどうなるかなど)はignoreobserveenforceのいずれかが契約アノテーションごとに決定され、その決定は実装定義となります。さらに、observeの場合に契約違反を起こした場合の振る舞い(即終了するのか、例外を投げるのか、継続するのか)は違反ハンドラの差し替えによってユーザーがカスタマイズすることができます。

P2878R2 Reference checking

P2878R3 Reference checking

P2878R4 Reference checking

プログラマが明示的に関数の戻り値に関するライフタイム注釈を行えるようにする提案。

以前の記事を参照

R2での変更はサンプルコードの修正などです。

R3での変更は

  • do式/パターンマッチングの未解決の問題に対する解決策の追加
  • 一時オブジェクトの名前付き変数への代入がエラーになることを追加
  • 関数が個別に分析されることを示すために、再帰関数呼び出しの例を追加
  • ラムダ式の別の例を追加
  • Usageセクションの追加
  • Viral Attribution Effortセクションの追加

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

  • サンプルコードの修正
  • FAQの追加(ポインタを対象としないことについて)

などです。

この提案はSG23のレビューによってこれ以上議論されないことが決定されています。

P2885R0 Requirements for a Contracts syntax

C++契約プログラミング機能の構文の選択のための評価基準や要件をまとめる文書。

少し上のP2877R0などの採択によって、契約プログラミング機能の意味論の部分はほぼ固まりつつあります。残っている大きな問題は構文の選択に関するものです。

現在のContratcts MVPでは属性likeな構文を採用しており、これはC++20の契約プログラミング機能から受け継いでいるものです。それに対して、クロージャベースの構文(P2461R1)や条件中心構文(P2737R0)が提案され、また他の構文を考えることもできます。しかし、現在のところこれらの構文提案を統一的に比較し評価するためのフレームワークが存在しません。

この提案は、契約構文の選択のために、契約構文に求められる要件をまとめその比較基準を提供しようとするものです。ただし、この提案は構文を選択することを意図しておらず、既存提案の詳細な分析を行おうとするものではなく、あくまで契約機能の構文に求められる要件やその比較基準を確立することを目的としたものです。

提案されている要件の概要は次のようなものです

  • 基本要件
    • 美学
      • 読みやすく、目立ちすぎない
    • 簡潔さ
      • なるべく短いトークンで記述できる
    • アクセシビリティ
      • 学びやすく、教えやすい
    • 既存プラクティスとの一貫性
    • 残りのC++の部分との一貫性
      • 既存のC++言語の構文に自然に適合する
  • 互換性の要件
    • C++言語の一部としての有効性
      • 既存の言語機能に対して、曖昧さ、混乱、非一貫性、意図しない相互作用を起こさない
    • 破壊的変更がない
      • 既存のC++コードを壊さない
    • マクロなし
    • パースしやすさ
      • C++コードをパースするために新たなハードルを生み出してはならず、既存文法を不必要に複雑化させない
    • 実装経験
    • 後方互換
    • ツールによる利用が可能であること
    • C互換性
      • Cで別の意味を持つ既存構文と同じものを追加すべきではない
  • 機能性の要件
    • 述語を受けられる
      • 文脈的にbool変換可能な任意のC++における式を受けられる必要がある
    • 契約種別
    • 位置と名前探索
      • 事前/事後条件は関数宣言の一部である必要がある
      • アサーションは関数定義内部でのみ現れる
    • 複数の事前/事後条件
      • 同じ関数に対して複数の事前/事後条件を追加できる
    • 戻り値
      • 事後条件においては、戻り値を参照する方法を提供しなければならない
  • 将来の進化に備えた要件
    • 非参照非const引数の参照
      • 事後条件において、非参照非const引数を参照する拡張が可能である
    • 明示的なキャプチャ
      • 契約アノテーションで使用する変数を明示的にキャプチャする拡張が可能である
    • 戻り値の構造化束縛
      • 事後条件において、戻り値を構造化束縛して参照する拡張が可能である
    • 契約アノテーションの再利用
      • 同じ事前/事後条件の集合を共有する関数の間で、それを抽出してまとめて再利用できるようにする拡張が可能である
    • メタアノテーション
    • 引数をとるメタアノテーション
      • メタアノテーション構文はさらに、引数をとることができるように拡張可能である
    • ユーザー定義メタアノテーション
      • 標準で定義されるものと競合しない、ユーザー定義のメタアノテーションを可能にする拡張が可能である
    • 無視できないメタアノテーション
      • 標準属性の無視可能性ルールに適合しないようなメタアノテーションを可能にする拡張が可能である
    • 一次情報と二次情報の識別
    • クラス不変条件
      • クラス不変条件を表現するための新しい構文を導入可能であること
    • 手続き型インターフェース
      • P0465R0で提案されている手続き型インターフェースに対応可能であること
    • requires
      • 契約アノテーションrequires節で制約できるようにする拡張が可能であること
    • より一般的な拡張性
      • ここの要件のリストは大量だが、これでも将来的に契約機能に必要になる可能性のある拡張を網羅していない

これらの要件は排他的なものもあるため必ずしも全てを満たす必要はなく、それはトレードオフとして比較することを意図しています。

P2898R1 Build System Requirements for Importable Headers

モジュールにおけるインポート可能なヘッダ(importable header)というものを修正する提案。

以前の記事を参照

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

  • 誤解に基づいていたため、インポート構文に関するセクションを削除
  • インポート可能なヘッダの導入におけるインクリメンタルビルドのパフォーマンスの重要性を強調
  • フィードバック等に基づいて、推奨事項のセクションを書き換え
  • 新しい結論を反映するためにタイトルを変更

などです。

このリビジョンでの提案(推奨事項)は

  • 依存関係のスキャンでは(ヘッダユニットの)インポートをエミュレートする必要がある
    • 一般的には、#includeに基づくプリプロセスはimportを使用した時とほぼ同等と理解されている
    • しかし、それが誤って(その理解を破る形で)行われた場合にそれを検出したり検証する方法はない
    • 依存関係スキャンと実際のコンパイルとの間で動作及び理解を一貫させるためには、依存関係スキャンはimport処理を完全にエミュレートしなければならない
  • インポート可能なヘッダの検出を許可する
    • インポート可能なヘッダの実装における最大の課題の1つは、ヘッダユニットのリストとその引数を依存関係スキャンに提供する時に生じるボトルネックを回避するという要件が、そのステップでインポートを正しくエミュレートするための要件と矛盾していること
    • これは中間ターゲットが同じ出力を生成した場合に下流ターゲットの無効化を解除できるビルドシステムによって軽減できるが、その機能をサポートしていないビルドシステムでは使用できない
    • この提案ではその代替案として、依存関係スキャンが動的にどのヘッダがインポート可能かを識別できるようにすることを提案する
      • 実装する方法の1つとして、ヘッダと同じ場所に同じ名前かつ特定の拡張子で、識別のためのメタデータファイルを置く方法が考えられる
      • メタデータファイルはインポートプロセスエミュレーションのために必要なローカルプリプロセッサ引数に関する情報を含む
      • この方法の利点はヘッダのインクルードとインポートで同じ検索メカニズムが使用されること
  • ビルドグラフで動的ノードをサポートしないビルドシステムにおいても、インポート可能なヘッダが宣言されたリストが必要
    • ビルドグラフでノードの動的生成(つまりコンパイル実行時にコンパイル対象が増える)に対応していないビルドシステムでは、プロジェクトで必要となる可能性のあるすべてのバリエーションですべてのインポート可能ヘッダのビルドルールを事前生成する必要がある
      • 従って、インポート可能なヘッダのリストを事前に知っている必要がある

などです。

P2902R0 constexpr 'Parallel' Algorithms

並列アルゴリズムを定数式で使用できるようにする提案。

並列アルゴリズムとはC++17で追加された実行ポリシーを受け取るオーバーロードの事で、これれらのものは並列化やベクトル化によってアルゴリズムの実行時パフォーマンスを向上させることが目的であるため、cosntexpr指定はされておらず定数式では使用できません。

並列アルゴリズムも含めたアルゴリズムは他の処理や他のアルゴリズムと組み合わせて使用するものであり、他のアルゴリズムや標準ライブラリの多くのものがconstexpr対応を果たしている中で並列アルゴリズムconstexpr対応しない場合、そのような並列アルゴリズムと組み合わされた一連の処理を定数式で実行することができなくなります。

その場合でも、std::is_constant_evaluated()if constevalを使用して分岐をすればとりあえず対応はさせられますが、そもそも並列アルゴリズムconstexpであればより単純に目的を達成できます。

この提案はそのような目的のために並列アルゴリズムconstexpr対応させることを目指すものです。ただし、コンパイル時にも並列化を要求するものではありません。

この提案では、全ての実行ポリシーを定数式で指定可能とすることを提案しており、その実装はコンパイル時に対応する通常のアルゴリズム関数に処理を委譲することでコンパイル時実行することを意図しています。

P2904R0 Removing exception in precedence rule(s) when using member pointer syntax

メンバポインタ構文で()の使用を許可する提案。

クラスCのメンバBarのメンバポインタを取得する構文は&C::barのように書きますが、この時&(C::bar)のように括弧で括る形式は明確に禁止されており、コンパイルエラーとなります。

なぜこのような制限があるのかは不明ですが、この制限は不要のものと思われ他の場合と一貫していないため、この提案はこの制限を取り払うことを提案するものです。

struct C {
  void Bar(int);
};

int main() {
  void (C::*ptr)(int) = &(C::Bar); // MSVCはok、clang/gccはng
}

また、これは実装間で挙動の相違を生み出してしまっているようで、MSVCでは意図通り(この提案の提案通りに)になるようです。

P2905R0 Runtime format strings

P2905R1 Runtime format strings

std::make_format_args()が左辺値でフォーマット対象の値を受け取るようにする提案。

std::formatはフォーマット文字列のコンパイル時チェックを行うため、フォーマット文字列はコンパイル時に決定していなければなりません。そのため、フォーマット文字列を実行時に与えたい場合はstd::vformat()を使用します。

std::string str = translate("The answer is {}."); // gettextライブラリによる翻訳後文字列をフォーマット文字列として使う
std::string msg = std::vformat(str, std::make_format_args(42));

ただ、このv系の型消去APIはテンプレートの肥大化を回避するためのAPIであり、エンドユーザーではなくフォーマット内部実装や独自のフォーマット関数を作成する場合などに使用するものであるため、実行時のフォーマット文字列指定のために使用するのは本来の用途ではありません。

特に、std::make_format_args()は簡単に間違った使い方ができてしまいます。

std::string str = "{}";
std::filesystem::path path = "path/etic/experience";
auto args = std::make_format_args(path.string()); // path::string()はprvalueを返す

std::string msg = std::vformat(str, args);  // UB、argsの参照するstd::stringオブジェクトは寿命が尽きている

std::make_format_args()はフォーマット対象の値を型消去して渡すためのものであり、内部でその値を保持するわけではなく、一時オブジェクトを渡してもその寿命は延長されません。このことは、関数のインターフェースや効果などから読み取ることは難しく、これはあくまで内部実装のための関数です。

この提案はstd::make_format_args()のこの問題を改善するために、引数型を非const左辺値参照をとるように変更することで一時オブジェクトを渡すとコンパイルエラーになるようにしようとするものです。

namespace std {

  // make_format_args()の現在の宣言例
  template<class Context = format_context, class... Args>
  format-arg-store<Context, Args...> make_format_args(Args&&... fmt_args);

  // この提案による変更
  template<class Context = format_context, class... Args>
  format-arg-store<Context, Args...> make_format_args(Args&... fmt_args);
}

これによって、先ほどのようなコードはコンパイルエラーとして弾かれるようになります。

これは破壊的変更となりますが、この提案はC++23へのDRとすることがLEWGによって承認されています。

std::make_format_args()の利用率は低いと思われ、コンパイルエラーとなるのは一時オブジェクトか右辺値を渡した場合なので、正しい使用法のほとんどはこの提案の後でも変更の必要はありません。エラーとなる場合はstd::forward()してしまっているか、一時オブジェクトを直接渡しているかの場合のどちらかのはずで、前者はstd::forward()を削除する修正が必要となり、後者は一時オブジェクトの寿命を延長する変更が必要となります。

この提案のR0では、実行時のフォーマット文字列指定のためのAPIstd::runtime_format)も同時に提案していましたが、それは別の提案に分離されました。

P2906R0 Structured bindings for std::extents

std::extentsに構造化束縛サポートを追加する提案。

std::extentsstd::mdspanに対してその要素数を指定するためのクラステンプレートです。

// double型の3x3行列を表すmdspan
using mat33d = std::mdspan<double, std::extents<std::size_t, 3, 3>>;

// double型の3xN行列を表すmdspan(Nは実行時に決定)
using mat3nd = std::mdspan<double, std::extents<std::size_t, 3, std::dynamic_extante>>;

// double型の2次元行列を表すmdspan(サイズは実行時に決定)
using matd = std::mdspan<double, std::dextents<std::size_t, 2>>;

std::mdspanメンバ関数.extents()からこのstd::extentsオブジェクトは取得できて、std::extents.extent(n)からn + 1次元の要素数を取得できます。特に実行時にその要素数が決まる場合はこれを用いてループを回す必要があります。

// 3次元mdspanを出力する関数
template<typaname D, typename E, typename L, typename A>
void print_3d_mdspan(std::mdspan<D, E, L, A> mat) {
  using I = std::mdspan<D, E, L, A>::index_type;

  // 要素数情報の取得
  const auto e = mat.extents();

  // 全要素の走査
  for (I z = 0; z < e.extent(2); ++z) {
    for (I y = 0; y < e.extent(1); ++y) {
      for (I x = 0; x < e.extent(0); ++x) {
        // 要素取得
        const auto v = mat[z, y, x];

        // 出力
        std::print("[{}, {}, {}] = {}\n", z, y, x, v);
      }
    }
  }
}

現在のstd::extentsAPIは限定されており、次元ごとの要素数の取得はこうするしかありません。

この提案は、std::extentsを構造化束縛対応させることで、各次元の要素数取得をより簡易に行えるようにしようとするものです。

先ほどの例は例えば次のように書き直せます

// 3次元mdspanを出力する関数
template<typaname D, typename E, typename L, typename A>
void print_3d_mdspan(std::mdspan<D, E, L, A> mat) {
  using I = std::mdspan<D, E, L, A>::index_type;

  // 要素数情報の取得
  const auto [depth, height, width] = mat.extents();  // 👈 構造化束縛によって直接要素数を取得する

  // 全要素の走査
  for (I z = 0; z < depth; ++z) {
    for (I y = 0; y < height; ++y) {
      for (I x = 0; x < width; ++x) {
        // 要素取得
        const auto v = mat[z, y, x];

        // 出力
        std::print("[{}, {}, {}] = {}\n", z, y, x, v);
      }
    }
  }
}

さらに、P1061にて提案されている構造化束縛でパラメータパックを導入する機能を加味すると、このコードはさらに簡易に書くことができます

// 3次元mdspanを出力する関数
template<typaname D, typename E, typename L, typename A>
void print_3d_mdspan(std::mdspan<D, E, L, A> mat) {
  using I = std::mdspan<D, E, L, A>::index_type;

  // 要素数情報の取得
  const auto [...es] = mat.extents();

  // 全要素の走査
  for (const auto [...is] : std::cartesian_product(std::views::iota(0, es)...)) {
    // 要素取得
    const auto v = mat[is...];

    // 出力
    std::print("[{}, {}, {}] = {}\n", is..., v);
  }
}

このことを実装するにあたって、静的エクステントをどう扱うかについて選択肢があります。静的エクステントはコンパイル時に定まっている定数値であるため、構造化束縛でそのことを維持するかどうかで少し実装が異なります(構造化束縛はconstexpr指定できない)。

  1. 静的エクステントを実行時の値に降格する
  2. std::integral_constantなどを利用して、コンパイル時定数であることを維持する

オプション1に比べてオプション2では実装の複雑さが増大し、定数値の扱いが難しくなります(std::integral_constantの暗黙変換によって容易に定数性が失われるため)。

この選択に関してはLEWGの投票において決定される予定で、(この記事を書いてる時点では)まだその投票は行われていません。

P2910R0 C++ Standard Library Ready Issues to be moved in Varna, Jun. 2023

6月に行われたVarna会議でWDに適用されたライブラリに対するIssue報告の一覧

P2911R0 Python Bindings with Value-Based Reflection

提案中の値ベースリフレクションによってPythonバインディングの作成を簡素化できることを示す文書。

この文書は、pybind11によるPythonバインディングの作成に値ベースリフレクション(P1240R2)を活用することでその作業を簡素化できることを示し、その利点と課題についての議論を行おうとするものです。

例えば次のような列挙型があるとき

struct Execution {
  enum class Type {
    new_,
    fill,
    partial,
    cancelled,
    rejected
  };
};

これのPythonバインディングを作成するコードは次のようになります

py::enum_<Execution::Type>(/*binding scope*/, "Type")
  .value("new_" , Execution::Type::new_)
  .value("fill" , Execution::Type::fill)
  .value("partial" , Execution::Type::partial)
  .value("cancelled", Execution::Type::cancelled)
  .value("rejected" , Execution::Type::rejected);

これは列挙型のメンバを手動で展開しているため、繰り返し同じようなコードを書かなければならず、Execution::Typeが更新されるとこちらも手動で更新しなければならないなど、不便な面があります。

対して、値ベースリフレクションを利用すると同等のバインディングは次のようなコードで完結させられます。

bind_enum<Execution::Type>(/*binding scope*/);

bind_enum()はこの目的に書かれた汎用的なコードであり、ライブラリなどとして提供されることを意図しています。そして、このbind_enum()は次のように実装できます

// 型名だけを取り出す関数
template<typename T>
std::string basename() {
  auto name = std::string{name_of(^T)}; // ^T reflects type T

  if (size_t pos = name.rfind(':'); pos != std::string::npos) {
    return name.substr(pos + 1);
  }
  
  return name;
}

template<typename EnumT, typename Scope>
void bind_enum(Scope& s) {
  auto enum_ = py::enum_<EnumT>(s, basename<EnumT>().c_str());

  // 列挙型EnumTの全てのメンバに対してイテレートする
  template for (constexpr auto e : members_of(^EnumT)) {
    enum_.value(name_of(e), [:e:]); // [:e:] un-reflects e
  }
}

このように、値ベースリフレクションを用いると、型のメンバなどを列挙しその名前を取得してそれを特定の関数に投げることを繰り返すような処理をかなり自動化して書くことができ、現在手動で書かざるをえずに不便やバグの元となっている部分をより簡易かつ安全に記述することができます。

提案には、そのほかにもクラスメンバや継承、関数オーバーロードなどの同様の例が記載されています。

これらの試みによって得られた利点と課題は次のようなものです

  • 利点
    • ボイラープレートの大幅(~95%)な削減
    • リフレクションを使用すると多くの場合にエラーの可能性を軽減しうる
      • 元のC++コード変更への追随忘れなどを回避できるなどによる
    • ほとんどのバインディングは慎重に選択されたデフォルトの動作を使用して合理的に自動化できる
      • この提案では、pybind11で指定されたデフォルトを活用している
  • 課題
    • バインディングのカスタマイズ機能はPythonC++の言語機能の違いをカバーする必要がある
      • そのサポートのために、ユーザー定義属性があると便利だと思われる
    • パラメータ名リフレクションなど、いくつかのリフレクション機能は危険
      • Pythonの名前付き引数をサポートさせるために、C++の関数引数名をリフレクトしイテレートする方法が考えられるが、C++においては関数引数名はそのシグネチャの一部ではなく、宣言/定義によって変わりうる
    • いくつかのコーナーケースでは、リフレクションに基づく自動化は問題を隠蔽し、誤った安心感を与える可能性がある
    • rangeのリフレクションをその要素名のリストに展開する機能があると便利な場合があった

この文書はこのようなことを行うライブラリを提案しているのではなく、値ベースリフレクションの利点を示すとともに足りないものやあった方がいい機能などを示し、その議論を前進させようとするものです。

P2912R0 Concurrent queues and sender/receivers

P1958R0で提案されているbuffer_queueにP2300のsender/receiverモデルをベースとしたAPIを拡張する提案。

P1958R0のbuffer_queueは、内部の固定長バッファをリングバッファとして使用して同期/非同期の両方のAPIを提供することで並行プログラミングで使用可能な並行キューを提供しようとするものです。

この提案は、このbuffer_queueをP2300のsender/receiverに適応可能なように拡張しようとするものです。また、それに伴ってAPIのスタイルも変更されています。

提案及び実装より、buffer_queueの概要

template <typename T, typename Alloc = std::allocator<T>>
class buffer_queue {
public:
  using value_type = T;

  explicit buffer_queue(size_t max_elems, Alloc alloc = Alloc());
  ~buffer_queue() noexcept;

  // observers
  bool is_closed() noexcept;
  size_t capacity() const noexcept;

  // modifiers
  void close() noexcept;

  // 同期pop
  T pop();
  std::optional<T> pop(std::error_code& ec);
  std::optional<T> try_pop(std::error_code& ec);

  // 同期push(コピー)
  void push(const T& x);
  bool push(const T& x, error_code& ec); // used to be wait_push
  bool try_push(const T& x, error_code& ec);

  // 同期push(ムーブ)
  void push(T&& x);
  bool push(T&& x, error_code& ec); // used to be wait_push
  bool try_push(T&& x, error_code& ec);

  // 非同期push/pop
  sender auto async_push(const T& x) noexcept(is_nothrow_copy_constructible_v<T>);
  sender auto async_push(T&& x) noexcept(is_nothrow_move_constructible_v<T>);
  sender auto async_pop() noexcept;
};

この提案で追加されたのは、async_~という名前の3つの操作です。これらは非同期的にpush/popを行う関数で戻り値としてsenderを返します。これによって、非同期操作の待機や継続はP2300のsenderアルゴリズムを利用して行うことができます。

また、同期操作のインターフェースも変更されており、操作の成否を判定できるインターフェースがstd::error_codeを受け取るようになり、pop()操作では戻り値がstd::optionalを返すようにされています。

P2915R0 Proposed resolution to CWG1223

後置戻り値型関数宣言との曖昧さを解消するために、autoの存在を構文的に扱うようにする提案。

後置戻り値型関数宣言はautoを戻り値型に持つ関数宣言でのみ考慮されますが、この制限は意味的なものであり構文的なものではありませんでした。それによって、C++11以前は有効だった変数宣言や式が関数宣言として扱われてしまう場合がありました。

struct A {
  A(int *);
  A *operator()(void);
  int B;
};

int *p;
typedef struct BB { int C[2]; } *B, C;

void foo() {
  A (p)()->B;  // ng、関数宣言として扱われる(C++11以降

  A a(B ()->C);       // ng、関数宣言 or 変数宣言
  sizeof(B ()->C[1]); // ng、関数宣言に対するsizeof or 式に対するsizeof
}

この提案は、この場合にautoの存在を構文的にもチェックし制限することでこの問題を解決しようとするものです。この提案後、上記コードは次のようにコンパイルされるようになります

void foo() {
  A (p)()->B;  // ok、式(A::Bへのメンバアクセス)

  A a(B ()->C);       // ok、変数宣言
  sizeof(B ()->C[1]); // ok、式に対するsizeof
}

提案文書より、その他の例

struct M;
struct S {
  S* operator()();
  int N;
  int M;

  void mem(S s) {
    auto(s)()->M; // S::Mは::Mを非表示にし、これは式とみなされる
  }
};

void f(S s) {
  {
    auto(s)()->N; // 式
    auto(s)()->M; // 関数宣言(::Mが見えているため)
  }
  {
    S(s)()->N; // 式(autoがないため関数宣言とはみなされない)
    S(s)()->M; // 式(autoがないため関数宣言とはみなされない)
  }
}

この問題はIssue1223としてC++11に対して2010年に提出されて以降放置されていましたが、C++23にてauto(x)のdecay-copy構文が導入されたことによって影響が大きくなることがわかったため、この提案で迅速に解決されました(既に2023年6月の会議で承認され、C++26WDに取り込まれています)。

P2917R0 An in-line defaulted destructor should keep the copy- and move-operations

P2917R1 An in-line defaulted destructor should keep the copy- and move-operations

クラス定義内でdefault宣言されたデストラクタがある場合に、コピー/ムーブコンストラクタ及び代入演算子を暗黙default宣言するようにする提案。

C++11以降、default含むユーザー宣言デストラクタが存在する場合、そのクラスのコピーコンストラクタは暗黙的にdefault定義されますがそれは非推奨とされます。また、そのようなクラス型のムーブコンストラクタ及び代入演算子は暗黙定義されません。

class Apple {
public:
  ~Apple() = default;
  // コピーコンストラクタ及び代入演算子は暗黙default定義される(非推奨の振る舞い)
  // ムーブコンストラクタ及び代入演算子は暗黙定義されていない
};

このAppleのようなクラスをコピー/ムーブ可能にしたければ、全てのメンバ関数を明示的に宣言しなければなりません。

class Apple {
public:
  ~Apple() = default;
  
  // コピーを有効化
  Apple(const Apple&) = default;
  Apple& operator=(const Apple&) = default;

  // ムーブを有効化
  Apple(Apple&&) = default;
  Apple& operator=(Apple&&) = default;
};

このことは、特殊メンバ関数のデフォルト宣言というボイラープレートコードを量産しており、また、デストラクタは他の特殊メンバ関数とは独立しているべきだとして、この提案はこの制限を解除しようとするものです。

この提案では、クラスの最初のデストラクタの宣言がdefault宣言である時、コピー/ムーブコンストラクタ及び代入演算子を暗黙default宣言するようにします。それによって、上記のAppleのようなクラスは追加の特殊メンバ関数の宣言なしでコピーとムーブが可能になります。

class Apple {
public:
  ~Apple() = default;
  // コピー/ムーブコンストラクタ及び代入演算子は暗黙default定義される
};

ただし、最初のデストラクタの宣言がdefaultではない場合、すなわちクラス定義外でdefault宣言されている場合は現行通りにムーブコンストラクタ/代入演算子は定義されず、コピーコンストラクタ/代入演算子の暗黙default定義は非推奨です。

class Apple {
public:
  ~Apple();
  // コピーコンストラクタ及び代入演算子は暗黙default定義される(非推奨の振る舞い)
  // ムーブコンストラクタ及び代入演算子は暗黙定義されていない
};

Apple::~Apple() = default;

この提案の内容は、以前のコードに破壊的変更をもたらします。

// 現在このクラスはムーブ可能ではないが、この提案の後ではムーブ可能となる
struct A {
  std::unique_ptr<int> pi;
  virtual ~A() = default;
};


// 現在このクラスはムーブ可能ではないが、この提案の後ではムーブ可能となる
struct B {
  std::string s;
  virtual ~B() = default;
};

void f() {
  B b1;
  B b2;

  b2 = std::move(b1); // 現在はコピーされているが、この提案の後ではムーブされる
}


// 現在このクラスはムーブ可能ではないが、この提案の後ではムーブ可能となる
struct C {
  // ムーブ操作を無効化することを意図するデストラクタ宣言
  ~C() = default;
};


struct Base {
  int x{};
  
  virtual ~Base() = default;

  // その他virtualメンバ関数
};

struct Derived : Base {
  int y{};
};

void g() {
  Base b{};
  Derived a = b;  // スライスが発生する、この提案後は気付きづらくなる可能性がある
}

P2918R0 Runtime format strings II

P2918R1 Runtime format strings II

std::format()の実行時フォーマット文字列のためのAPIを追加する提案。

モチベーションの一部は少し上のP2905R1と共通しているのでそちらもご覧ください。

std::format()のフォーマット文字列はコンパイル時検査される関係上、コンパイル時に確定している文字列でなければなりません。実行時文字列によってフォーマット文字列を指定したい場合はstd::vformatなどを使用することになるのですが、これは内部実装用のもので利用しやすいAPIではありませんでした。

この提案は、実行時文字列によるstd::formatのための専用のAPIを追加することで、実行時フォーマット文字列によるstd::formatの利便性を改善しようとするものです。

提案では、std::runtime_format()という関数に実行時フォーマット文字列を渡し、この関数の戻り値をstd::format()のフォーマット文字列(第一引数)として渡すことで実行時フォーマット文字列によるフォーマットをstd::format()に組み込みます。

void f(std::string_view str) {
  // 現在の実行時文字列によるフォーマット文字列指定
  auto rfmt1 = std::vformat(str, std::make_format_args(42));

  // この提案
  auto rfmt2 = std::format(std::runtime_format(str), 42);
}

std::runtime_format()は受け取った文字列をラップする専用の型を返し、std::basic_format_stringがその型を受け取れるようにすることでこのサポートを行えるようにしており、フォーマット文字列の検証はコンパイル時と同様にstd::basic_format_stringのコンストラクタで(実行時に)行われます。

P2920R0 Library Evolution Leadership's Understanding of the Noexcept Policy History

LEWGの管理者が認識する、noexceptの指定ルールについてのこれまでとこれからの議論についてのまとめ。

おそらく、5月公開の提案においてLakos Ruleに関する提言が相次いだことを受けてのものです。Lakos Rule周りの議論についての歴史がまとめられており、今回提出された提案を受けてどう議論をしていくのかなどがスライドで説明されています。

P2921R0 Exploring std::expected based API alternatives for buffer_queue

P2912R0で提案されているbuffer_queueAPI拡張について、std::expectedを返すAPIを検討する提案。

少し上のP2912R0によるP1958R0のbuffer_queueAPI拡張においては、一部の失敗しうる操作に対してstd::error_codeを受けてそこにエラー情報を出力する無例外APIを提案していました。

template <typename T, typename Alloc = std::allocator<T>>
class buffer_queue {
  
  ...

  // 同期pop
  std::optional<T> pop(std::error_code& ec);
  std::optional<T> try_pop(std::error_code& ec);

  // 同期push(コピー)
  bool push(const T& x, error_code& ec);
  bool try_push(const T& x, error_code& ec);

  // 同期push(ムーブ)
  bool push(T&& x, error_code& ec);
  bool try_push(T&& x, error_code& ec);

  ...
};

この提案は、これらのAPIstd::error_codeを受け取るのではなくstd::expectedを返すようにすることを検討するものです。

この提案ではいくつかのタイプのAPIを検討しています。

まず一つは、std::error_codeの代わりにstd::nothrowを渡してstd::expectedを受けることを明示するタイプです。

template <typename T, typename Alloc = std::allocator<T>>
class buffer_queue {
  
  ...
  
  // P2912R0のAPIの同期push
  void push(const T& x);
  bool push(const T& x, error_code& ec);

  // expectedを返すAPI
  void push(const T&);
  auto push(const T&, nothrow_t) -> expected<void, conqueue_errc>;
  ...
};

int main() {
  buffer_queue<T> q{};

  // P2912R0
  std::error_code ec;
  if (q.push(5, ec))
    return;
  println("got {}", ec);

  // この提案
  if (auto result = q.push(5, nothrow))
    return;
  else
    println("got {}", result.error());
}

この場合の利点は、API呼び出し前にerror_codeオブジェクトを用意しなくても良いところです。

もう一つは、例外を投げうるAPIを削除して、全てstd::expectedを返すAPIに統一するものです。

template <typename T, typename Alloc = std::allocator<T>>
class buffer_queue {
  
  ...
  
  // P2912R0のAPIの同期push
  void push(const T& x);
  bool push(const T& x, error_code& ec);

  // expectedを返すAPI
  auto push(const T&) -> expected<void, conqueue_errc>;

  ...
};
int main() {
  buffer_queue<T> q{};

  // 無例外の例
  {
    // P2912R0
    std::error_code ec;
    if (q.push(5, ec))
      return;
    println("got {}", ec);

    // この提案
    if (auto result = q.push(5))
      return;
    else
      println("got {}", result.error());
  }

  // 例外を投げる例
  {
    // P2912R0
    try {
      q.push(5);
    } catch(const conqueue_error& e) {
      ...
    }

    // この提案
    try {
      q.push(5).or_else([](auto code) {
        throw conqueue_error(code);
      });
    } catch(const conqueue_error& e) {
      ...
    }
  }
}

この場合、APIは基本的に例外を投げないため、標準ライブラリの他の部分(特にコンテナ)と一貫性がなく、1つ目のAPI候補よりも劣っています。

また、この提案ではtry系関数について連想コンテナのtry_emplace()の設計を踏襲して、キューに値が挿入されない場合は渡された引数は変更されないようにすることを推奨しています。そしてその場合に、それに反してstd::expectedのエラー値で渡された値を返すAPIについても検討しており、その場合はユーザーに対する負担が大きくなると報告されています。

もう一種類、try_emplace()の設計を踏襲する方針のもと、1つ目のAPI候補においてtry系関数でstd::nothrowの指定を省略するAPIについても検討されています。

とはいえこの提案の結論としては、std::expectedベースのこれらのAPIがP2912R0で提案されているAPIと比較して明確に改善されているとは思えない、と報告しています。

P2922R0 Core Language Working Group "ready" Issues for the June, 2023 meeting

6月に行われたVarna会議でWDに適用されたコア言語に対するIssue報告の一覧。

P2925R0 inplace_vector - D0843R7 LEWG presentation

提案中のinplace_vectorの紹介スライド。

inplace_vectorP0843R8で提案されています。

ここでは、要素を追加するインターフェースについて解説されているほか、各インターフェースとstd::vectorとの比較ベンチマークの結果も記載されています。

P2926R0 std::simd types should be regular - P2892R0 LEWG presentation

P2892R0の解説スライド。

P2892R0の反対意見がいくつか紹介された上で、それに対する回答が肯定的立場から説明されています。

P2929R0 simd_invoke

std::simdで組み込み関数の使用を簡易にする呼び出しラッパ関数の提案。

C++26を目指して提案中のstd::simdは、任意の環境で効率的なデータ並列処理を簡易に書くことができるようにするためのデータ並列型です。そのため、std::simdは多くのプラットフォームで使用可能なSIMD演算等を意識して設計されています。

それによって、特定のハードウェアでは組み込み関数(intrinsic)を使用してより効率的なSIMD演算を呼び出したい場合が想定されます。そのためには、std::simdが保持する値を環境のSIMDレジスタに移して、それを用いて組み込み関数を実行し、その結果を再びstd::simdに保持させる、といったことをする必要があります。

このために、現在のstd::simdにはネイティブのSIMDデータ型とやり取りするためのコンストラクタと変換演算子を実装定義で追加することが許可されています

template<class T, class Abi = ...>
class simd {
  ...

  // ネイティブのSIMDデータ型からの変換コンストラクタ
  constexpr explicit simd(const implementation-defined& init);

  // ネイティブのSIMDデータ型への変換演算子
  constexpr explicit operator implementation-defined() const;

  ...
}

これを用いて、std::simdを組み込み関数呼び出しのために必要なネイティブのSIMDデータ型へ変換しで組み込み関数を呼び出すことができます。また、std::simdのコンストラクタはこの逆の変換を行えるため、その結果をstd::simd型に戻すこともそのまま行えます。

// _mm256_addsub_psは奇数要素を足し算し、偶数要素を引き算するintel AVXの組み込み関数
// std::simdには対応するマッピングがない
auto addsub(simd<float> a, simd<float> b) -> simd<float> {
  return static_cast<simd<float>>(_mm256_addsub_ps(static_cast<__m256>(a), static_cast<__m256>(b)));
}

ただし、この変換はstd::simdのサイズがネイティブのSIMDレジスタサイズに等しい場合にのみ正しく動作します。ネイティブのSIMD幅と異なる場合はそれを考慮しなければならず、ネイティブのSIMD幅を超え複数のレジスタにまたがるサイズの場合はこのような変換は利用できません。

その場合は、大きな幅のstd::simd値をネイティブのSIMDレジスタサイズにマッチするサイズで分割して順番に組み込み関数を呼び出して、その結果をstd::simdに格納するようなことをすることになります。

// AVXを使用する環境で、ネイティブSIMDレジスタサイズは256(floatx8)とする
// simd<float, 16>はネイティブSIMD幅の倍のサイズ
auto addsub(simd<float, 16> a, simd<float, 16> b) -> simd<float, 16> {

  // ネイティブのレジスタサイズごとに分割
  auto [lowA, highA] = simd_split<simd<float>>(a);
  auto [lowB, highB] = simd_split<simd<float>>(b);

  // 分割した部分ごとに組み込み関数を呼び結果を取得
  auto resultLow = simd<float>(_mm256_addsub_ps(static_cast<__m256>(lowA),
                                                static_cast<__m256>(lowB)));
  auto resultHigh = simd<float>(_mm256_addsub_ps(static_cast<__m256>(highA),
                                                 static_cast<__m256>(highB)));

  // 分割して処理した結果をsimd<float, 16>に再結合する
  return simd_concat(resultLow, resultHigh);
}

この例はネイティブのSIMDレジスタサイズの倍のstd::simd値に対してのみ動作し、異なるサイズの場合はそれに合わせて書き直す必要があるほか、ネイティブのSIMDレジスタサイズの整数倍とならないようなサイズの場合はそのための対応が必要となります。そのようなコードを書くことは難しくはなくほとんど典型的なコードとなると思われますが、それでも冗長であり、汎用的なソリューションでは技巧的なコードを書くことになります。

ここでやるべきことは、std::simd値の引数をネイティブのSIMDレジスタサイズで分割し、その部分ごとに組み込み関数を呼び出し、その結果をstd::simd値に再びまとめる、ということです。やるべきことのほとんどは典型的な処理であり、個別ユーザーそれぞれにそれを書かせる代わりに、この一連の手順を抽象化した一般的なメカニズムとして提供することができます。この提案ではそれを、simd_invoke()という関数テンプレートとして提案しています。

template<typename Fn, typename... Args>
auto simd_invoke(Fn&& fn, Args&&...);

fnはネイティブのSIMDレジスタサイズのstd::simd値を受け取って組み込み関数を呼びだす部分を指定する呼び出し可能なものです。例えば、先ほどの_mm256_addsub_psを呼び出す関数の場合は次のようになります

// AVXレジスタ1つ分の_mm256_addsub_psを処理する
inline auto native_addsub(simd<float> lhs, simd<float> rhs) {
  auto nativeLhs = static_cast<__m256>(lhs);
  auto nativeRhs = static_cast<__m256>(rhs);

  return simd<float>(_mm256_addsub_ps(nativeLhs, nativeRhs));
}

これを利用して、先ほどのようなネイティブのSIMDレジスタサイズを超える幅のstd::simd値で組み込み関数を呼びだすコードは次のように書き直せます

auto addsub(simd<float, 32> x, simd<float, 32> y) {
  return simd_invoke(native_addsub, x, y);
}

入力のstd::simd値をネイティブSIMD幅ごとに分割して組み込み関数を呼びだす部分と、その処理結果を再統合する部分はsimd_invoke()が勝手にやってくれています。それによって、ユーザーは単位SIMD幅のstd::simd値に対して何をするかだけを用意する(上記native_addsub()のように)だけでSIMD幅を気にせずにstd::simdを使って組み込み関数を呼びだすことができるようになっています。

先ほどの例の直接比較

現在 この提案
auto addsub(simd<float, 16> a, simd<float, 16> b) -> simd<float, 16> {

  auto [lowA, highA] = simd_split<simd<float>>(a);
  auto [lowB, highB] = simd_split<simd<float>>(b);

  auto resultLow = simd<float>(_mm256_addsub_ps(static_cast<__m256>(lowA),
                                                static_cast<__m256>(lowB)));
  auto resultHigh = simd<float>(_mm256_addsub_ps(static_cast<__m256>(highA),
                                                 static_cast<__m256>(highB)));

  return simd_concat(resultLow, resultHigh);
}
auto addsub(simd<float, 16> a, simd<float, 16> b) {
  auto do_native = [](simd<float> lhs, simd<float> rhs) {
      return simd<float>(_mm256_addsub_ps(
          static_cast<__m256>(lhs),
          static_cast<__m256>(rhs)));
  };

  return simd_invoke(do_native, x, y);
}

また、ネイティブのSIMDレジスタサイズの整数倍とならないような入力に対してより適切に組み込み命令を選択することもできます

auto addsub(simd<float, 19> x, simd<float, 19> y) {
  // AVXでfloat 19要素の場合、8, 8, 3 と分割して処理する

  // 入力のsimdのサイズごとに適切な組み込み関数を選択する
  auto do_native = 
    []<typename T, typename ABI>(basic_simd<T, ABI> lhs, basic_simd<T, ABI> rhs) {
      constexpr auto size = basic_simd<T, ABI>::size;
      if constexpr (size <= 4) {
        // 4要素(128bit)SIMD
        return simd<float, size>(_mm_addsub_ps(static_cast<__m128>(lhs),
                                               static_cast<__m128>(rhs)));
      } else {
        // 8要素(256bit)SIMD
        return simd<float, size>(_mm256_addsub_ps(static_cast<__m256>(lhs),
                                                  static_cast<__m256>(rhs)));
      }
  };

  return simd_invoke(do_native, x, y);
}

他にも、simd_invoke()はNTTP値で指定することでより狭いレジスタ幅で処理をするようにできたり、インデックスを同時に渡すsimd_invoke_indexed()が用意されていたりします

// より狭いレジスタ幅で呼び出しを行う例
auto addsub(simd<float, 32> x, simd<float, 32> y) {
  auto do_native = [](simd<float, 4> lhs, simd<float, 4> rhs) {
    return simd<float, 4>(_mm_addsub_ps(static_cast<__m128>(lhs), static_cast<__m128>(rhs)));
  };

  // NTTPでレジスタサイズを指定
  return simd_invoke<4>(do_native, x, y);
}


// 組み込み関数によって何か特殊なメモリストアを行う例
// _mm256_special_store_psは実際には存在しない
auto special_memory_store(simd<float, 32> x, float* ptr) {
  // ptrの領域に適切に出力していくために、オフセットが必要
  auto do_native = 
    [=]<typename T, typename ABI>(basic_simd<T, ABI> data, auto idx) {
      (_mm256_special_store_ps(ptr + idx, static_cast<__m256>(data)));
  };

  // 分割ごとの通し番号を同時に渡す
  simd_invoke_indexed(do_native, x);
}

P2930R0 Formatter specializations for the standard library

標準ライブラリのクラス型について、std::formatで直接文字列化できるようにする提案。

C++20でstd::formatが追加されると同時に基本型のためのフォーマット指定構文が導入され、C++23ではstd::printが追加されるとともに任意のrange型やstd::tupleなどがサポートされるようになりました。しかし、依然としてその他の多くの標準ライブラリのクラス型にはstd::formatのサポートがなく、効率的に基本型へ変換できない場合は文字列化するためには従来のストリーム出力(<<)に頼るしかありません。

この問題は以前から認識されており、P1636R2で同様の提案がなされLEWGでの設計合意を得ていましたが、著者の方と連絡が取れなくなったため議論は停止していました。この提案は、P1636R2をベースに、C++23におけるstd::formatterの改善なども盛り込みながら、より多くの標準ライブラリクラス型をフォーマット可能にすることを目指すものです。

この提案でフォーマットサポート追加を提案しているのは次のものです

  • std::bitset
  • std::byte
  • std::complex
  • std::error_category
  • std::error_code
  • std::error_condition
  • std::sub_match

これ以外のものはそれぞれ簡単には解決できない問題があるとしてここでは提案されていません(ただし、std::filesystem::pathは別の提案で提案されています)。

P2931R0 WG21 February 2023 Meeting Record of Discussion

2023年6月のWG21全体会議(Varna会議)の議事録

P2937R0 Freestanding: Remove strtok

フリースタンディングライブラリ機能として指定するものから、strtokを外す提案。

strtokP2338R4によってC++26のWDに対してフリースタンディング機能であると指定されました。

strtokは実際にはその動作のためにグローバルストレージを使用しており、スレッドセーフであることが保証されません。そのためP2338の初期のリビジョンには含まれていなかったのですが、C2X(Cの次期バージョン)に対してstrtokがフリースタンディングであると指定されたことで、P2338も最終的にはそれに追随しました。

しかしその後、C2XのCD2(comitee draft)の段階でstrtokのフリースタンディング指定は解除されたため、この提案はC++でも同様にstrtokをフリースタンディングでは無くすことを提案しています。

P2940R0 switch for Pattern Matching

パターンマッチングの構文として、switchを拡張する方向性についての提案。

現在のパターンマッチングの議論においては、swicthを再利用するのではなくinspectという新しいキーワードによってパターンマッチングのための領域を導入する方向性で議論が進んでいます。これは主に次の2つの理由によっています

  1. swicth構文が古いswitch文なのかパターンマッチ構文なのかを判別するのが難しい
  2. 古いswicthとパターンマッチでは機能や意味論が異なるためそれを教育する際の懸念がある

この提案は、どちらの問題も解決可能であるとして、switch構文を拡張する形でパターンマッチングを導入しようとするものです。

1つ目の問題の解決として、この提案ではswitchキーワード直後の値を指定する部分を()ではなく[]を使用することで区別することを提案しています。

// 現在のswitch文
switch (a) {
  ...
}

// この提案によるパターンマッチ構文、基本形
switch [a] {
  ...
};

// この提案によるパターンマッチ構文、aとbの両方でマッチングする
switch [a, b] {
  ...
};

これによって導入されるswitchは式となり、通常のswitch同様の構文(casedefaultラベルによって)でマッチングを記述でき、また現在提案中のパターンマッチング構文と同様の構文(=>など)を用いてマッチングし値を返すことができます(casedefaultラベルによるマッチングの場合は値を返すことができない)。ただしこのために、[]によるswitchでは末尾に;が必要とされます。

この提案のswitch式はフォールスルーに関してswitch文と異なるデフォルトとすることを提案しており、明示的にOR条件を記述しない限りフォールスルーしないようにしています。

switch文 switch式
auto some_value = 2;

switch (some_value) {
  case 1:   
    ... 
    break;
  case 2:   
    ... 
    // break忘れ、フォールスルーしてしまう
  case 3: 
    ... 
    break;
  ...
}
auto some_value = 2;

switch [some_value] {
  case 1:   
    ... 
    break;
  case 2:   
    ... 
    // break忘れ、フォールスルーしない
  case 3: 
    ... 
    break;
  ...
};

このフォールスルーに関して以外は、新しいswitch式は現在のswitch文の機能を包含しています。これによって、現在のswitch文はパターンマッチのより制限された場合と見ることができるようになり、教育において2つを区別する必要は無くなります(2つ目の問題を解決します)。

提案より、その他の例

auto some_value = string("hi");

// 文字列のマッチング
switch [some_value] {
  case "hi":   // handle "hi"
  case "bye":  // handle "bye" 
  default:     // handle all else
};

auto some_value2 = Point(12, 13);

// 現在のパターンマッチング構文によるマッチング
switch [some_value2] {
  case [0, 0]: // handle point at origin 
  case [0, _]: // handle x at origin 
  case [_, 0]: // handle y at origin 
  ...
};

auto some_value3 = true;

// 値を返すswitch式、戻り値型は推論され、const char*になる
auto result = switch [some_value3] {
  true => "yes";
  false => "no";
};

// 値を返すswitch式、戻り値型はstd::string
auto result = switch [some_value] -> std::string {
  true => "yes";
  false => "no";
};

enum class Op { Add, Sub, Mul, Div };

// caseラベルと=>を混合させることで、値を返さないパターンを簡易に記述できる
Op parseOp(Parser& parser) {
  return switch [parser.consumeToken()] {
    '+' => Op::Add;
    '-' => Op::Sub;
    '*' => Op::Mul;
    '/' => Op::Div;
    case [[noreturn]] let token: {
      std::cerr << "Unexpected: " << token;
      std::terminate();
    }
  };
}

これらの例が示すように、パターンマッチングサポートのためにswitchを拡張することは可能でありかつ自然な拡張となり、導入キーワードやcase記述などを再利用することで言語の変更を抑えることができ、現在のswitchの自然な拡張となることで初心者が学ぶべきことも減少します。

P2941R0 Identifiers for Pattern Matching

パターンマッチング中で使用される識別子を区別可能なパターンマッチング構文の提案。

現在議論中のパターンマッチング構文においては、マッチングを記述するところに識別子(なんらかの名前)を使用してマッチングを行うことができます。この時問題となるのは、そのように使用されている識別子が型なのか変数なのかはたまた新しく導入されたものなのかわからない場合があるという点です。

static constexpr int zero = 0, one = 1;
int v = 42;

inspect (v) {
  zero => { std::cout << zero; }  // zeroはどれ?
};

他言語におけるパターンマッチングを見てみてもこの問題に正解はないようです。しかし、次の2つの理由により、C++ではこのことが深刻な問題となる可能性があります

  1. C++には変数導入のキーワードがない
    • 例えば、C#varやswiftのletのようなもの
  2. C++では変数と型が同じ名前を持つことができる

このことは現在ではあまり問題とはなりませんが、パターンマッチング構文の柔軟さと合わさると複雑な問題を引き起こします。

パターンマッチング構文内でxという識別子が表れているとき、それは次の3つのいずれかであるはずです

  • xはパターンマッチング構文内で新しく導入された変数もしくはバインディング(マッチング対象の値のサブオブジェクトへの参照)
  • xは比較対象となる外部の変数
  • xは型

これに対して、現在のパターンマッチング提案は次のようになっています

  • P1371R3
    • デフォルトではバインディングを作成する
    • 外部の変数を比較対象とする時は、caseを使用する
    • 型名は<type>のように記述する
  • P2688(P1371の将来バージョン)
    • バインディングの作成はletで行う
    • 外部変数は単なる式として扱われ、識別子名のデフォルトは外部変数となる
    • 型についてはそのまま
  • P2392(is asを使用する提案)
    • 識別子がis asの左側にある場合、バインディングが作成される
    • 外部変数はisの右側にくる
    • 型もis asの右側にくる

ただし、どちらのアプローチにおいても式と型名を同じ構文で指定できてしまうため、型のマッチング記述に問題があります。

P1371R3のAlternative Pattern(パターンマッチング対象の値を直接マッチングする代わりに、そこから何かしら値を取り出してそれによってマッチングする。std::varintのマッチングなど)

void sample1(auto some_value) {
  constexpr auto size = 13;

  inspect (some_value) {
    <size> => // マッチングには定数13が使用される
  };
}

void sample2(auto some_value) {
  using size = int;

  inspect (some_value) {
    <size> => // マッチングにはint型が使用される
  };
}

一応この場合<>の中に指定する式は定数式である必要はあります。

P2392の場合

void sample1(auto some_value) {
  constexpr auto size = 13;

  inspect (some_value) {
    is size => // some_valueが13に等しい時にマッチング
  };
}

void sample2(auto some_value) {
  using size = int;

  inspect (some_value) {
    is size => // some_valueの型がintの時にマッチング
  };
}

同じ構文によって異なるマッチングができてしまうことによって、ある時点で識別子の意味が変化するとパターンマッチングの意味もまた静かに変化してしまいそれに気づけない可能性があります。より大規模な関数ではこのような状況が容易に発生することが予想されます。

また逆に、同じ名前で別の種類のエンティティがパターンマッチングのスコープに導入されている場合も考えられます。マッチングの記述方法によっては問題とならない場合もありますが、上記のように式も型も書ける構文でそのような識別子を使用してしまうとコンパイルエラーとなります。

いずれにせよ、この型マッチング記述の曖昧さの問題によってパターンマッチングはプログラマの意図通りに動作せず、さらに悪い場合は動作しているように見えてしていないという問題に頭を悩ませることになる未来があり得ます。

この問題の根本的原因はパターンマッチングによって導入されたわけではありませんが、今日のコードで問題となる場合はほとんどありません。なぜなら、識別子名が型であるか変数であるかが競合する場所というのは言語機能としては存在していないからです。NTTPと型を受け取る関数テンプレートのようにそのような場所を作り出すことはできますが、これを使用する場合は何らかのライブラリAPIとして使用することになり、それは言語機能ではありません。

パターンマッチングの場合は言語機能で識別子名の種類が競合するコンテキストが表れてしまっています。現在のC++の式は、同じ名前の型や変数の有無で意味が切り替わることはなく、コンパイルエラーとなるはずです。パターンマッチングは現在の言語の振る舞いより悪くなることはなくむしろ良い振る舞いをしなければなりません。そうならない場合、新しい罠を生み出してしまい、それを回避するためのガイドラインを普及させることになります。

また、パターンマッチで外部変数を使用する場合の構文として、マッチングとその結果あるいはサブオブジェクトの代入を同時に行うものが考えられます。例えば、構造化束縛を拡張して既存の変数に代入するようなもの(std::tie()のような)が考えられます

some_t a,b;

// 構造化束縛構文を拡張して既存の変数に代入するようにしたもの
[a,b] = something;

これはパターンマッチングと関係なく有用であり導入可能である可能性がありますが、同様にパターンマッチングにおいても有用である可能性があります。その場合、パターンマッチングで現れる識別子の種別として代入に使用される既存変数名が追加されます。

さらに、パターンマッチングにおいてバインディングを作成する場合、その不変性を制御できることが望ましいことは明らかです。現在のパターンマッチング提案ではこれはマッチング対象のオブジェクトのconst性とその伝播の通常のルールによって制御されますが、それは次のような理由から完璧ではありません

  • パターンマッチングの全ての分岐パスで同じ使い方をするわけではなく、分岐によってconstでよかったり変更が必要だったり変化しうる。マッチング全体で一律にconstであるかそうでないかを指定させることは、全体として最適なconst性を表現できなくなる
  • ポインタやビューなど参照セマンティクスを持つものの場合、そのconst性は参照先のconstを意味しない。現在のC++でもそのようなオブジェクトは広く使用されており、マッチングにおけるバインディングそれぞれで個別にconst性を制御しない方法は実用的ではない

これらの検討の結果、パターンマッチングにおいて現れる識別子の種類としては次の5つが考慮されることになります

  • xはパターンマッチング構文内で新しく導入された、観測専用(constな)変数もしくはバインディング
  • xはパターンマッチング構文内で新しく導入された、変更可能な変数もしくはバインディング
  • xは比較対象となる外部の変数
  • xはマッチング結果の代入対象となる外部の変数
  • xは型

パターンマッチング構文においては、これらのパターンを区別しながら識別子を使用できることが望ましいです。

この提案では、その解決のための構文として次のようなものを提案しています。

識別子xがあったとき

ただし、値マッチングに常に==を要求するのは冗長であり見づらくなる可能性があるほか、単一の識別子がいつも型名とみなされるのも混乱を招く可能性があります。そこで、次のような制限を加えています

  • 最後のパターンの場合、==を省略できる
  • 最後のパターンは常に型名マッチングではない
    • 最後のパターンに現れる識別子は型名とみなされない

最後のパターンというのは、パターンマッチングにおける一つのパターンのマッチングにおいて、パターンの外側から内側へ再帰的にマッチングされる際の最も内側のパターン(つまり一番最後にマッチングがチェックされるパターン)のことです。この制限によって、パターンマッチングに現れる単一の識別子のデフォルトは==xとなり、型マッチングはx __のように記述することになります。

struct size{...}; // #1
size size;        // #2

inspect (...) {
  size __          => ...  // 型マッチング(sizeは型名#1)
  size             => ...  // 値のマッチング(sizeは変数名#2)
  [size, size]     => ...  // これも値マッチング(#2)
  [==size, ==size] => ...  // 上と同じ意味
   
  size size     => ...     // size型(#1)のsize値(#2)とのマッチング
  size (==size) => ...     // 上と同じ意味
};

==が必要とされる例

struct Size{...};
Size size;  // #1

inspect (...) {
//  size [w=, h=] => ...      // error、sizeは型名ではない
//  Size size[w=, h=] => ...  // error、sizeは型名ではない(最後のパターンではないため変数名とみなされていない)
    ==size[w=, h=] => ...     // ok、::size(#1)とのマッチング、そのサブオブジェクトをw, hにバインディング
};

この場合、最後のパターンは[]の中のw=, h=(の両方)になるため、それ以外のところで==を省略するとその識別子は型名だとみなされ、それによって最初の2つの例はエラーになります。

同様に、型の一致は最後のパターンに来ないようにする必要があります。

struct Size{...};
Size size;

struct Rectangle { int x; int y; Size size; };
Rectangle rect = ...;

inspect (rect) {
//  [__, __, size __] => ...    // error、sizeは型名ではない
    [__, __, ==size __] => ...  // ok、明示的な値マッチング
    [__, __, size] => ...       // ok、最後のパターンのため値マッチングとみなされる

//  [__, __, Size] => ...           // error Sizeは型名(最後のパターンのためSizeは変数名とみなされている)
    [__, __, Size __] => ...        // ok、最後のパターンではないためSizeは型名とみなされる
    [__, __, Size [w=, h=]] => ...  // ok、最後のパターンではないためSizeは型名とみなされる
};

その他の例

int x = 5;

inspect (...){
    x     =>        // ::x(変数)とマッチング
    ==x   =>        // 上と同じ意味(明示的)
    x=    =>        // バインディングの導入、::xを隠蔽
    x=x   =>        // バインディングの導入 + ::xとのマッチング
  x=(==x) =>        // 上と同じ意味(明示的)
  int x   =>        // int型かつ::xとマッチング
  px= (int x=x) =>  // int型かつ::xとマッチング、マッチングした場合その値はint型の値xにバインドされ(値は::xと一致する)、多態的型にバインドされたpxにさらにバインドする
  int &x=x      =>  // int型かつ::xとマッチング、マッチングした場合その値はint型の参照xにバインドされる(値は::xと一致する)
  ...
};

P2944R0 Comparisons for reference_wrapper

reference_wrapperに比較演算子を追加する提案。

reference_wrapper<T>には比較演算子が定義されていませんが、reference_wrapper<T>T&に暗黙変換が可能であり、比較を行う際はADLにおいてT名前空間が探索候補になることから、Tに定義されている比較演算子を使うことができます。ただし、これには制限もあり比較演算子の定義のされ方などによって比較が可能かが変化します。

reference_wrapper<T>Tの種類と演算子の定義のされ方、比較の仕方によって比較が可能かは次の表のようになります。

T ref(t) == ref(t) ref(t) == t
組み込み型
クラス(テンプレート)で==はメンバで定義
クラスで==は非メンバで定義
クラステンプレートで==はhidden friendで定義
クラステンプレートで==は非メンバ関数テンプレートで定義
std::string_view

reference_wrapper<T>を介してTに定義された比較演算子の上で比較を行いたい場合、そのままだとこの表にあるようにできたりできなかったりします。

reference_wrapper<T>を介してTの比較演算子によって比較したいユースケースがあり、それはreference_wrapperを使用する理由と関連しています。

// valueと等しいかをチェックする述語生成ラッパ
inline constexpr auto equals = [](auto&& value) {
  return [value=FWD(value)](auto&& e){ return value == e; };
};

これはvalueを値でキャプチャするためvalueがコピーの重い型だと非効率になりますが、ここで適応的に参照キャプチャしようとすると実装が複雑化します。この時、このラッパの入力にreference_wrapperを用いることによって、使用者の意思で明示的に参照キャプチャするかどうかを切り替えることができます。

if (std::ranges::any_of(v, equals(0))) {
  // ...
}

if (std::ranges::any_of(v, equals(std::ref(target)))) {
  // ...
}

これは先ほどの表のようにtargetの型と比較演算子の定義のされ方によって動作したりしなかったりします。この提案の目的は、これが常に動作するようにすることにあります。

この提案による解決は単純で、reference_wrapper<T>に対して直接的に比較演算子を定義することで、ADLに頼らずにTの比較演算子を利用できるようにします。

namespace std {
  template<class T>
  class reference_wrapper {
  public:
    ...

    // 追加される比較演算子
    friend constexpr bool operator==(reference_wrapper, reference_wrapper);
    friend constexpr synth-three-way-result<T> operator<=>(reference_wrapper, reference_wrapper);
  };
}

これらの比較演算子は、reference_wrapper<T>が参照しているTの値で直接対応する比較を行うことによって比較を実行するため、現在のreference_wrapper<T>のように比較できたりできなかったりすることは無くなります。

また、比較演算子の導出によってTでそれが可能ならば全ての種類の比較が可能となります。

P2945R0 Additional format specifiers for time_point

<chrono>time_point型のフォーマット指定を追加する提案。

C++20で<chrono>ライブラリは拡張され、カレンダー表現やタイムゾーンのサポートなどと共にstd::format()によるフォーマット対応もなされました。それによってtime_point型の値(時刻の値)はそのままstd::format()で文字列化することができ、またフォーマット指定を添えることで文字列化のされ方を制御することができます。

#include <chrono>
#include <format>
#include <iostream>

using namespace std::chrono;

int main() {
  // system_clockのtime_point値(現在時刻)の取得
  auto now = system_clock::now();

  // time_point値のフォーマット出力
  std::cout << std::format("{}\n", now);
  std::cout << std::format("{:%Y年%m月%d日 %H時%M分%S秒}\n", now);
  std::cout << std::format("{:%D %T}\n", now);
  std::cout << std::format("{:%F %T %Z %z (%Ez)}\n", now);
}

出力例

2023-09-05 12:28:41.739583722
2023年09月05日 12時28分41.739583722秒
09/05/23 12:28:41.739583722
2023-09-05 12:28:41.739583722 UTC +0000 (+00:00)

この提案は、現在のchronoのフォーマット指定に欠けているフォーマット方法を追加しようとするものです。

この提案が問題視しているのは、上記例にも表れているように秒の単位以下の出力精度を制御する方法が提供されていないことです。%Sは秒単位かつミリ秒未満は10進小数で出力しますが、この時ミリ秒未満をどこまで出力するのかあるいは出力しないということを指定する方法が提供されていません。%T%H:%M:%Sの省略指定であるため同様の問題があります。これによって、time_point値を秒単位で出力、ミリ秒単位(小数点以下3桁)だけ出力、などの指定は少し遠回りをする必要があります。

#include <chrono>
#include <format>
#include <iostream>

using namespace std::chrono;

int main() {
  // system_clockのtime_point値(現在時刻)の取得
  auto now = system_clock::now();

  // 秒単位まで出力
  std::cout << std::format("{:%H:%M:%S}\n", time_point_cast<seconds>(now));
  // ミリ秒単位(小数点以下3桁)まで出力
  std::cout << std::format("{:%H:%M:%S}\n", time_point_cast<milliseconds>(now));
  // マイクロ秒単位(小数点以下6桁)まで出力
  std::cout << std::format("{:%H:%M:%S}\n", time_point_cast<microseconds>(now));
}

出力例

12:42:14
12:42:14.700
12:42:14.700612

この提案では、これに対して%.nSのようなフォーマット指定を提案しており、nで秒単位の出力精度(桁数)を指定します。

#include <chrono>
#include <format>
#include <iostream>

using namespace std::chrono;

int main() {
  auto now = system_clock::now();

  // 秒単位まで出力
  std::cout << std::format("{:%H:%M:%.0S}\n", now);
  // ミリ秒単位(小数点以下3桁)まで出力
  std::cout << std::format("{:%H:%M:%.3S}\n", now);
  // マイクロ秒単位(小数点以下6桁)まで出力
  std::cout << std::format("{:%H:%M:%.6S}\n", now);
}

同時に、%Tに対しても%.nTのように同様に秒単位の出力桁数を指定するフォーマット指定を提案しています。

#include <chrono>
#include <format>
#include <iostream>

using namespace std::chrono;

int main() {
  auto now = system_clock::now();

  // 秒単位まで出力
  std::cout << std::format("{:%.0T}\n", now);
  // ミリ秒単位(小数点以下3桁)まで出力
  std::cout << std::format("{:%.3T}\n", now);
  // マイクロ秒単位(小数点以下6桁)まで出力
  std::cout << std::format("{:%.6T}\n", now);
}

このフォーマット指定の利点は以下の3点が主張されています

  1. これがない場合、time_point値の精度を変換するための冗長なキャスト(time_point_cast)を記述しなければならず、不便
  2. 提案しているフォーマット指定では、実際のtime_point値の精度を気にする必要がないことを保証している
    • time_point値の精度によらず、一貫して同じ出力を得られる
  3. std::optionalrangeなどの要素となっている時のフォーマット時にフォーマット指定を再利用できる
    • 単に入れ子にすれば良い
    • time_point_castで回避する場合、追加の変換(.transform()views::transform)が必要になり、これは場合によって安全性の問題を引き起こす

また、多言語におけるこのtime_point値のフォーマットに対応するフォーマット指定を見てみると、次のような明確なコンセンサスが確認できます

  1. %Sは秒数を00 ~ 59の2桁の整数としてフォーマットする
  2. %sは秒数をエポックからの整数値としてフォーマットする

現在のC++は残念ながらどちらも満たしていません(%s指定は存在しない)。そこでこの提案では、%s指定をこの意味論に沿った効果を持つフォーマット指定として追加することも提案しています。

#include <chrono>
#include <format>
#include <iostream>

using namespace std::chrono;

int main() {
  auto now = system_clock::now();

  // エポックからの経過秒数(or経過単位時間カウント値)の出力

  // 現在
  std::cout << std::format("{:%Q}\n", now.time_since_epoch());  // 最大精度
  std::cout << std::format("{:%Q}\n", time_point_cast<seconds>(now).time_since_epoch());  // 秒単位

  // この提案
  std::cout << std::format("{:%s}\n", now);    // 最大精度
  std::cout << std::format("{:%.0s}\n", now);  // 秒単位
}

出力例

1693915931163502515
1693915931
1693915931163502515
1693915931

まとめると変更は次のようになります

  • %sを追加して、time_point値をその精度単位でエポックからのカウント数を整数値でフォーマットする
  • %S %sの両方で、プリフィックスとして出力精度(秒未満の桁数)を受け付けるようにする
    • %.nS %.ns
  • %T%Sと同様に拡張
  • %fを追加して、%0S%.nf%.nSと同じ意味になるようにする
  • %Q %qtime_pointでも機能するようにする
    • 現在はdurationでのみ有効

この提案ではさらに踏み込んで、%Sの出力を他言語と一貫性のあるものに修正することを提案しています。それが受け入れられる場合、%S %sの両方で%.nS %.nsの指定の代わりに小数点以下の部分を表すフォーマット指定として%fを追加することを提案しています。

#include <chrono>
#include <format>
#include <iostream>

using namespace std::chrono;

int main() {
  auto now = system_clock::now();

  // `%S`の出力修正が受け入れられる場合の提案

  // 秒単位まで出力
  std::cout << std::format("{:%H:%M:%S}\n", now);
  // ミリ秒単位(小数点以下3桁)まで出力
  std::cout << std::format("{:%H:%M:%S%.3f}\n", now);
  // マイクロ秒単位(小数点以下6桁)まで出力
  std::cout << std::format("{:%H:%M:%S%.6f}\n", now);

  // 秒単位まで出力
  std::cout << std::format("{:%T%}\n", now);
  // ミリ秒単位(小数点以下3桁)まで出力
  std::cout << std::format("{:%T%.3f}\n", now);
  // マイクロ秒単位(小数点以下6桁)まで出力
  std::cout << std::format("{:%T%.6f}\n", now);

  // エポックからの経過秒数(or経過単位時間カウント値)の出力
  std::cout << std::format("{:%s}\n", now);    // 最大精度
  std::cout << std::format("{:%.0s}\n", now);  // 秒単位
}

こちらの提案の場合変更は次のようになります

  • %Sは秒数を00 ~ 59の2桁の整数としてフォーマットするように変更
    • %H %Mと同様になる
  • %sを追加して、time_point値をエポックからの秒数を表す整数値としてフォーマットする
  • %fを追加して、time_point値の出力時にその最大精度でフォーマットする
    • %.nfのようにして、精度を指定することもできる
  • %Q %qtime_pointでも機能するようにする
    • 現在はdurationでのみ有効

こちらの提案は出力結果に関して破壊的変更となり、現在の%S%S%.f%T%T%.fが対応します。

%Sの修正を含む提案を提案2、%Sを現状維持する提案を提案1とすると、2023年7月09日15時40分34秒(+0.295314673秒、UTC)の時刻を保持するtime_pointtpに対して同じ出力を得るためのフォーマット指定には次のような差が生じます

提案2 期待する出力 提案1
std::format("{:%s%9f}", tp) 1688830834295314673 std::format("{:%s}", tp)
std::format("{:%s}", tp) 1688830834 std::format("{:%.0s}", tp)
std::format("{:%H:%M:%S}", tp)
std::format("{:%T}", tp)
15:40:34 std::format("{:%H:%M:%.0S}", tp)
std::format("{:%.0T}", tp)
std::format("{:%H:%M:%S%.3f}", tp)
std::format("{:%T.%3f}", tp)
15:40:34.295 std::format("{:%H:%M:%.3S}", tp)
std::format("{:%.3T}", tp)
std::format("{:%H:%M:%S%.6f}", tp)
std::format("{:%T.%6f}", tp)
15:40:34.295314 std::format("{:%H:%M:%.6S}", tp)
std::format("{:%.6T}", tp)

この提案では提案2を押していますが、破壊的変更となるため受け入れられない場合のために提案1を用意しています。

LEWGの最初のレビューでは、%Sの修正を受け入れることに弱いコンセンサスがあり、その場合実装はC++20/23モードまで遡って修正を適用することにコンセンサスがありました。

P2946R0 A flexible solution to the problems of noexcept

noexceptよりも弱い無例外指定である[[throws_nothing]]の提案。

関数に対するnoexcept指定はその関数が例外を投げないことを指定し、例外が投げられた場合はプログラムを終了させます。これは、std::vector等のコンテナの挿入操作などにおいて強い例外保証とムーブコンストラクタの効率性を両立させるために導入されました。ムーブはムーブ元の状態を変更してしまうため、ムーブコンストラクタが例外を投げるとムーブ元の状態を元に戻せなくなり、強い例外保証を守ることができなくなります。そのため、ムーブコンストラクタが例外を投げないことをコンパイル時に調べてその場合にのみムーブによって要素を構築するために、コンストラクタ(関数)が例外を投げないことの表明と関数が例外を投げないことのチェックをnoexcept指定子と演算子によって行えるようにしています。

その後これを標準ライブラリの他の関数等にも適用する際のルールとしてLakos Ruleが整備されました。これは関数が事前条件を持つかによって2種類に分類してその上でnoexceptを指定すべきかを決定するシンプルなルールです。簡単には次のようなものです

  • 関数は事前条件を持たず(広い契約を持つ)、例外を投げない場合、関数をnoexcept指定する
  • 関数は事前条件を持っている(狭い契約を持つ)か、事前条件を満たして呼び出された場合にも例外を投げる可能性がある場合、関数はnoexcept指定するべきではない

単純なvector実装における例

template <class T, class A>
class vector {
  // ...
  constexpr size_type size() const noexcept;  // 広い契約, 例外を投げない
  constexpr reference at(size_type);          // 広い契約, 例外を投げうる
  constexpr reference operator[](size_type);  // 狭い契約, 例外を投げない
  constexpr reference front();                // 狭い契約, 例外を投げない
};

Lakos Ruleは効果的であり、理論的にも実践的にも強い裏付けを持っています(下位互換性のある拡張を可能にしたり、より広いインターフェースに適合することができるなど)が、このルールに従わない理由として次の2つのようなものが明らかになっています

  1. 多くの状況ではnoexcept関数の呼び出しに伴って生成されるコードが少なくなる。そのため、コード生成を改善するためにnoexceptをとにかく使用したい需要がある
  2. C++の規格書において、「Throws: nothing」と指定されているものとnoexceptの区別が明確ではない

また、その他の動機として、noexcept関数が例外を投げた場合に即終了しないでほしい場合があり、単なる事前条件のチェックのために例外を用いたいなどの需要もあるようです。

この2つの需要に対しては、関数にとにかくnoexceptを付けるという動機が生まれてきます(つまり、Lakos Ruleを無視する)。しかし、ある時点で関数にnoexceptを付加してしまうと将来のバージョンで下位互換性を壊すことなく削除することができなくなるため(関数にnoexceptが付加されている場合、その関数を例外を投げる可能性のある値を受け入れるように拡張できなくなる)、どうしてもコンパイル時にその関数のnoexcept性が重要になる説得力のある理由がない限りはLakos Ruleに違反することは望ましくありません。

このような現状に対してこの提案は、現在のnoexcept及びLakos Ruleを維持しながらも、これらのような現在のnoexceptでは満たすことのできないいくつかの需要に対処するために、[[throws_nothing]]という無例外を表明する属性を追加しようとするものです。

この提案の目的は現在のnoexceptでは対応しきれない次ような需要に対処することです

  • より小さなコードサイズ
    • 生成されるコードサイズの削減の恩恵を最も受けるのはメモリに制約のある環境
    • 組み込み環境において例外が使用しやすくなる可能性がある
  • 正常なシャットダウン
    • エラー(例外)が発生したからといって即終了されることは受け入れられない環境がある
    • 終了の前にリソースを解放したりログをとったりデータを保存したりする必要がある
  • 終了しない例外
    • 決して終了しないプログラムが存在する
    • テストドライバーはその一例だが、関数の事前条件のテストにおいては意図的な失敗も含めてテストをするが、ここで例外が投げられプログラムが終了する場合テストは不可能になる

[[throws_nothing]]は関数宣言に指定する属性であり、その関数の事前条件が満たされていれば例外を投げないことを指定します。しかしこの指定はコンパイル時に検出する方法がなく、noexcept演算子はこれを無視し関数型にも現れません。

[[throws_nothing]] void g1(int);
static_assert(noexcept(g1(0)) == false);

[[throws_nothing]] void g2(int) noexcept;
static_assert(noexcept(g2(0)) == true);

[[throws_nothing]] void g3(int) noexcept(false);
static_assert(noexcept(g3(0)) == false);

void g4(int);
static_assert(std::is_same_v<decltype(g1), decltype(g4)>);

[[throws_nothing]]noexcept演算子等で検出できないため、それによってコンパイル時に分岐することもできなくなり、これによって関数の動作が以前に有効だった入力に対して変更されない限りは、後からこの指定を削除することができます。

[[throws_nothing]]指定された関数が例外を投げることで終了する場合にプログラムが終了するか(あるいは例外が通常のように伝播するか)は実装定義とされます。推奨される実装としてはその動作をユーザーが指定できることが望ましいとしています。これによって、例外発生時に終了することを選択した実装ではnoexcept同様にコードサイズ削減効果が期待でき、終了しない実装では正常なシャットダウンや事前条件テストなどを例外を用いて行うことができるようになります。

また、標準ライブラリ実装においては現在のnoexcept同様に標準で指定されていなくても[[throws_nothing]]を指定することを許可します。これによって、「Throws: nothing」と指定されている関数に対して[[throws_nothing]]属性を指定することができ、現在のnoexceptと同等の効用を得ながらもより柔軟な運用が可能となります。前述のように、後から削除する場合でも問題なく行うことができます。

noexcept及び何も指定しない場合との効用の比較表

指定なし noexcept [[throws_nothing]]
関数の自己ドキュメント no yes yes
コード生成へのヒント no yes 終了する場合yes
予期しない例外時の終了 no yes 終了する場合yes
広い契約に最適 yes yes yes
狭い契約に最適 yes no yes
正常なシャットダウンに対応 yes no 無視する場合yes
ログをとって継続に対応 yes no 無視する場合yes
例外による防衛的テストに対応 yes no 無視する場合yes
コンパイル時の検出と分岐のサポート no yes no

この表を見るとわかるように、noexcept[[throws_nothing]]はどちらがどちらよりも優れていて対立するものではなく、それぞれ異なる目的を果たしており、Lakos Ruleを維持しながらより広い無例外指定のユースケースを満たそうとするものです。

P2947R0 Contracts must avoid disclosing sensitive information

提案中のcontract_violationクラスのメンバ関数comment(), location()が意味のない応答を返すようにオプトアウトできることを必須とする提案。

C++26契約プログラミングに向けて議論が進んでおり、現在の仕様では違反ハンドラ(契約違反が起きたときに呼ばれる関数)がデフォルトとなりビルドモードの概念が違反ハンドラのカスタマイズで置き換えられています。

違反ハンドラはその引数で起きた契約違反についての情報をcontract_violationというクラスのオブジェクトとして受け取ります。contract_violationクラスについてはP2811R7で議論中ですが、このクラスには契約違反を起こした契約条件式をテキストで取得する.comment()と、契約違反が起きたソースコード上での場所を取得するlocation()という2つの関数が用意されています。P2811R7では、これらの関数が意味のある応答をするのはオプショナルであり、空の文字列やstd::source_locationオブジェクトを返すことが許可されています。

これはあくまで許可でありいつも意味のある応答をする実装が前提となっていますが、この提案は意味のある応答をしないようにする(comment(), location()が空の応答を返せるようにする)オプションを必ず提供しなければならないように規定しようとするものです。

この提案の目的はコンパイル後のバイナリファイルに不要な情報が含まれることを回避することにあります。

contract_violationクラスのメンバ関数comment(), location()ソースコードに関する情報を提供し、契約アノテーションは実行時に評価されるまでどれが破られるかは分かりません。すなわち、これらの関数を使用すると契約アノテーションに関するソースコードの情報がコンパイル後のバイナリに何かしらの形で書き込まれることになります。現在の契約プログラミング仕様ではビルドモードの概念が削除されているためリリースビルドにおいてもこれらの情報がバイナリに記録されることを避けることができません。これは、リバースエンジニアリングを行う人に対して大きな助けとなる情報を与えることになってしまいます。

製品のセキュリティに大きな労力を費やしているベンダーでは、この理由により契約プログラミングの使用が妨げられる可能性があります。提案では、筆者の方々の経験として次のような例が報告されています

  1. A社では、本番コードからログメッセージを削除することを要求された結果、コンパイル時のテキスト難読化機能とそれに付随するログテキストデコーダユーティリティを導入した
  2. B社では、コンパイル時に全てのログメッセージをIDに変換し、出荷バイナリと共にログメッセージをIDにマップするマップファイルを作成した。バイナリがIDのみのログを生成している間、マップファイルは社内にあり続ける(出荷されない)
  3. C社では、出荷バイナリにプレーンテキストでシンボル名が含まれることを避けるためにRTTIを無効化した。これはペネトレーションテストの結果を受けての措置。

これらの例は、機密性の高いテキストがバイナリに埋め込まれることを回避し知的財産を保護することを最優先事項として他の安全性やセキュリティへの配慮と同等以上のものとするために、企業が多大な労力を払い、また制限を受け入れる姿勢を示しています。

このような理由から、この提案ではcontract_violationクラスのメンバ関数comment(), location()が何の情報も返さないようにするオプションを提供しなければならないことを規定することで、契約プログラミングを使用するとソースコード上の情報がバイナリファイルに含まれてしまうことを回避できるようにしようとしています。

P2949R0 Slides for P2861R0: Narrow Contracts and noexcept are Inherently Incompatable

P2861R0の紹介スライド。

P2861R0は標準ライブラリにおけるnoexcept適用基準であるLakos Ruleとそこから得られる効用について説明したものです。このスライドはそれをWG21のメンバに紹介するものです。

P2950R0 Slides for P2836R1: std::basic_const_iterator should follow its underlying type's convertibility

P2836R1の紹介スライド。

P2836R1は、ある範囲について、そのイテレータstd::constant_iteratorに通して得られる型とstd::ranges::cbegin()の返すイテレータ型が異なり相互変換不可能な場合があるとして、それを正そうとするものです。詳細は上の方にあるのでそちらを参照してください。

このスライドは、LEWGのメンバにむけて提案の内容や問題点、解決方法などを解説するものです。

P2951R0 Shadowing is good for safety

P2951R1 Shadowing is good for safety

変数のシャドウィングを活用した、安全性向上のための言語機能の提案。

この提案では主に安全性の向上を目的として、変数のシャドウィングの制限を解除することで安全性やコードのシンプルさに資する機能を導入しようとするもので、4つの提案が行われています。

1つ目は、voidで既存変数を再宣言することで以降そのスコープでのその変数の利用を禁止するものです。別の言い方をすると、シャドウィングを明示的に行う構文を導入しようとするものです。

現在 提案1
#include <string>
#include <vector>

using namespace std;

// シャドウィングのためのタグ型
struct dename{};

int main() {
  vector<string> vs{"1", "2", "3"};
  for (auto &s : vs) {
    dename vs;
    // 以降、vsをvectorとして使用できない
  }
}
#include <string>
#include <vector>

using namespace std;

int main()
{
  vector<string> vs{"1", "2", "3"};
  for (auto &s : vs) {
    void vs;  // vsを明示的にシャドウィング
    // もしくは
    auto vs;
    // 以降、vsを使用できない
  }
}

これは例にあるようにdenameのようなクラスを標準化することによっても達成できるため、言語機能として提案しているものが受け入れられない場合はライブラリ機能でも良いとされています。ライブラリ機能ではなく言語機能である事のメリットは、エラーメッセージを改善できることにあります。

シャドウィング方法 エラーメッセージ例
void vs; error: 'vs' was not declared in this scope
dename vs; error: 'struct dename' has no member named '*****'

2つ目は、シャドウィング対象の変数名で変数を宣言し初期化することでシャドウィングするものです。

#include <string>
#include <vector>
#include <optional>

using namespace std;

int main() {
  vector<string> vs{"1", "2", "3"};
  for (auto &s : vs) {
    // constで宣言しなおす
    const vector<string>& vs = vs;  // 現在できない

    // 以降、vsはコンテナを変更しない操作のみが可能
    ...
  }

  ...
  
  auto s = optional<string>{"Godzilla"};
  if (s) {
    // optionalの中身でシャドウィング
    auto s = *s;  // 現在できない

    // 以降、optionalとしてのsは必要ない
    ...
  }
}

どうやらこれは、現在でもGCCでのみ意図通りに行えるようです(他のコンパイラは未初期化変数扱いになる)。

3つ目は、子スコープを導入することなく変数をシャドウィングすることを許可するものです。これは、前2つの提案と組み合わせることもできます。

#include <string>
#include <vector>

using namespace std;

int main() {
  vector<string> vs{"1", "2", "3"};

  // ある時点からconstにする
  const vector<string>& vs = vs;  // 現在できない

  // 以降、vsはコンテナを変更しない操作のみが可能
  ...
}

同じスコープで同じ変数名を宣言できないため、現在でも前2つの提案でもシャドウィングするには子スコープを導入しなければなりません。この制限を解除することによって、このように最初の宣言における変数のconst性を後から変更することができるようになります。

4つ目は、前のものとは少し毛色が異なるもので、条件付きキャストによってシャドウィングを行うものです。

#include <string>
#include <optional>
#include <memory>

using namespace std;

int main() {
  auto s = optional<string>{"Godzilla"};

  // sをstringにキャスト(*による)
  if (s as string)  // もしくは if (s is string)
  {
    // このスコープでは、sはstring&
  }
  else
  {
    // このスコープでは、sはoptional<string>
  }


  auto i = shared_ptr<int>{42};
  
  if (i as int&)// もしくは if (i is int&)
  {
    // このスコープでは、iはint&
  }
  else
  {
    // このスコープでは、iはshared_ptr<int>
  }
}

ここでのキャストは*.get()等によって行われるもので、パターンマッチングにis asを使用する提案(P2392R2)をベースとしています。

P2392ではパターンマッチングにおける利用と一般化に焦点を置いていますが、この提案ではシャドウィングとそれによってもたらされる安全性向上に焦点を当てています。

これら4つの提案は互いに直行しているため、全てを導入しなくてもどれか1つだけから導入することもできます。

この提案を導入することによって次のようなメリットが得られます

  • プログラマは、イテレータや参照、ポインタの無効化の問題を回避しデバッグするために、コンパイラを使用できるようになる
  • 普遍性とスレッド安全性のためのconstの活用を支援する
  • シャドウィングを増やすと、コードがシンプルかつ簡潔になる

また、この提案は静的解析ツールのためのヒントとなるとも述べられています。

おわり

この記事のMarkdownソース