以下の内容はhttps://onihusube.hatenablog.com/entry/2025/12/16/230316より取得しました。


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

文書の一覧

全部で99本あります。

もくじ

N5010 WG21 agenda: 16-21 June 2025, Sofia Bulgaria

2025年6月にソフィア(ブルガリア)で行われる会議のアジェンダ

P0085R1 Oo... adding a coherent character sequence to begin octal-literals

8進リテラルの新しいプリフィックスの提案。

現在の8進リテラルの構文では、0始まりの数値リテラルが8進リテラルとして認識されます。

01234; // 8進リテラル
0;     // 8進リテラル
08;    // ng、8は8進数値として無効

ただ、この構文は初学者にとって間違えやすいことが指摘されています。例えば、10進リテラルの整数値を幅をそろえるなどの目的で先頭に0パディングしてしまったときでもエラーにはならず、8進リテラルとして解釈されることで整数値が変化します。

 12340; // 10進リテラル
012340; // 8進リテラル

この提案はこのような構文を修正するために、8進リテラルの新しいプリフィックスとして0o/0Oを追加するとともに、現在の8進リテラルプリフィックスを非推奨にするものです。

// この提案の8進リテラル
0o1234;
0O1234;

この構文はHaskellPythonなどを始めとする他言語で採用されているものと同様で、現在8進数値リテラルデファクトスタンダードな構文となりつつあります。また、16進リテラルや2進リテラルプリフィックスと整合しています。

0b00101010; // 2進リテラル
0o52;       // 8進リテラル
0x2A;       // 16進リテラル

Cの8進リテラルC++の現在のものと同様の構文を取っていますが、これはすでに非推奨化されており、廃止予定とされています。C++でもこれに倣って、まず非推奨化してコンパイラの警告を促し、将来的に廃止することを目指します。

この修正は基本的に後方互換性を維持するものですが、一つ非互換な変更となる部分があります。それはstd::format()で整数値を8進出力した時のフォーマット結果で、#oオプションによって基数プリフィックスを表示させるようにしたときの結果が構文と一貫しなくなるというものです。

std::println("{:o}", 0o77);  // 8進出力
std::println("{:#o}", 0o77); // 基数プリフィックス付き8進出力
                             // `#O`はない
std::println("{:#x}", 0x3f); // 基数プリフィックス付き16進出力(小文字)
std::println("{:#X}", 0x3f); // 基数プリフィックス付き16進出力(大文字)
77
077
0x3f
0X3F

特にこの提案の後で、#Oオプションが無いこともあり、この出力は16進の場合(#Xだとプリフィックス0Xになる)と一貫しなくなります。

この提案では、オプションはそのままとして#oオプションの出力結果のプリフィックス0oに変更することを推奨しています(提案にはしていません)。これは破壊的変更になりますが、Githubの検索では利用頻度が低いので問題ない可能性があるとしています。また、代替のオプションを4つ提示しています。

P0149R2 Generalised member pointers

P0149R3 Generalised member pointers

メンバポインタの表現可能な範囲を拡張する提案。

以前の記事を参照

R2での変更は

  • 以前は言及のみだった->*と単項*演算子についての変更を提案するようになった
  • 提案する文言の修正

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

  • EWGにおける明確化のための投票を受けての修正
    • E1->E2(*(E1)).E2と等価であり、これと同様にE1->*E2(*(E1)).*E2と常に等価
  • 提案する文言の修正

などです。

このリビジョンでは、->*と単項*および->演算子についても一般化メンバポインタに対応するようにしています。

  • *: 「クラス型T1の型T2の配列メンバへのポインタ」型の式に対して*が適用された場合、「T1T2型メンバへのポインタ」型の値になる
    • E1.*(*E2)という式は*(E1.*E2)と等価である必要がある
      • E1T1型の式、E2T2型配列メンバへのポインタ型の式、式全体の型はT2
  • ->: 式E1, E2に対してE1->E2のように->が適用された場合で、↑の新しい*演算子が使用される場合、既存の場合と同様に(*(E1)).E2と等価
    • この適用範囲は、E1がメンバへのポインタである場合と、E1が配列型メンバへのポインタである場合、の両方に及ぶ
  • ->*: 式E1, E2に対してE1->*E2のように->*が適用された場合で、新しい.*演算子が使用される場合、既存の場合と同様に(*(E1)).*E2と等価
    • この適用範囲は、E1がメンバポインタのポインタである場合と、E1が配列型メンバへのポインタである場合、の両方に及ぶ

->*を用いて、->*.*を用いて定義されるというところは既存のものと変わりませんが。*.*が一般化メンバポインタに対応するのに合わせて動作範囲が拡張されています。

*->のサンプルコード

struct A { int is[42]; };
constexpr int (A::*isp)[42] = &A::is; // A::is(Aのint型配列メンバ)へのメンバポインタisp

A a;
constexpr int& is0_1 = *(a.*isp); // OK, C++98(constexprを除いて、以下同様)
constexpr int& is0_2 = a.*(*isp); // NG、この提案ではok
static_assert(&is0_1 == &is0_2);  // NG、この提案ではok
// どちらも、a.is[0]を参照している

struct B { int i; };
struct C { B b; };
constexpr B C::*bp = &C::b; // C::bへのメンバポインタbp

C c;
constexpr int& i_1 = &(c.*bp)->i;   // OK, C++98
constexpr int& i_2 = c.*((&bp)->i); // NG、この提案ではok
//                   c.*((*(&bp)).i) : CのBメンバへのポインタ(*(&bp))に、B::iを.で適用 => Cのint型メンバポインタが得られる
//                   c.*cip          : それをcipとると、c.*cip はCのint型メンバへのポインタをcに適用し、c.b.iを参照する
static_assert(&i_1 == &i_2);        // NG、この提案ではok
// どちらも、c.b.iを参照している

struct C { int i; };
struct D { C cs[42]; };
constexpr C (D::*csp)[42] = &D::cs; // D::cs(DのC型配列メンバ)へのメンバポインタ

D d;
constexpr int& cs0i_1 = (d.*csp)->i;  // OK, C++98
constexpr int& cs0i_2 = d.*(csp->i);  // NG、この提案ではok
//                      d.*((*csp).i) : DのC型配列メンバ(csp)に*を適用 => d.cs[0]を指すDのC型メンバポインタが得られる
//                                    : DのC型メンバポインタ(*csp)にC::iを.で適用 => Dのint型メンバポインタが得られる
//                      d.*dip        : それをdipとすると、d.*dip はDのint型メンバへのポインタをdに適用し、d.cs[0].iを参照する
static_assert(&cs0i_1 == &cs0i_2);    // NG、この提案ではok
// どちらも、d.cs[0].iを参照している

*演算子は配列メンバポインタに適用されると、その配列メンバを保持しているクラス型のメンバポインタ(型は配列の要素型)、を返すようになります。->演算子はその動作と.の新しい動作を利用して、メンバのメンバへのポインタをそのクラスの直接のメンバポインタへ変換するようになります。

->*のサンプルコード

struct A { int i; };
struct B { A a{}; };
constexpr A B::*ap = &B::a;   // B::aへのメンバポインタap
constexpr int A::*ip = &A::i; // A::iへのメンバポインタip

B b;
constexpr int& i_1 = (b.*ap).*ip;   // OK, C++98
constexpr int& i_2 = b.*(ap.*ip);   // NG、この提案ではok
//                       ap.*ip     : BのA型メンバへのポインタapに、Aのint型メンバへのポインタipを.*で適用 => Bのint型メンバへのポインタが得られる
//                   b.*bip         : それをbipとすると、b.*bipはBのint型メンバへのポインタをbに適用し、b.a.iを参照する
static_assert(&i_1 == &i_2);        // NG、この提案ではok
// どちらも、b.a.iを参照している

constexpr int& i_3 = (&(b.*ap))->*ip; // OK, C++98
constexpr int& i_4 = b.*((&ap)->*ip); // NG、この提案ではok
//                      (*(&ap).*ip)  : B::aへのメンバポインタ(*(&ap))に、Aのint型メンバへのポインタipを.*で適用 => Bのint型メンバへのポインタが得られる
//                   b.*bip           : それをbipとすると、b.*bipはBのint型メンバへのポインタをbに適用し、b.a.iを参照する
static_assert(&i_3 == &i_4);          // NG、この提案ではok
// どちらも、b.a.iを参照している
struct C { int i; };
struct D { C cs[42]; };
constexpr int C::*ip = &C::i;       // C::iへのメンバポインタ
constexpr C (D::*csp)[42] = &D::cs; // D::cs(DのC型配列メンバ)へのメンバポインタ

D d;
constexpr int& cs0i_1 = (d.*csp)->*ip; // OK, C++98
constexpr int& cs0i_2 = d.*(csp->*ip); // NG、この提案ではok
//                        ((*csp).*ip) : DのC型配列メンバ(csp)に*を適用 => d.cs[0]を指すDのC型メンバポインタが得られる
//                                     : DのC型メンバポインタにCのint型メンバへのポインタipを.*で適用 => Dのint型メンバへのポインタが得られる
//                      d.*dip         : それをdipとすると、d.*dip はDのint型メンバへのポインタをdに適用し、d.cs[0].iを参照する
static_assert(&cs0i_1 == &cs0i_2);     // NG、この提案ではok
// どちらも、d.cs[0].iを参照している

この提案はC++29に向けて、CWGでレビュー中です。

P1144R13 std::is_trivially_relocatable

オブジェクトの再配置(relocation)という操作を定義し、それをサポートするための基盤を整える提案。

以前の記事を参照

このリビジョンでの変更は明確ではありませんが、リロケーションに関する別の提案であるP2786がC++26に採択されたことを受けて、P3236で提起された問題が解決されていないため、P3236に引き続いてP2786に対する異議申し立ての姿勢が明確にされています。

また、P3236R1が提出された後で、P2786R13までの間に新しく別の問題が追加されているとのことです

  • ARM64プラットフォームでは"vptr signing"を使用しており、これはmemcpyに対して安全ではない。しかし、P2786(WD)ではvptrを持つ型やそれを持つ型をメンバに含むような型に対してis_trivially_relocatabletrueを返す
    • このためARM64上ではis_trivially_relocatableに従ってリロケーションを行うことが安全ではない
  • P2786のリロケーション関連クラスアノテーションは驚くほど醜い。一方P1144の属性構文はそれほどではない

これらの解決のため、このリビジョンでの提案する文言はP2786マージ後のWDに対する差分になるように変更されているようです。

この提案は、EWG/LEWGのどちらにおいてもコンセンサスを得られなかったようで、リジェクトされています。

P1306R4 Expansion statements

コンパイル時にステートメントをループ生成することのできる、展開ステートメントの提案。

以前の記事を参照

このリビジョンでの変更は、文章と文言を書き直したことのみです。

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

P2079R8 Parallel Scheduler

ハードウェアの提供するコア数(スレッド数)に合わせた固定サイズのスレッドプールを提供するSchedulerの提案。

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

  • HagenbergでのLEWGレビューのフィードバックを適用
  • bulk_chunkedbulk_unchunkedの場合の図を追加
  • 提案する文言の改善

などです。

P2287R5 Designated-initializers for base classes

基底クラスに対して指示付初期化できるようにする提案。

以前の記事を参照

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

P2414R7 Pointer lifetime-end zap proposed solutions

Pointer lifetime-end zapと呼ばれる問題の解決策の提案。

以前の記事を参照

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

  • "discussion"の内容をP2434R4のものにリベース
  • angelic provenanceがない場合のpointer-zapのエルゴノミクスの限界を示すために、露出ポインタを使用したLIFOプッシュアルゴリズムの例を追加
  • usable_ptr<T>関連
    • 比較を<=>に変更
    • T&を返すoperator*のみをconstexprにする
    • operator==constexprではなくconstにする
    • ハッシュの規定からクラスDを削除
    • make_ptr_prospective()make_usable_ptr()にリネーム
    • uintptr_tがない実装でもas-ifルールによって実装できることを明記
    • 比較演算子との互換性を保つために、nullptr_tを取るコンストラクタでiptr(内部ポインタ)をゼロに初期化する

などです。

P2434R4 Nondeterministic pointer provenance

現在のC++のポインタ意味論をポインタのprovenanceモデルに対して整合させるための提案。

以前の記事を参照

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

  • N5008にリベース
  • 因果関係に反するポインタ値を禁止する
  • P3501R0について議論を追加

などです。

P2509R1 A proposal for a type trait to detect value-preserving conversions

算術型について、その値を保持する変換を検出するための型特性を追加する提案。

以前の記事を参照

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

  • スコープの縮小
  • 参考文献の更新

などです。

P2664R10 Proposal to extend std::simd with permutation API

std::simdに、permute操作のサポートを追加する提案。

以前の記事を参照

このリビジョンでの変更は、LEWGレビューを受けての文言更新のみです。

P2719R5 Type-aware allocation and deallocation functions

型を指定する形のnew/delete演算子カスタマイズ方法の提案。

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

  • EWGガイダンスに従って文言更新
    • type_identity型を構築する際、型から修飾子を削除する
    • グローバルスコープのtype aware演算子(この提案のもの)は、定数実行時に置換可能なグローバル割り当て関数として扱う(定数式で使用可能)
    • type awareなnew/new[]delete/delete[]のスコープ間選択がill-fomredになるようにする
    • 現在暗黙な引数をすべて必須にする
  • Introductionの例で正しいシグネチャを使用するように修正
  • [expr.new]のオーバーロード解決例を更新
    • 既存の例を更新し、type awareな演算子(この提案のもの)を追加
    • placement newにおけるstd::align_val_t引数の動作を示す新しい例を追加
  • std::type_identity<T>ではなくstd::type_identity<U>を使用するように文言を更新
  • 文言の変更を反映するように例を更新

などです。

P2902R2 constexpr 'Parallel' Algorithms

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

以前の記事を参照

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

  • 定数評価中に並列アルゴリズムが例外を送出した場合でも、実行時の意味論が順守されることを明確化
  • 前のリビジョンでのリベース時のミスを修正

などです。

P2927R3 Observing exceptions stored in exception_ptr

std::exception_ptrを再スローせずに例外オブジェクトの取得を試みる関数の提案。

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

  • void exception_ptr_cast(const exception_ptr&&a) = deleteを追加した

などです。

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

P2956R1 Add saturating library support to std::simd

P0543で提案されている整数型の飽和演算関数群にstd::simdオーバーロードを追加する提案。

以前の記事を参照

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

  • 実装経験の追加
  • 文言の改善

などです。

P2970R0 Partial application of concepts in template arguments

コンセプトテンプレートパラメータで、部分適用されたコンセプトを受け入れるようにする提案。

C++26では、コンセプトテンプレートパラメータによってコンセプトそのものをテンプレート引数に渡すことができるようになっています。しかし、渡せるのはその引数を埋める前のコンセプトそのものであり、何かしらの引数が部分適用されたコンセプトを渡すことはできません。

// Cを満たす要素型によるrangeであることを制約するコンセプト
template <typename R, template <typename> concept C>
concept range_of = std::ranges::range<R> && C<std::ranges::range_value_t<R>>;

auto f(range_of<std::integral> auto&&);     // ok
auto f(range_of<std::same_as<int>> auto&&); // ng

コンセプトテンプレートパラメータCには引数適用前のコンセプトを渡すことしかできません。

これに対処する一つの方法としては、range_ofコンセプトの定義をこれを考慮して変更することができます。

template <typename R, template <typename...> concept C, typename... Args>
concept range_of = std::ranges::range<R> && C<std::ranges::range_value_t<R>, Args...>;

void f(range_of<std::same_as, int> auto&&);

Cに渡すテンプレート引数を別に受け取るようにして、内部で適用するようにしています。しかし、これはコンセプトの定義でテクニックが必要になるとともに、引数の意味が明確ではなく、要素型に対する制約を複数取れるようにrange_ofを拡張する方向性を閉ざしてしまいます。

別の方法として、コンセプトテンプレートパラメータとその引数を別の型を経由して渡すようにする方法も考えられます。

template <template <typename...> concept C, typename... Args>
struct packed_concept {
  template <typename T>
  static constexpr bool apply = C<T, Args...>;
};

template <typename R, typename... PackedConcept>
concept range_of = std::ranges::range<R> && (PackedConcept::template apply<std::ranges::range_value_t<R>> && ...);

void f(range_of<packed_concept<std::convertible_to, int>, packed_concept<std::regular>> auto&&);

こちらの場合は要素型に対する制約の合成がサポートされているものの、先ほどの方法と比較しても可読性が低下しており、包摂関係の成立を妨げるようになっています。

結局、最初の例にあったような部分適用されたコンセプトをそのまま渡すことができれば一番使いやすくなるとして、ここではそれを提案しています。ただし、部分適用されたコンセプトを渡す構文は、conceptの指定が必要になります。

// コンセプトテンプレートパラメータを受け取る何らかのテンプレートとする
some_template_name <
  std::regular,           // コンセプトを渡す(C++26
  concept regular<>,      // 0引数が部分適用されたコンセプトを渡す(この提案
  invocable<int>,         // コンセプトの結果のbool値が渡される(診断されるはず
  concept invocable<int>  // 1引数が部分適用されたコンセプトを渡す(この提案
>;

このように、部分適用されたコンセプトを渡す際は渡す側でconceptの指定が必要になります。

これは、可変長引数を取るコンセプトではbool値を渡そうとしているのか部分適用されたコンセプトを渡そうとしているのかが曖昧となり、無理やり推定しようとすると既存のコードを壊してしまう可能性があるほか、将来の機能としてのユニバーサルテンプレートパラメータが来た場合に推定が曖昧になることが予想される、などの理由によってこのようになっています。

最初のrange_ofの例はコンセプト定義の変更を必要とせず、conceptを利用側で追加することで有効になります。

// Cを満たす要素型によるrangeであることを制約するコンセプト
template <typename R, template <typename> concept C>
concept range_of = std::ranges::range<R> && C<std::ranges::range_value_t<R>>;

auto f(range_of<std::integral> auto&&);     // ok
auto f(range_of<std::same_as<int>> auto&&); // ng
auto f(range_of<concept std::same_as<int>> auto&&); // ok

この部分適用コンセプトテンプレートパラメータは、そのコンセプトをラップして引数を部分適用するコンセプトをその場でインラインで定義してそれを使用するかのように動作します。

// 即席コンセプト
template<typename T>
concept invented_same_as = std::same_as<T, int>;

// これは
auto f(range_of<concept std::same_as<int>> auto&&);

// こう書きかえられるのと等しい
auto f(range_of<invented_same_as> auto&&);

これによって、コンセプトの包摂関係は部分適用されたコンセプトを渡した時でも保たれるようになります。

この書き換え例からも分かるように、部分適用されたコンセプトは第二引数以降が部分適用されたものとして扱われます。これは、テンプレートパラメータ宣言時などにコンセプトの第一引数指定を省略できる機能と一貫したものです。

// これら3つの例の制約は全て等しい

template<typename T>
void f() requires invocable<T, int>;

template<invocable<int> T>  // Tが第一引数に自動的に補われる
void f();

void f(invocable<int> auto&& f);  // decltype((f))が第一引数に自動的に補われる
// このように使用したとすると
auto f(range_of<concept invocable<int>> auto&&);

// このような即席コンセプトを定義して
template<typename T>
concept invented_invocable = invocable<T, int>;

// こう書きかえられるのと等しい
auto f(range_of<invented_invocable> auto&&);

このために、この部分適用されたテンプレートを渡す機能はコンセプトに限られており、変数テンプレートなどでは利用できません(ほかの種類のテンプレートの場合はこのような補完で有用なものが確立されていないため)。

また、2つ以上のテンプレートパラメータを残した状態で部分適用することはできません。

提案されている文言からの例

template <typename T, template <typename> concept... Concepts>
concept all_of = (Concepts<T> && ...);

template <typename, auto>
concept A = true;

template <typename T, typename>
concept C = true;

template <typename... T>
concept D = true;

template <typename>
concept E = true;

void f(all_of<concept C<0>, concept C<int>, concept D<int>> auto); // ok
void f(all_of<concept E<int>> auto);      // error: Eは単一のテンプレート引数しか取らない(これ以上引数を適用できない)
void f(all_of<concept C<int, int>> auto); // error: 即席コンセプト内の制約式はC<T, int, int>となるが、これは有効な式ではない

この機能はP2841R7の早期のリビジョンでは含まれて提案されていたものの、C++26を目指すために提案のスコープを絞ったことやEWGが重視していなかったこともあり、最終的なP2841R7ではドロップされていたものです。

P2996R12 Reflection for C++26

値ベースの静的リフレクションの提案。

以前の記事を参照

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

  • コア言語の文言の変更
    • スプライスされた関数呼び出しとオーバーロード解決の相互作用を明確化
      • CWG Issue 2701の修正を統合
    • requires式によって導入されたローカルパラメータのリフレクションを禁止
    • “naming class”を“designating class”に変更し、スプライス式について定義する
    • reflect_valuevalue_ofreflect_constantconstant_ofに置換
  • ライブラリの文言変更
    • access_context::current()の仕様を改善(例を含む
    • rがビットフィールドの場合、size_or(r)は定数ではなくなった
    • extractを使用して、配列からポインタを取得できるようにした

などです。

P3008R5 Atomic floating-point min/max

浮動小数点数型のstd::atomicにおけるfetch_max()/fetch_min()の問題を解消する提案。

以前の記事を参照

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

  • P3348がWDにマージされたことを受けて、P3348に依存しないように文言を修正
  • 不足していたkey->operationのマッピングを追加
  • atomic/atomic_ref<floating-point>::fetch_min/maxの仕様を明確化
  • typoの修正
  • [math.syn]の変更を文言セクションの先頭に移動

などです。

P3037R6 constexpr std::shared_ptr and friends

std::shared_ptrを定数式でも使えるようにする提案。

以前の記事を参照

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

  • アトミック部分特殊化の実装に関する議論を追加
  • スマートポインタアダプタ(out_ptrなど)の実装に関する議論を追加
  • スマートポインタアダプタのvoid**変換演算子からconstexprを外す
  • HagenbergでのLEWG投票結果を追加

などです。

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

P3086R4 Proxy: A Pointer-Semantics-Based Polymorphism Library

静的な多態的プログラミングのためのユーティリティ、"Proxy"の提案。

以前の記事を参照

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

  • Proxyライブラリ4.0の実装に合わせて文言を更新
    • facade_aware_overload_tを追加
    • ProOverload要件を追加
    • proxyクラステンプレートおよびaccess_proxy, proxy_invoke, proxy_reflect関数テンプレートの制約を改訂

などです。

P3096R9 Function Parameter Reflection in Reflection for C++26

C++26に向けた静的リフレクションに対して、関数仮引数に対するリフレクションを追加する提案。

以前の記事を参照

このリビジョンでの変更は、CWGのフィードバックを受けての文言修正のみです。

P3100R2 Implicit contract assertions

UB(及びEB)を契約違反として扱うようにする提案。

以前の記事を参照

このリビジョンでの変更は明示的ではないですが、SG21においてP3100R1の方向性が受け入れられたこととContracts提案(P2900R14)がC++26にマージされたことを受けて全面的に書き直されています。特に、コア言語UBホワイトペーパー(P3656R1)の方針確立を受けてその作業に貢献しそこに採択されることを目標としています。

とはいえ基本的な方向性に変更はないようですが、以前のリビジョンからの主要な変更は

  • assumeセマンティクスを暗黙的な契約アサーションでのみ許可する
  • detection_mode列挙型への変更の削除
    • エラーカテゴリ(検出されたUB種別)の識別には列挙値の代わりにラベルを使用する

などのようです。

P3111R6 Atomic Reduction Operations

std::atomicにアトミックリダクション操作を追加する提案。

以前の記事を参照

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

  • PPC64LEにおける実装に関する推奨事項を追加
  • [atomics.order]に"atomic modify-write operation"の定義を追加
    • これを受けて、定義名を"atomic reduction operation"から"atomic modify-write operation"に変更
  • P3008の文言の組み込み方法を変更(WDマージ済み

などです。

P3125R4 constexpr pointer tagging

タグ付きポインタをサポートするためのライブラリ機能の提案。

以前の記事を参照

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

  • LEWGの要求による変更を追加
    • nullptr_tコンストラクタの削除
    • void*サポートを追加
    • pointer_tag_pairの最初のテンプレート引数がpointeeではなくpointerになった
    • ポインタ内の利用可能なビットを調査するためのpointer_tag_traitを追加
    • pointer_tag_pairにタプルプロトコルサポートを追加

どのリビジョンからかはわかりませんが、この提案はポインタ値の下位ビットに安全かつポータブルにアクセスするための機能を提供しようとしていますが、上位ビットへのアクセスを標準化しようとはしていません(移植性が無く、OSやCPUにおける利用を妨げるため)。pointer_tag_pair型は、あるポインタ値に対してオリジナルのポインタ値と埋め込んだタグ値のペアです。

P3149R10 async_scope -- Creating scopes for non-sequential concurrency

P2300のExecutorライブラリについて、並列数が実行時に決まる場合の並行処理のハンドリングを安全に行うための機能を提供する提案。

以前の記事を参照

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

  • いくつかのフィードバックを適用
  • “is [boolean]”を“returns [boolean]”に置換
  • nest-dataが必要な動作を行うためには推論補助が必要であることを確認
  • impls-for<nest_t>::get-stateを更新し、条件付きnoexceptを付加
    • 他のsenderアルゴリズムget-stateが例外を送出するかに応じてそのset_error動作を変更する必要がある
  • async_scope_tokenを更新し、t.disassociate()noexceptであることを必須とする
  • simple_counting_scope::try_associate()counting_scope::try_associate()の両方をnoexceptにする
    • impls-for<nest_t>::get-statenoexceptは、指定されたsenderが左辺値の場合指定されたトークンのtry_associate()noexceptであるかどうかに依存する
  • メンバ名の末尾アンダースコアの付与に関する記述を一貫させる
  • empty_envenv<>に変更

などです。

P3164R4 Early Diagnostics for Sender Expressions

提案中のExecutorライブラリにおいて、senderチェーンのエラーを早期に報告するようにする提案。

以前の記事を参照

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

  • sizeof...(Env)が1より大きい場合get_completion_signatures<Sndr, Env...>()をill-formedとする
  • senderの(非依存)完了シグネチャを宣言するために、ネストした::completion_signaturesエイリアスを使用するオプションを削除

などです。

この提案はP3557(定数式での例外送出を使用したエラー報告)によって置き換えられることになったため、ここでストップされています。

P3179R8 C++ parallel range algorithms

RangeアルゴリズムExecutionPolicyに対応させる提案。

以前の記事を参照

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

P3284R4 write_env and unstoppable Sender Adaptors

receiverの環境に値を書き込むためのwrite_envと、それを用いたunstoppableアルゴリズムの提案。

以前の記事を参照

このリビジョンでの変更は明確ではないですが、文言の調整のみのようです。

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

P3293R2 Splicing a base class subobject

リフレクション機能において、基底クラスのサブオブジェクトへアクセスする簡単な方法を提供する提案。

以前の記事を参照

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

  • P3547R1(リフレクションにおけるアクセスコンテキストの考慮)を考慮して文言を調整
  • has_inaccessible_subobjects()を追加
    • has_inaccessible_subobjects(r, ctx)は、ctxのコンテキストでクラスのリフレクションrのサブオブジェクトにアクセス可能かをbool値で返す

などです。

P3310R6 Solving issues introduced by relaxed template template parameter matching

P0552R0の影響を緩和するための提案。

以前の記事を参照

このリビジョンでの変更は、文言の更新です。

P3347R2 Invalid/Prospective Pointer Operations

無効化されたポインタに対する一部の演算を明示的に許可する提案。

以前の記事を参照

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

  • Hagenberg会議のレビューを受けての修正
    • pointer-arithmetic要件を削除
      • この要件はコンパイラがサイズを知らない不完全型には適用できない
      • 後で復帰する可能性があるものの、完全型に限定されて適用される
    • angelic provenanceの参照をすべて削除
    • prospective pointersの参照をすべて削除
      • P2434R2の今後の進展次第では、これらの参照は再び復帰する可能性がある

などです。

P3375R3 Reproducible floating-point results

計算結果の再現性の保証された浮動小数点数型の提案。

以前の記事を参照

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

  • 貢献者リストを更新
  • abstractを更新
  • banking discussionのセクションを更新
  • Cの浮動小数点数拡張に関するTSについての議論を追加
  • 可能なアプローチのリストに正しく丸めを行う関数を追加
  • 正しく丸めを行う関数に焦点を当てて、スコープの拡大を抑制
  • open questionsを更新
  • 参考文献を更新

などです。

P3385R5 Attributes reflection

リフレクションにおいて、エンティティに指定されている属性の情報を取得・付加できるようにする提案。

以前の記事を参照

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

  • in-placeスプライス構文を削除
  • 関連メタ関数を追加
  • 実装フィードバックを拡張

などです。

P3394R3 Annotations for Reflection

C++任意のエンティティ(宣言)に対して静的リフレクションのためのアノテーションを付加できるようにする提案。

以前の記事を参照

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

  • P2996R12へのリベース
  • プロセス効率化のために注釈を削除

などです。

P3395R4 Fix encoding issues and add a formatter for std::error_code

std::error_codeをフォーマット可能にする提案。

以前の記事を参照

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

  • R3についてのLEWG投票結果を追加
  • 文言の誤字修正
  • デバッグ出力の文言を修正
  • std::error_codeの値についてのフォーマットオプションを提供しない理由を明確化
    • カテゴリ情報なしでは用途が限られるためと、{fmt}においてもその要望はなかったため

などです。

P3402R3 A Safety Profile Verifying Initialization

クラスのすべてのサブオブジェクトが初期化されていることを保証するプロファイルの提案。

以前の記事を参照

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

  • no.reassignルールを削除
  • std::verified_castを議論の対象レベルへ格下げ(提案しない
  • 不一致の問題をIFNDRとして修正
  • 許容される入力セットを修正
  • restrict.returnsルールを修正
  • POD型ではなくトリビアル型を使用するようにテキストを更新
  • 提案する文言を追加

などです。

P3411R2 any_view

viewを型消去するためのviewany_viewの提案。

以前の記事を参照

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

  • constexprサポートを追加
  • 提案する文言を追加
  • ムーブオンリーをデフォルトとする
  • 2つ目のリファレンス実装を追加
  • その他修正

などです。

P3412R2 String interpolation

std::format/std::print向けの引数となるフォーマット文字列と対象引数列の組を生成する、文字列補完リテラルの提案。

以前の記事を参照

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

  • 関数のどの引数がフォーマット文字列であるかを示す無視できない属性を追加し、xリテラルの必要性を回避する
  • マクロではないことを明確にするため、__FORMAT__関数の名前を__format__に変更
  • マクロ展開を式の抽出後に行うようにする
    • これにより、fリテラル処理の様々なステップを翻訳フェーズに分離できるようになる
    • エディタ等での可視化の堅牢性が向上
  • ユーザー定義リテラルfリテラルと連携しないように明確化
  • printf形式フォーマットに関する章を追加
  • プリプロセッサのフェーズで可能な(文言)表現戦略に関するテキストを追加

などです。

無視できない属性とは、[[cpp_format_string(N)]]のようなもので、fリテラルN番目の関数引数に指定されている場合にプリプロセッサによって展開されている__format__()関数呼び出しをインライン展開することを表すものです。

このために、__format__()関数呼び出しを展開する必要があるかをオーバーロード解決フェーズで確定させようとしています。

// print()の宣言
template<typename... Args>
void [[cpp_fmt_string(1)]] print(format_string<Args...> fmt, Args&&...args);

template<typename... Args>
void [[cpp_fmt_string(2)]] print(ostream&, format_string<Args...> fmt, Args&&... args);
// In user code
std::print(f"Value {1}");                           // #1
std::print(std::cerr, f"Wrong value {2}");          // #2
std::print("String value: {}", f"Some value {3}");  // #3

このような呼び出しが行われている場合、コンパイラstd::print()オーバーロードセットを(fリテラルの引数位置Nに対して)[[cpp_fmt_string(N)]]に一致するものとそうでないものの2つに分割し、それぞれでオーバーロード解決を行います。どちらか片方のセットだけで解決が成功すればそれが呼び出し結果となります。

#1の場合、fリテラルが1番目にあるので、[[cpp_fmt_string(1)]]とそうでないグループに分割され、前者のグループでは__format__()呼び出しをインライン展開した結果を引数としてオーバーロード解決が行われ、マッチするためこれが採用されます(後者のグループではインライン展開せずにオーバーロード解決が行われるものの引数が不足しているため失敗する)。

#2の場合、fリテラルが2番目にあるので、[[cpp_fmt_string(2)]]とそうでないグループに分割され、前者のグループでは__format__()呼び出しをインライン展開した結果を引数としてオーバーロード解決が行われ、マッチするためこれが採用されます(後者のグループではインライン展開せずにオーバーロード解決が行われるものの、std::cerrをフォーマット文字列として使用できずオーバーロード解決は失敗する。

#3の場合、fリテラルが2番目にあるので、[[cpp_fmt_string(2)]]とそうでないグループに分割され、[[cpp_fmt_string(1)]]オーバーロードは展開しないグループに、[[cpp_fmt_string(2)]]オーバーロードは展開するグループに属します。展開するグループでは文字列リテラルからostream&への暗黙変換ができないためオーバーロード解決は失敗し、展開しないグループでは1つ目の引数がフォーマット文字列に、2つ目の引数(fリテラルの展開後__format__()呼び出し)がフォーマット対象引数としてマッチするため、1つ目のオーバーロードが選択されます。その後、実行時に__format__()呼び出し経由でstd::format()が呼び出されることでfリテラルの文字列が出力されます。

P3439R2 Chained comparisons: Safe, correct, efficient

誤って書かれることの多い、連鎖比較を意図通りに動作するようにする提案。

以前の記事を参照

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

  • 実際のコードベースの調査で連鎖比較を期待するバグコードが発見されたことを受けて、これを明記して再提案した
    • R1はバグが稀であることと実装経験の乏しさによってリジェクトされていた

などです。

P3442R2 [[invalidate_dereferencing]] attribute

関数に渡されるポインタが無効化されることをコンパイラに通知するための属性、[[invalidate_dereferencing]]の提案。

以前の記事を参照

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

  • SG23での投票結果を追加
  • [basic.compound] p4に関して、記憶域外ポインタについての議論を追加
  • オブジェクトの無効化についてコア言語の観点からより明確な説明を追加

などです。

P3480R5 std::simd is a range

std::simdrangeにする提案。

以前の記事を参照

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

  • iterator_categoryinput_iterator_tagに変更し、iterator_conceptrandom_access_iterator_tagに変更
  • コンストラクタ内のintsimd-size-typeに変更
  • constexprfriendの順序を修正
  • default_sentinel_tを使用したoperator<=>を削除
  • #if LEWG_WANTS_CONVERSIONを削除するものの、変換コンストラクタはそのままにする
  • operator*offset_に対する事前条件を修正

などです。

P3516R2 Uninitialized algorithms for relocation

未初期化メモリに対するリロケーションアルゴリズムのライブラリ機能の提案。

以前の記事を参照

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

  • relocate-atstd::relocate_at()として提案の一部にする
  • std::ranges::の関数にstd::execution_policyオーバーロードを追加
  • P3179R8で追加済みのコンセプトを削除

などです。

P3552R2 Add a Coroutine Task Type

std::executionに対応したコルーチンlazy型の提案。

以前の記事を参照

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

  • 仕様からdecay_tの使用を削除
  • 利用できない場合はexception_ptrを回避するように文言を変更
  • 引数の用途をより適切に反映するために、ContextEnvironmentに変更
  • 説明専用のマクロは斜体を使用する
  • 機能テストマクロを追加

などです。

P3556R1 Input files are source files

規格中の入力C++ソースファイルに関する用語を整理する提案。

以前の記事を参照

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

  • 代替表現を追加
    • "source text"の明確な定義を追加
    • フェーズ1終了時のsource textの内容に関する規範的な保証
  • SG16のフィードバックを適用

などです。

P3557R2 High-Quality Sender Diagnostics with Constexpr Exceptions

std::executionにおける、エラーメッセージ出力を改善する提案。

以前の記事を参照

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

  • nice-to-havesの機能を別の提案に分離
  • ネストされた型エイリアスによるsenderの完了シグネチャ指定のサポートを削除
  • 説明専用だったdependent-sender-errorクラスをstd::execution::dependent_sender_errorに昇格
  • basic-sender::check-typesとそのすべてのカスタマイズをconstexprからconstevalに変更
  • 文言レビューを簡単にするために、P3164R4の提案文言を組み込む

などです。

P3560R1 Error Handling in Reflection

静的リフレクション機能におけるエラーハンドリング方法として例外を採用すべきとする提案。

以前の記事を参照

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

  • std::meta::exceptionstd::exceptionから派生するように変更
    • char const* what()アクセサが追加
  • P2996R12にリベース

などです。

P3565R1 Virtual floating-point values

C++における浮動小数点数演算のセマンティクスについての提案。

以前の記事を参照

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

  • FMAの例と解説を追加
  • FLT_EVAL_METHODとの関係について説明
  • CWG2752への影響について説明
  • 実装要件について説明

などです。

P3566R1 You shall not pass char* - Safety concerns working with unbounded null-terminated strings

その長さが静的に既知ではない文字列(const char*)を受け取るstd::string/std::string_viewのコンストラクタを置き換える提案。

以前の記事を参照

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

  • SafeStringViewUnSafeStringViewのコンセプトを導入
  • 既存コードへの影響について追加
  • nullptrは空文字として扱う

などです。

P3570R1 optional variants in sender/receiver

並行キューのasync_pop()の最適な戻り値型を探る提案。

以前の記事を参照

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

P3588R1 Allow static data members in local and unnamed classes

ローカルクラスでstaticメンバ変数を宣言できるようにする提案。

以前の記事を参照

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

  • 目標をC++29へ変更
  • 初期化順序に関する議論を拡張し、設計例を3つ提示
  • 実装経験に関する議論を拡張し、実装におけるバグに関する議論も追加
  • complete-class contextsを含む順序付けに関する議論を追加
  • 静的変数宣言が重複する可能性があることを考慮して文言を更新

などです。

P3589R2 C++ Profiles: The Framework

フレームワークとしてのプロファイル機能の概説文書。

以前の記事を参照

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

  • インクルードされたソースファイルを除外するメカニズムを追加
  • プロファイルが教育目的にも役立つことを明確化

などです。

P3617R0 std::meta::reflect_constant_{array,string}

定数文字列・配列のリフレクションを返すAPIの提案。

この提案は以前にP3491で提案されていたdefine_static_string()/define_static_array()の拡張となる機能の提案です。

// P3491で提案されているAPIの一部

template <ranges::input_range R> // only if the value_type is char or char8_t
consteval auto define_static_string(R&& r) -> ranges::range_value_t<R> const*;

template <ranges::input_range R>
consteval auto define_static_array(R&& r) -> span<ranges::range_value_t<R> const>;

まず、define_static_string(r)は文字範囲rから静的文字列を作成しその先頭ポインタを返す関数です。しかし、ポインタを返すことによってC++20からのNTTP文字列のパターンで使用できない問題が指摘されました

// NTTPで使用可能な固定長文字列型
template <size_t N>
struct FixedString {
  char data[N] = {};

  constexpr FixedString(char const(&str)[N]) {
    std::ranges::copy(str, str+N, data);
  }
};

template <FixedString S>
struct Test { };

これの使用例と問題の例はつぎのものです

using A = Test<"foo">;  // ok、文字列リテラルはそのまま渡せる
using B = [: substitute(^^Test, {reflect_constant("foo"sv)}) :];  // ng、string_viewはNTTPとして使用できない
using C = Test<define_static_string("foo")>;  // ng、define_static_string()の戻り値からだと長さが取得できない
using D = [: substitute(^^Test, {reflect_constant(define_static_string("foo"))}) :];  // ng、同じく長さが取得できない

substitute(template_refr, refle...)はテンプレートのリフレクションtemplate_refrのテンプレート引数に対してrefle...を順番に引数として与えてインスタンス化したもののリフレクションを返すもので、reflect_constant(arg)argによる定数のリフレクションを返すものです。

ここでは、substitute()の戻り値をスプライス[: refl :])することでクラス型(ここではすべてTest<"...">型)として定義しようとています。

しかし、現状ではA以外は全てエラーとなります。define_static_string()の問題点は文字列ポインタ(const char*)を返すためにFixedStringのCTADに失敗(文字列配列char[N]ではないため)することにあります。ここで重要なことは、substituteスプライスを使用しているように、リフレクションによってこのTest型の実体を生成する、ような生成コードを書けないことにあります。

次に、define_static_array(r)は範囲rから静的配列を作成しその参照を返す関数です。

template <class T>
auto f(const T& var) -> void {
  template for (constexpr auto M : define_static_array(nsdms(^^T))) {
    do_something_with(var.[:M:]);
  }
}

nsdms(class_refl)はクラス型のリフレクションclass_reflから非静的メンバ変数のリフレクションの配列(std::vector<std::meta::info>)を得るものです(おそらくP2996R12のnonstatic_data_members_of()の略記)。展開ステートメントはその処理の都合上nsdms()の返すstd::vectorを直接イテレーションできないため、define_static_array()を通して静的配列に変換して展開しています(このような使い方がP3491のモチベーションです)。

このコードでは、f()内部でTの非静的メンバ変数全てによってdo_something_with()を呼び出していくコードを生成します。

しかし、別の状況では1ステートメント内で全てのメンバ(nsdms(^^T)の戻り値)を処理したい場合があります。展開ステートメントはそのループの結果としてステートメントを生成していくため、このような用途に使用できません。この場合、C++26からの仕様である構造化束縛のパック導入とconstexpr構造化束縛を使用するとシンプルに書くことができます。

// 提案文書より、配列の構造体クラスの例
template <class T>
struct SoaVector {
  // 配列ストレージ
  Pointers pointers_;
  ...
  
  // Pointers型の非静的メンバ(=各配列)のリフレクションを列挙
  // これは`span<const info>`型になる
  static constexpr auto ptr_mems = define_static_array(nsdms(^^Pointers));
  ...

  auto operator[](size_t idx) const -> T {
    // 構造化束縛でのパック導入とconstexpr構造化束縛
    constexpr auto [...M] = [: ptr_mems :]; // ng、spanをパックに展開できない

    // メンバ配列からidx番目の要素を引き当てて配列で返す
    // 畳み込み式によって一文で展開する
    return T{pointers_.[:M:][idx]...};
  }
};

しかし、define_static_array()はリフレクションの配列の参照(span<const info>)を返してしまい、これはパックに変換できないため、この用途には使用できません。必要なのはリフレクションの配列のリフレクション(型としてはinfoになる)を返す何かですが、P3491にはそれはありません。

この提案は、P3491をベースとしつつこれら2つの用途に沿うようなAPIを追加するものです。

// P3491で提案されているAPIの一部

template <ranges::input_range R> // only if the value_type is char or char8_t
consteval auto define_static_string(R&& r) -> ranges::range_value_t<R> const*;

template <ranges::input_range R>
consteval auto define_static_array(R&& r) -> span<ranges::range_value_t<R> const>;

// この提案のAPI

template <ranges::input_range R>
consteval info reflect_constant_string(R&& r);

template <ranges::input_range R>
consteval info reflect_constant_array(R&& r);

どちらも、受け取った範囲rから生成した定数文字列/配列のリフレクションを返すものです。これらの関数を用いると先ほど問題として挙げた例をシンプルに解消することができます。

固定長文字列クラス型のNTTPをリフレクションによって生成する例

using E = Test<[:reflect_constant_string("foo"):]>; // ok
using F = [:substitute(^^Test, {reflect_constant_string("foo")}):]; // ok

reflect_constant_string()は定数文字列へのリフレクションを返すため、それを用いてTest型を生成するリフレクションコードを記述することができます。

SoaVectorの添え字演算子の実装例

// 提案文書より、配列の構造体クラスの例
template <class T>
struct SoaVector {
  // 配列ストレージ
  Pointers pointers_;
  ...
  
  // Pointers型の非静的メンバ(=各配列)のリフレクションを列挙
  // これは`std::array<info, N>`型になる
  static constexpr auto ptr_mems = reflect_constant_array(nsdms(^^Pointers)); // 👈
  ...

  auto operator[](size_t idx) const -> T {
    // 構造化束縛でのパック導入とconstexpr構造化束縛
    constexpr auto [...M] = [: ptr_mems :]; // ok、std::arrayはタプルプロトコルによってパックへ変換できる

    // メンバ配列からidx番目の要素を引き当てて配列で返す
    // 畳み込み式によって一文で展開する
    return T{pointers_.[:M:][idx]...};
  }
};

define_static_array()がそうだったように、この関数もコンパイラの特別なサポートを必要とせずに手書きすることができます。しかも、define_static_array()の実装を少し移動するだけでよく、文言の変更的にも少なく済みます。

template <typename T, T... Vs>
inline constexpr T __fixed_array[sizeof...(Vs)]{Vs...};

template <ranges::input_range R>
consteval auto reflect_constant_array(R&& r) -> meta::info {
  auto args = vector<meta::info>{
    ^^ranges::range_value_t<R>};
  for (auto&& elem : r) {
    args.push_back(meta::reflect_constant(elem));
  }
  return substitute(^^__fixed_array, args);
}

template <ranges::input_range R>
consteval auto define_static_array(R&& r)
    -> span<ranges::range_value_t<R> const>
{
  using T = ranges::range_value_t<R>;

  // produce the array
  auto array = reflect_constant_array(r);

  // turn the array into a span
  return span<T const>(
      extract<T const*>(array),
      extent(type_of(array)));
}

したがってC++26に必須の機能というわけではありません。それでも、このようなものは人々が再発明することが想定できることや、P3491の文言をベースに小さな変更で済むこと、コンパイラ支援によって__fixed_arrayのようなバック配列を(文字列リテラルinitializer_listのように)共通化することができるなどの理由によりC++26の機能として提案しています。

この提案はEWGのレビューで承認され、P3491R2にマージされています。そして、P3491R2は6月全体会議で承認されC++26にマージされています。

P3631R0 Cleaning up the trivial relocation APIs in C++26

リロケーション関連のライブラリAPIを整理する提案。

P2786R13ではリロケーションに関する言語規定と最小のライブラリAPIが導入され、P3516では未初期化メモリに対するリロケーションアルゴリズムが提案されています。P2786はすでにC++26にマージ済みで、P3516は(この提案が書かれた時点で)LEWGの設計承認を獲得しているようです。

ただし、P3516の導入によってP2786で導入されたライブラリAPIには不要になるものがあるため整理の必要が認識されていました。この提案はその整理を行うものです。

この提案無しの現状のリロケーション関連ライブラリAPIは次のようになっています

// P2786 APIs (in draft)
template <class T>
T* trivially_relocate(T* first, T* last, T* result); // freestanding

template <class T>
constexpr T* relocate(T* first, T* last, T* result); // freestanding

// P3516 APIs (design approved)
template<class T>
 requires relocatable-from<T, T>
  constexpr T* relocate_at(T* dest, T* source)
    noexcept(is_nothrow_relocatable_v<T>);

template<class NoThrowForwardIterator1, class NoThrowForwardIterator2>
  constexpr NoThrowForwardIterator2
    uninitialized_relocate(NoThrowForwardIterator1 first,
                           NoThrowForwardIterator1 last,
                           NoThrowForwardIterator2 result); // freestanding

template<class NoThrowForwardIterator1, class Size, class NoThrowForwardIterator2>
  constexpr pair<NoThrowForwardIterator1, NoThrowForwardIterator2>
    uninitialized_relocate_n(NoThrowForwardIterator1 first,
                             Size n,
                             NoThrowForwardIterator2 result); // freestanding

template<class NoThrowBidirectionalIterator1, class NoThrowBidirectionalIterator2>
  constexpr NoThrowBidirectionalIterator2
    uninitialized_relocate_backward(NoThrowBidirectionalIterator1 first,
                                    NoThrowBidirectionalIterator1 last,
                                    NoThrowBidirectionalIterator2 result); // freestanding

// execution_policyを取るオーバーロード
...

namespace ranges {
  // range版オーバーロード
  ...
}

この提案ではこのうちstd::relocate()を削除しようとしています。

この関数はP3516で提案されている未初期化メモリに対するアルゴリズムの機能制限版であり、P3516が提出されるよりも前に発明され残されていたものです。両者の違いはstd::relocate()が入力範囲と出力範囲のオーバーラップチェックを行い、前方/後方のどちらにリロケーションすべきかを判定する点にありますが、P3516のアルゴリズムではリロケーションすべき方向の決定を_backwardオーバーロードで指定しており、このAPIは既存の未初期化メモリに対するアルゴリズムと一貫しています。

この提案はLEWGのレビューにおいて合意を得られず、リジェクトされています。

P3649R0 A principled approach to safety profiles

初期のプロファイル機能については安全性プロファイルに注力すべきとする提案。

形式手法における安全性とは、プログラム全体にわたって特定の不変条件が常に満たされることを保証することを言います。このことを安全性不変条件とここでは呼んでいます。この安全性は、静的解析(コンパイル時の検査)と実行時検査を組み合わせることで達成することができ、どちらの検査も100%の安全性を保証できない場合があります(静的解析で安全性を証明できないため拒否せざるを得ない構文や、実行時検査コストが許容できない場合など)。

Rustではこれに対して、Safe RustとUnsafe Rustの2つの言語を内包することで対処しています。Safe Rustは特定の安全性不変条件が保証されているため、Safe Rustで書かれたプログラムはその保証によって排除されるバグが混入していないことが保証されます。Unsafe RustはSafe Rustに対してそれが提供する安全性不変条件が必ずしも満たされないような機能を追加したもので、この部分はCやC++と同等程度の保証だけを提供しています。ただし、Unsafe Rustはunsafeキーワードによってマーキングが必要なオプトインとなっています。

このアプローチはセキュリティが重要なプログラムの記述において効果的であることが実証されており、プログラムの大部分をSafe Rustで記述し必要な部分だけUnsafe Rustを使用することで、Unsafe Rustの部分のバグだけが問題となります。Unsafe Rust部分にバグがなければ、Safe Rustの保証によってそのプログラムにはある特定の種類のバグがないことが保証されます。

P3589R1などで提唱されているプロファイル機能はC++コードにおける特定のチェックを強制するフレームワークを提案しています。これを用いれば、特定の安全性不変条件の保証を強制する事が可能です(RustのSafeがデフォルトにたいして、Unsafeがデフォルトになってしまうものの)。

P3081R2では安全性を主眼に置いたその具体的なプロファイルの初期セットが提案されていますが、そこでは従来のイディオムやテクニックの延長にあるようなヒューリスティックやスタイル規則などを強制しようとするもので、ここで述べている安全性不変条件が満たされることを保証する類のものではりません。

これに対してこの提案は、P3589R1のフレームワークをベースとするプロファイル機能の初期セットにおいては、安全性プロファイル(特定の安全性不変条件を保証するもの)の標準化にのみ焦点をあてる事を提案するものです。

具体的には、全ての標準プロファイルPは関連する不変条件を持ちます。プロファイルPを有効化することで、この不変条件がPの適用領域内で常に満たされることが保証されます。これは静的解析と実行時検査の組み合わせによって達成され、その不変条件を保証できないような操作は禁止(コンパイルエラー)されます。

これは、C++にある種のSafeブロックを導入するものです。これによって、Pの不変条件が成り立たないのはプロファイルPが有効化されていないコードのみになります。最初はこのような安全の島は小さいですが、時間の経過とともに多くのコードでプロファイルが有効化されることで増加していき、やがて安全の大陸になっていくでしょう。このアプローチはデフォルトが逆ではあるもののRustのアプローチと同じであり、その有効性は実証されています。

安全性プロファイルが安全性を保証するためには、対応する不変条件がほぼ満たされることを強制するのではなく必ず満たされることを強制する必要があります。不変条件を満たすことのできないコードは拒否されるか、実行時検査を伴わなければなりません。これが達成されない場合、安全性不変条件は単なる安全性ヒューリスティックにすぎません。すなわち、不変条件の保証においてFN(false negatives)は許容されません。

安全性ヒューリスティックは現在のC++がすでに行っていることでもあり、これが不十分であるためC++は安全性・セキュリティに関する規制圧力に晒されています。安全性ヒューリスティックのアプローチを継続することはC++を安全な言語にすることはなく、具体的な保証のないヒューリスティックが得られるだけの機能のために既存コードを更新する人は多くないでしょう。

提案では初期化プロファイル(全ての変数の初期化を強制する)プロファイルの例を紹介しています。そのなかで、ユーザー定義の関数などあるプロファイルにおける不変条件を保証できないものに対するアノテーションとして[[profiles::prohibit_in(P)]]の必要性を提案しています。

// ユーザー定義malloc()-like関数
[[profiles::prohibit_in(initialization)]] void* my_malloc(std::size_t);

[[profiles::prohibit_in(P)]]の意味は、「これ(関数やクラスなど)はPの不変条件を破る可能性があるためPによって禁止される必要がある」ことを意味するアノテーションです。対して、[[profiles::suppress(P)]](P3589にすでにある)は「これは不変条件を保証できないためPによって禁止されているものの、この特定のコンテキストでは保証可能であるため一時的に無効化する」ことを意味するもので、異なるものです。

// この関数は初期化済みの領域を返すため、initializationプロファイルに従っている
void* zeroed_malloc(std::size_t size) {
  // すぐ後で初期化するため、initializedプロファイルを抑制する
  [[profiles::suppress(initialized)]] auto ptr = std::malloc(size);
  std::memset(ptr, 0, size);

  return ptr;
}

P3655R1 zstring_view

null終端文字列専用のstd::string_viewの提案。

以前の記事を参照

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

  • Githubでの使用に関する数値情報を更新
  • SG16のフィードバックを適用
  • string-like引数を取る関数の競合するオーバーロードについて、特定のオーバーロードの優先順位付けまたは選択するための方法を追加
  • 議論を容易にするために、コンストラクタにナンバリングを追加
  • char_traitsとNULに関するアンケートを追加

などです。

P3658R1 Adjust identifier following new Unicode recommendations

識別子に使用可能な文字の範囲を広げる提案。

以前の記事を参照

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

  • CWG 2843を同時解決しようとしていた部分を削除
  • ǃ!ではないことを明確にするために例の表に注記を追加し、Q&Aのspoofingに関する議論を拡張して既に導入済みの緩和策を提示

などです。

P3663R1 Future-proof submdspan-mapping

submdsapn()submdspan_mappingを呼び出す場合について、将来的な拡張を考慮しておくようにする提案。

以前の記事を参照

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

  • canonical-iceに、入力がIndexType型の値として表現可能であるというMandatesとPreconditionsを追加
  • subtract-iceを修正し、constant_wrapper演算を適用する
    • まずcanonical-iceを適用し、その後結果のconstant_wrapperで減算を実行する
  • pairスライスに対しては、get()ではなく構造化束縛を利用する
  • submdspanは特定のsubmdspan_mapping(input.mapping(), slices...)の呼び出しが的確であることを実際には制約できない
    • これは、構造化束縛宣言が適格であることを制約する方法が無いため
    • 代わりに、submdspanの制約をsubmdspan_mapping(input.mapping(), full_extent, ..., full_extent)が適格であることに変更し、submdspan_mapping(input.mapping(), slices...)が適格であることだけをMandateするようにする
      • この制約はmapping-sliceable-with-full-extentsという説明専用コンセプトで表現される
  • スライス可能なマッピングに関する要件についてのセクションを更新
    • sliceable layout mapping要件と、標準マッピングに関する例外を追加
  • <mdspan>submdspan_canonicalize_slices()を追加
  • 文言を含まないAbstractとその他セクションを更新し、これらの変更を実際に提案していることを明記
  • submdspan_canonicalize_slices()が説明専用ではない理由を説明するためのセクションを追加
  • P2781 (std::constant_wrapper) の参照をR8に更新
  • 標準レイアウトマッピングは、正規化されたスライス型だけではなくすべての有効なスライス型を受け入れる可能性があることを説明する注記を追加
    • 正規化によるパフォーマンスへの懸念に対処するため
  • 正規化済スライス型に関する要求事項記述時の重複を避けるため、check-static-bounds()を定義

などです。

P3668R1 Defaulting Postfix Increment and Decrement Operations

後置インクリメント/デクリメント演算子default定義できるようにする提案。

以前の記事を参照

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

  • デフォルト関数ですでに実行可能な機能についての説明を追加
  • テンプレートのデフォルト化によってボイラープレートをさらに削減できる可能性について説明を追加
  • 文言のtypo修正

などです。

P3669R1 Non-Blocking Support for std::execution

operation_stateの開始操作がブロッキングするかどうかを取得するインターフェースを追加する提案。

以前の記事を参照

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

  • concurrent-op-stateを削除
  • try_scheduleを追加

などです。

P3670R1 Pack Indexing for Template Names

テンプレートパックに対するインデックスアクセスを許可する提案。

以前の記事を参照

このリビジョンでの変更は例を修正したことのみです。

P3676R0 Enhanced inline Keyword with Configurable Inlining Levels

様々なレベルのinline指定を可能にする提案。

関数に対してinlineを指定してもその関数をインライン展開してほしいという要望をコンパイラに表明するだけで、インライン化が必ず行われるわけではありません。そのため、パフォーマンスを重視するプログラムやライブラリでは関数のインライン化を強制するためにALWAYS_INLINEマクロのようなものを良く使用します。これは、各コンパイラ固有の機能などをマクロでラップして疑似的にポータブルにしたものです。

また逆に、インライン展開無効化するために同様のアプローチが取られる場合もあります。

この提案は、現在のinline指定ではサポートできていないこれらのユースケースを標準で提供しようとするものです。

提案ではinline指定構文を拡張して整数を渡せるようにすることで、プログラマが望むインライン展開のレベルをコンパイラに伝達できるようにします。提案されているレベルは3種類です

  • inline(0): インライン展開しない
  • inline(1): 現在のinlineと同等(ヒントとしてのinline指定)
  • inline(2): 常にインライン展開する
inline void f1() { ... }    // 現在のinlineと同等
inline(1) void f2() { ... } // 現在のinlineと同等
inline(2) void f3() { ... } // 常にインライン展開する
inline(0) void f4() { ... } // インライン展開しない

inline(expr)にはnoexceptexplicitbool値を渡す場合と同じく式によって指定することができます。これによってライブラリの利用ユーザーがインライン展開のレベルを選択できるようになり、インライン展開のレベルを調整するためにマクロによる条件付きコンパイルに頼る必要がなくなります。

template <int Mode>
inline(Mode) int heavy_function(int x) {
  return complex_calculation(x);
}

int forced = heavy_function<2>(10);     // strong request to always inline
int optional = heavy_function<1>(10);   // normal inline hint
int none = heavy_function<0>(10);       // request no inlining

提案ではさらに、これら3段階のインライン展開レベルを指定する定数を標準ライブラリで提供しておくことも提案しています。名前は、std::noinline, std::normal_inline, std::always_inlineの3つで、これによって単なる整数値よりも意図が明瞭になります。

inline(std::always_inline) int add(int a, int b) {
  return a + b;
}

inline(std::noinline) int slow_function(int x) {
  // 一定程度複雑なロジックはインライン化しないようにしたい
  return complex_calculation(x);
}

この提案の後でも現在のinline指定はそのまま使用可能であり、inline(1)と同等として扱われることで既存コードの動作は変更されません。

さらに、モジュールからはマクロをエクスポートできないことによって、ALWAYS_INLINEのようなマクロを使用している既存のライブラリをモジュール対応させようとする場合に問題となることが知られていますが、この提案のソリューションは完全にマクロフリーになるためこの問題を解消でき、より高パフォーマンスのライブラリをモジュール化しやすくなります。

この提案の著者の方はGlazeという高パフォーマンスJSONライブラリの開発者の方で、GlazeライブラリにおいてALWAYS_INLINEを使用するとパフォーマンスを10~30%向上させられるもののコンパイル時間が増大する(MSVCで最大10倍)ことを経験しており、この提案の機能によってライブラリ利用者がパフォーマンスとビルド時間(+バイナリサイズ)のどちらを選択するかをコンパイル時のオプションとして提供できるようになる、と述べています。

P3677R0 Preserving LC_CTYPE at program start for UTF-8 locales

プログラム起動時のデフォルトのロケール設定で環境のUTF-8エンコーディングを保持できるようにする提案。

C言語ロケールのデフォルトはsetlocale(LC_ALL, "C")が呼び出された場合と同じ振る舞いをすることが規定されており、C(およびC++)のプログラムは開始時にこれが呼び出されたかのように動作します。"C"ロケールエンコーディングについては規定されていませんが、多くの場合はASCIIエンコーディングが使用されます。

ロケールは多言語ローカライズに関する側面(プログラムが想定する環境の言語・文化圏)と文字エンコーディングに関する側面(プログラムが想定する環境のエンコーディング)が融合される形で設計されてしまっており、"C"ロケールは主にローカライズに関して何も指定しない(プログラムをローカライズフリーにする)デフォルトのモードを志向したものです。"C"ロケールはC標準内ではほぼ何も規定されておらず(ctypeの関数の動作のみを規定している)、主にPOSIX規格で定義されています。ただし、"C"ロケールエンコーディングの側面についてはPOSIXでも規定されていません。

しかし、"C"ロケールエンコーディングとしてはASCIIがほぼ実装のデファクトとなっているため、"C"ロケールを指定するとローカライズに関してデフォルトを指定するだけではなく、エンコーディングをASCIIに設定することになってしまいます。これによって、現代のほとんどのシステムの環境エンコーディングUTF-8であるにもかかわらず、C/C++のプログラムのエンコーディングはASCIIに設定されてしまい、これが文字化けの原因にもなっています。

このような問題は既によく知られており、ローカライズに関しては"C"ロケールを使用し、エンコーディングとしてUTF-8を使用するロケール指定C.UTF-8ロケールが一部のlinuxディストリビューションGlibcで提供されるようになっています。これを使用するとロケールローカライズに関してを指定せずに(ローカライズフリーを保ったまま)環境エンコーディングだけを指定できます(en_US.UTF-8などの指定はローカライズも指定してしまう)。ただし、Cの標準では"C"ロケールがデフォルトとして規定されたままになっているため完全には問題が解決されていません。

この提案は、このCの標準ライブラリが規定するデフォルトのロケールに、可能であればC.UTF-8ロケールを使用することを提案するものです。これによって、C/C++のプログラムが起動時に環境のUTF-8エンコーディングを保持できるようになります。

提案では、次の条件を満たす場合にロケールフリーなUTF-8ロケールC.UTF-8ロケール相当)をデフォルトのロケールとして使用するようにします

  1. C UTF-8 ロケールが存在する
  2. 環境に関連付けられたロケールsetlocale(LC_CTYPE, ""))もUTF-8ロケールである

曖昧な書き方になっているのは、C.UTF-8ロケールが必ずしも存在しない場合があるためです。また、2のチェックによって環境のエンコーディングを保持するようにデフォルトを設定するようにしています。

とはいえこの提案による影響があるのはC.UTF-8ロケールが存在する環境のみです。また設定されても影響を受けるのは一部の変換関数(mbtowc()fputws()など)と環境エンコーディングクエリのみが影響を受け、ctypeの関数の動作には影響しません。Windowsの場合はWindowsAPIがローカライズエンコーディングを分離して扱っていることやデフォルトのエンコーディングUTF-8ではないことなどから影響を受けません。

この提案は正確にはC標準(Cライブラリ)に対してのもので、C++にも影響があることからSG16/SG22に対しても提出されているものです。

P3678R0 Arbitrary attributes in define_aggregate

リフレクションAPIdefine_aggregate()において、非静的メンバ変数への属性適用方法を改善する提案。

define_aggregate()は集成体型の定義を生成できる関数です。この関数は不完全クラス型のリフレクションと、その非静的メンバ変数のプロパティを指定するオプション列を受け取って、集成体型を定義します。

template<typename T> struct S;
constexpr auto s_int_refl = define_aggregate(^^S<int>, {
  data_member_spec(^^int, {.name="i", .alignment=64}),
  data_member_spec(^^int, {.name=u8"こんにち"}),
});

// S<int>はこのような定義と等価に定義されている
template<> struct S<int> {
  alignas(64) int i;
              int こんにち;
};

この関数の呼び出しは定義行い、戻り値はそのクラス型のリフレクションです。

define_aggregate()は現在のところ非静的メンバを定義する能力しかなく、data_member_spec()という関数を通してdata_member_options構造体によってメンバ変数の定義を調整します。data_member_optionsは次のような構造体です

struct data_member_options {
  struct name_type {
    template <typename T>
      requires constructible_from<u8string, T>
    consteval name_type(T &&);

    template <typename T>
      requires constructible_from<string, T>
    consteval name_type(T &&);
  };

  optional<name_type> name;
  optional<int> alignment;
  optional<int> bit_width;
  bool no_unique_address = false;
};

現時点では属性のリフレクションを取得できないため、ここでは属性によるプロパティの指定を個別のbool値によって指定しています(alignmentno_unique_address)。しかし属性として有効なのはこの2つだけではなく、今後も増える可能性があります。また、P3385R4(標準属性のリフレクションを取得可能にする提案)がC++26に間に合いそうな進行状況です。

この提案では、define_aggregate()において非静的メンバ変数を定義する際に属性の指定については属性リフレクションの範囲によって指定できるようにする事を提案しています。このために、data_member_options構造体のメンバを調整します。

struct data_member_options {
  struct name_type {
    template <typename T> requires constructible_from<u8string, T>
      consteval name_type(T &&);

    template <typename T> requires constructible_from<string, T>
      consteval name_type(T &&);
  };

  optional<name_type> name;
  optional<int> alignment;
  optional<int> bit_width;

  // 追加・変更
  //bool no_unique_address = false;
  [[deprecated]] bool no_unique_address = false;
  vector<info> attributes;
};

define_aggregate()ではこのattributesに指定された属性のリフレクションから定義する非静的メンバ変数の宣言に属性を付与するようにします。これにより、[[no_unique_address]]だけではなく任意の属性を一貫した構文によって指定できるようになります。

ただしアライメント指定に関しては、alignasがそのほかの属性と異なるものでありこのリフレクションを取得する拡張は提案されていないため、現状維持しています。

提案文書より、この提案の後のdefine_aggregate()使用例

struct User;
constexpr auto r = define_aggregate(^^User, {
  data_member_spec(^^string, {
    .name = "uuidV4",
    .attributes = { ^^[[deprecated("Use UUIDV5 instead")]], ^^[[maybe_unused]] }
  }),
  data_member_spec(^^string, {
    .name = "uuidV5"
  }),
});

// ↑はこのような構造体定義と等価な定義を生成する
struct User {
  [[deprecated("Use UUIDV5 instead"), maybe_unused]] string uuidV4;
  string uuidV5;
};

この提案はSG7のレビューでP3385にマージする事に合意され、そちらにマージされたようです。

P3679R0 SFINAEable constexpr exceptions

コンセプトの制約評価時の例外送出が、その制約を満たさないこととして扱われるようにする提案。

C++26ではリフレクションにおけるエラー報告メカニズムのために、定数式における例外送出とそのcatchがサポートされるようになります。そこでは、定数式中で送出され、キャッチされなかった例外はコンパイルエラーを起こします。

定数式中での例外送出は、定数評価中に呼び出された任意の式・関数内でthrowによって行われる可能性があり、コンセプトの制約式の評価中にも例外送出が発生する可能性があります。この場合、例外による脱出がコンセプトの制約式まで到達するとコンパイルエラーになります。

template <typename... Ts>
constexpr bool throw_reason(std::format_string<Ts...> f, Ts &&... args) {
  throw exception{std::format(f, std::forward<Ts>(args)...)}; // std::format()が定数式で使用可能であるとする
}

template <typename T>
concept fits_into_four_bytes = 
  sizeof(T) <= 4 ||
  throw_reason("provided type `{}` is larger than 4 bytes!", display_string_of(^^T));

int overload(special_type value); // #1、special_type専用オーバーロード
int overload(fits_into_four_bytes auto value);  // #2、4バイト以下の型に制約されたオーバーロード
int overload(auto &&);  // #3、最も制約の緩い(というか無い)オーバーロード

int main() {
    overload(special_type{}); // ok、#1が選択される
    overload(42);             // ok、#2が選択される
    overload(new int);        // ng、ポインタ型は8バイトであり、`throw_reason()`を制約式として評価することで例外が送出される
}

この提案は、制約式の評価中に例外が送出された場合、その例外送出はその制約を満たさないこととして扱われるようにすることを提案するものです。これによって上記の最後の例はコンパイルエラーにならずに残りのオーバーロードの候補を探しに行くようになります。

int main() {
    overload(special_type{}); // ok、#1が選択される
    overload(42);             // ok、#2が選択される
    overload(new int);        // ok、#3が選択される
}

そして、この場合に最も外側の制約式まで達した例外のメッセージ(.what())をコンパイラの警告/エラーメッセージとして出力するようにすることで、コンセプトの制約チェック時のエラーメッセージをかなり改善することができます。

P3681R0 char_traits: Stop the bleeding

std::char_traitsを使用しないようにしていく提案。

std::char_traitsは標準ライブラリの文字列型(std::stringstd::string_viewなど)で使用される文字型に関する特性を文字列型に対して調整するためのクラスです。基本的には2つ目のテンプレートパラメータで渡して使用しますが、ユーザーがこれをカスタマイズすることはかなり稀だと思われます。

char_traitsには使用機会が乏しい以外にもいくつかの問題があります

  • マルチバイトエンコーディングの不適切な処理
    • char_traitsの全ての要件は基本的に単一の値単位での操作に特化して規定されており、comparecopyなどのシーケンスを操作するメンバー関数であっても例外ではない。これにより、マルチバイト文字列の変換に使用すると正しく動作しない可能性がある
    • シフト状態エンコーディングや非UTFマルチバイトエンコーディングの場合、比較も不正確になる可能性がある
  • コードユニットと無関係な機能の提供
    • assignlengthmovecopyのデフォルト以外の実装のユースケースを想定するのが困難
    • not_eofeofなどの機能はiostream関連機能にのみ有用であり、バイト読み取りとエンコーディングの懸念を混同している
      • これらのデフォルト以外の実装は不明
  • 肥大化(ブロート)の促進
    • char_traitsが存在することでマングル名が肥大化する場合がある
      • 例えばMSVC ABIの場合、std::unordered_map<std::zstring_view, std::zstring_view>では無い場合と比べてマングル名が25%大きくなる
      • Itanium ABIの場合は重複する型名を圧縮するため、それほど問題にならない
    • char_traitsに有用性がなければ、これは正当化できない
  • テキスト関連概念の教育の悪化
    • 現代のエンコーディングとの互換性がなく、テキストエンコーディングを混同する設計になっていることで、char_traitsは不適切なテキスト処理を助長している
    • 値のシーケンスを操作するAPIを公開するように設計されているstd::string等のAPIと整合していない

これらのことからこの提案は、今後char_traitsを使用しないようにすることを提案しています。特に、提案中のzstring_viewにおいて使用しないようにすることを推奨しています。また、今後のアクションとして次の事を提示しています

  1. 新しい文字列関連型でchar_traitsを使用しない
    • ただし、既存文字列型からの変換時にはchar_traitsをどう扱うかで問題がある
  2. char_traitsを不要にする
    • char_traitsの唯一の価値は、null終端文字列の長さを計算する機能
    • ただしそれも、char_traits<T>::length(str)がそのための最も直観的な方法ではない
    • 全ての文字型について、strlen()constexprオーバーロードが必要
  3. char_traitsの不適切なユースケースを置き換える機能を追加する
    • char_traitsが使用されることが比較的多いユースケースとして、文字列の大文字小文字を区別しない比較がある
      • 前述のように、これはマルチバイト文字列で機能しない
    • このようなユースケースは正当であり、現在の標準ライブラリではこれが簡単にできないことが問題であるため、正しく機能するより簡易な方法を提供する必要がある
  4. ユーザー提供のTraits引数の非推奨化
    • std::basic_string<CharT, Traits, Alloc>において、Traits引数がデフォルトと異なる場合を非推奨とする
      • 例えばstd::basic_string<char, MyTrait<char>>は非推奨
    • これをすべてのTraits引数を持つ文字列型に対して行う

より長期的には、ユーザー提供のTraits引数を与えられた文字列型をill-formedとすることを挙げていますが、後方互換性の懸念から削除はできないだろうとしています。この提案ではあくまで、char_traitsを不要なものとして認識して使用しないようにしていくことを推奨しているもので、削除しようとはしていません。

P3682R0 Remove std::execution::split

std::execution::splitアルゴリズムを削除する提案。

std::execution::splitC++26で導入されたstd::executionの一部であるsenderアルゴリズムで、senderによる処理グラフを分岐させるためのものです。sendersplitに渡すと、そのsenderを複数回呼び出し可能にしたsender(multi-shot sender)が得られます。これをwhel_allなどに渡すことで処理のフォークのようなことが可能になります。

std::execution::splitはシングルショットのsnderをマルチショットのsenderに変換するためのものですが、実際にはマルチショットのsenderに適用すると恒等変換にはならず、入力のsenderがマルチショットかどうかによらず同じ変換を行い、共有状態に関連付けられたsenderを返します。このsplitsenderstartすると、次のようなことが起こります

  1. 共有状態が完了状態を保持しているかどうかを調べ、完了の場合はその状態を用いてsplitsenderの完了とする
  2. そうではない場合、共有状態が進行中の操作を保持しているかを調べ、その場合はその完了を待機した後その結果をもってsplitsenderの完了する
  3. そうではない場合、splitの入力senderを用意したreceiverと接続して開始する
  4. 3の処理が完了したら、その結果を共有状態に格納し、2で待機していたすべての処理を起動する

このような動作のために、splitにはいくつか問題があります。

  1. メモリの動的確保を行う
    • splitsender初期化時に、共有状態の作成のために動的な確保を行う
      • 次のものを保持する
        • 入力sender
        • 入力senderをコピーして作成されたすべてのsender
        • これらのいずれかがconnectされて生成されるoperation_state
    • これはsender作成時に行われるため、接続したreceiverの環境のアロケータによってカスタマイズできない
  2. 共同所有権
    • splitに関連付けられた状態は共有され、これの保護のために参照カウント等の仕組みが必要になる
      • これは本来std::executionの提供するstructured concurrencyによって回避できるはずのもの
    • 共有状態はsplitsenderが接続された後のoperation_stateの生存期間と紐づけられる
      • これにより、途中にsplitがあるsenderチェーン全体の生存期間が終了するまで維持されている
  3. 貪欲な実行
    • 通常senderチェーン遅延評価されるが、splitは見ようによってはそうではない
    • splitsenderを最初に起動するものから見ると遅延実行だが、2回目以降に起動するものから見ると既に起動済みであり、遅延実行ではない
  4. 名前
    • 非常に短い名前だが、それに反して非常に複雑で難解な操作になっている

これらの問題から、この提案はstd::execution::splitを削除することを提案しています。提案には、splitとほぼ同等に振舞いつつ上記のような問題を回避する実装が掲載されています。

// splitによる処理のフォークの記述例

// フォーク対象の処理
std::execution::sender auto shared = /* ... */; 
// split sender
std::execution::sender auto split = std::execution::split(std::move(shared)); 

// フォーク後の処理
std::execution::sender auto a = split | /* ... */; 
std::execution::sender auto b = split | /* ... */; 
std::execution::sender auto c = std::move(split) | /* ... */; 

// 実行(どう実行されるかはscheduler次第)
(void)std::this_thread::sync_wait( 
  std::execution::when_all( 
    std::move(a), 
    std::move(b), 
    std::move(c))); 

このコードには先ほど述べたような問題(共有状態の存在やそのための動的確保など)があります。

// splitを使わない処理のフォークの記述例

// フォーク対象の処理
std::execution::sender auto shared = /* ... */;

(void)std::this_thread::sync_wait( 
  std::move(shared) | std::execution::let_value([](auto&&... values) {
    // let_valueアルゴリズム内で、先行操作を共有するフォーク後の処理を定義する
    std::execution::sender auto a = std::execution::just(std::ref(values)...) 
      | /* ... */; 
    std::execution::sender auto b = std::execution::just(std::ref(values)...) 
      | /* ... */; 
    std::execution::sender auto c = std::execution::just(std::ref(values)...) 
      | /* ... */; 
    
    // let_valueアルゴリズム内で実行
    return std::execution::when_all( 
      std::move(a), 
      std::move(b), 
      std::move(c)); 
  }));

こちらのコードはC++26のstd::executionで提供される他のアルゴリズムのみを使用しており、splitを使用しないで同等の処理を実現しています。このコードでは共有状態が存在せず、動的確保も発生しません。

この提案は、2025年6月の全体会議で承認され、C++26から削除されています。

P3685R0 Rename async_scope_token

std::execution::async_scope_tokenstd::execution::scope_tokenにリネームする提案。

async_scope_tokenはP3149R9で提案されているもので、非同期スコープの非所有ハンドルを表現するコンセプトです。この命名については次のような異論があるようです

  1. P2300のエンティティには他にasync_プリフィックスを持つものがない
    • std::execution内のものは非同期対応が基本であるため、名前によってそれを区別する必要がない
  2. P3149では"async scope"という概念が繰り返し登場するが、関連する他のものはasyncを使用せずscopeを優先的に使用している(counting_scopeなど)

この提案は、これらの理由によりasync_scope_tokenscope_token命名変更しようとするものです。

この提案はLEWGのレビューで受け入れられ、P3149に直接マージされたようです。

P3686R0 Allow named modules to export macros

名前付きモジュールからマクロをエクスポートできるようにする提案。

ユーザーが定義した(module宣言によって定義された)モジュールの事を名前付きモジュールと呼びますが、名前付きモジュールからはマクロをエクスポートする方法が用意されていません。これは意図的にそう設計されたものです。

しかし、この提案では名前付きモジュールからマクロをエクスポートすることができるとメリットがある場合について報告しています。

  1. 異なるモジュールそれぞれでインクルードされているヘッダの処理の効率化
    • 少なくとも現在のclangの実装では、あるライブラリのヘッダを異なる複数のモジュールでインクルードしているとき、ヘッダの処理がそれぞれのモジュールビルドにおいて行われるため効率が落ちる
    • サードパーティーライブラリへの依存が多い場合、モジュール化することでビルドが遅くなる要因となる
    • マクロをエクスポートできるようにすることで、処理済みのヘッダを検知しスキップできるようになる
  2. 既存のヘッダベースライブラリからの移行を容易にする
    • 既存のライブラリをモジュールへ移行する際、マクロはその定義を別のファイルに移す必要があり、これによってライブラリの構造が変化する
    • マクロをエクスポートできれば、この手間は不要になる

これらのメリットを得るために、この提案は名前付きモジュールからマクロをエクスポートできるようにすることを提案しています。

マクロのエクスポートのためには、エクスポートしたいマクロを次のようなアノテーションで囲みます

export module A;
#pragma modules "export-macro-begin"
// この中にあるマクロがエクスポートされる
#define VALUE 43
#pragma modules "export-macro-end"

全てのマクロをエクスポートしたい場合は次のようにします

export module A;
#pragma modules "export-macro-all"
#define VALUE 43

モジュールからマクロをインポートするには、import宣言に[[try_import_macros]]を付与します

import A [[try_import_macros]];
static_assert(VALUE == 43); // OK

これがない場合はマクロは一切インポートされません。

import A;
static_assert(VALUE == 43); // FAIL

筆者の方はこのような機能をclangをフォークしたプロダクトで実装しており、既に3年間の実装・出荷の経験があるようです。この機能をclangのメインストリームへパッチを送ろうとした際に、提案にすることを勧められたためこの提案を提出したようです。

この提案はEWGのレビューにおいて関心を得られず、追及は停止されています。

P3687R0 Final Adjustments to C++26 Reflection

P2996のリフレクション機能に対する修正の提案。

ここではP2996R12の静的リフレクション機能に対して2つの小さい変更が提案されています。

  1. splice template argumentの削除
  2. using宣言をリフレクションできるようにする

1. Splice template argumentの削除

splice template argumentとは、スプライス式の結果をテンプレートパラメータとして直接埋め込むための機能です。

template <class P1, auto P2, template<typename> class P3>
struct mytemplate;

auto R1 = ^^int;
auto R2 = std::meta::reflect_constant(26);
auto R3 = ^^std::optional;

mytemplate<[:R1:], [:R2:], [:R3:]>;  // ok
// mytemplate<int, 26, std::optional> と等価

R1, R2, R3がこの例以外のものであっても、展開先のテンプレートパラメータの形式に沿った展開結果になる限り、splice template argumentsは有効となります。

一見なんてことの無い機能ですが、現在の規定によればこれはテンプレートパラメータに渡した時点でスプライスを展開するのではなく、テンプレートパラメータ名とスプライス自体を同等に扱って、テンプレート内部までスプライスのまま置換していくような指定がされているようです。

例えば次のようなコードにおいて

template <template <typename> class TCls>
auto tfn1() {
  return TCls<int>{};
}

template <std::meta::info R>
auto tfn2() {
  return tfn1<[:R:]>();
}

tfn2()においてtfn1<[:R:]>が有効であるためには、依存スプライステンプレート引数[:R:]tfn1で置換可能でなければなりません。P2996R12の規定 に従えばこれは、tfn1内の依存名TCls<int>をsplice-specialization-specifier[:R:]<int>に置換し、テンプレート引数名TClsをsplice-specifier[:R:]に置換する、ことを意味しています。

これによって、テンプレート仮引数名とそれに対して与えられる実引数であるsplice template argumentの高度な結合状態が生じます。

問題なのはこれをどのように実装すべきかということで、Clangの実装ではsplice template argumentをテンプレート名と同一視して扱うことで実装していました。これはP2996R7時点では適合実装でしたが、R12至る中でCWGの見解としてsplice-specifierは名前ではないという認識が明確になったことで変更されており、その後Clangの実装はこれに追随できておらず、適切な代替実装戦略を見いだせていません。

これは単なる文言表現上の問題とも言えますが、実際に実装においてこれが問題となっています。また、CWGのレビューではこのsplice template argumentに十分な時間を割かれていないとのことで、この提案ではsplice template argumentを削除することを提案しています。

では、splice template argumentを削除した場合、テンプレートパラメータにスプライス式を直接渡すことはできなくなるのかというとそうではなく、型スプライス(型名のリフレクションに対するスプライス)はtypename [:r:]として型テンプレートパラメータに渡すことができ、式スプライス(式のリフレクションに対するスプライス)はそのまま非型テンプレートパラメータに渡すことができます。

constexpr auto r1 = ^^int;
constexpr auto r2 = std::meta::reflect_value(42);

std::array<typename [:r1:], [:r2:]> arr;  // ok、std::array<int, 42> arr;と等価

冒頭のコードもsubstitute()メタ関数を使用することでほぼ同等に表現できます

template <class P1, auto P2, template<typename> class P3>
struct mytemplate;

auto R1 = ^^int;
auto R2 = std::meta::reflect_constant(26);
auto R3 = ^^std::optional;

[:substitute(^^mytemplate, {R1, R2, R3}):];  // ok
// mytemplate<int, 26, std::optional> と等価

したがって、P2996からsplice template argumentを削除しても大きな表現力の低下はなく、CWGがレビューすべき分量を減らすことができます。また、C++29以降で改めて導入することも可能です(現在直接のスプライス指定が式スプライスとして解釈されてNTTPにマッチすることによって、完全に後方互換を保った状態にはなりません)。

2. using宣言をリフレクションできるようにする

P2996R12の仕様では、クラス内で基底クラスのメンバをアクセス可能にするためのusing宣言をリフレクションできません。これはusing宣言が名前の導入を行うもので、型や値などのエンティティを導入するものではないためで、リフレクションの対象が存在しないとみなされるためです。

struct Base { int member = 0; };
struct Derived : private Base { using Base::member; };

Derived d;
auto p1 = d.member;  // ok
auto p2 = d.[:^^Derived::member:];   // ng
#include <meta>

struct Base {
  protected: int member;
  friend void fn();
};

struct Derived : private Base {
  using Base::member;
};

void fn() {
  constexpr auto ctx = std::meta::access_context::unprivileged();

  // すべてこのコンテキストからアクセス不可と判定される
  static_assert(!is_accessible(^^Base::member, ctx));
  static_assert(!is_accessible(^^Derived::member, ctx));
  static_assert(!is_accessible(^^Base::member, ctx.via(^^Derived)));
  static_assert(!is_accessible(^^Derived::member, ctx.via(^^Derived)));
}

auto p = &Derived::member;  // ok、問題なくアクセスできる

これらの例においては、^^Derived::memberという式はBase::memberエンティティを表しています。これはusing宣言は名前探索時にそれが参照する名前に自動的に置き換えられるためです。

最初の例では、d.[:^^Derived::member:]は不適格となります。なぜなら、これは全体としてd.Base::memberと記述しようとしているのと同じになるからです。

2つ目の例では、式is_accessible(^^Derived::member, ctx.via(^^Derived))はクラスメンバBase::memberDerived内で名前付けられている場合にグローバルスコープのある位置からアクセスできるかを問うものになっています。これはアクセス不可能であり、実際にDerived::Base::memberという記述が不適格であることからも分かります。

特に、現在の仕様では次の2点のクエリを行う方法がありません

  1. Base::memberというusing宣言がDerivedに存在すること
  2. Base::memberDerivedのスコープ内でアクセス可能な名前であること

つまりusing宣言自体とusing宣言によって可視になっている名前についてをリフレクションによってクエリする方法がありません。

この提案では、これらの点を解消するために、using宣言をリフレクションできるようにすることを提案しています。

このためにここでは次のようなことを提案しています

  1. using宣言に関する文言を変更し、これがエンティティを導入するようにする
    • これは、P2996における型エイリアスに関する変更に類似したもの
  2. using宣言に対応するエンティティプロキシと呼ばれるメンバを導入する
    • エンティティプロキシは、"underlying entity"(基底エンティティ)を持ち、それはusing宣言子で指名されている名前に対応するエンティティ
    • これはusing宣言にリフレクション対象を導入するための論理的なものであり、既存の動作を変更する意図はない
  3. そのオペランドが一意のエンティティプロキシを指定するリフレクション(^^Cls::id)はそのエンティティプロキシを表すリフレクションを返す
    • エンティティプロキシの基底エンティティを透過的に表さない
    • using宣言自体をリフレクションできるようになる
  4. エンティティプロキシはクラス/名前空間のメンバであるため、members_of()の返すリフレクションはエンティティプロキシを含めるようにする
  5. そのほかの既存のメタ関数もエンティティプロキシのプロパティをクエリできるように更新し、エンティティプロキシを識別するためのis_entity_proxy()メタ関数を追加する
  6. std::meta::dealias()はエンティティプロキシのリフレクションをその基底エンティティのリフレクションにマップするようにする

提案では、この2つめの変更についてはC++26に導入することを推奨しています。例えば

struct A { protected: int m; };
struct B : private A { using A::m; };

constexpr auto rm = ^^B::m;

このコードにおいてrm

  • AのメンバであるA::mを表す
  • Bのメンバであり、その基底エンティティがA::mであるメンバBを表す
  • 不適格

のいずれであるかを判断する必要があります。このとき、rmBのメンバを表すようにしたい場合C++26でそうするか、C++29でそうするためにこのクエリの式全体を不適格にする必要があり、いずれにしてもC++26でその変更を行う必要があります。

P3688R0 ASCII character utilities

ASCIIに関連する文字の判定・処理関数群を提供する提案。

charの値として表現されている文字ががASCIIコードにおいてどのカテゴリの文字に該当するのか、あるいは大文字と小文字の変換などのASCII文字処理関数は<cctype><locale>で提供されています。しかし、これらの関数はどこかしら使いづらいところがあります。

例えば<cctype>の関数群には次のような問題があります

  • ユニコード文字型(char8_t, char16_t, char32_t)がサポートされない
  • constexprではないため定数式で利用できない
  • charwchar_tの間で関数名が異なり、ダックタイピングが行えない
    • std::isalnumstd::iswalnumなど
  • charが符号付整数型の表現を持つ場合、入力値はunsigned charで表現できるかEOFでなければならない
    • 容易に未定義動作を引き起こす
    • charエンコーディングUTF-8の場合、これらの関数は非ASCII範囲外のコード単位の入力に対して未定義動作となる
  • EOF入力も処理してしまうことで、ゼロオーバーヘッド原則を破っている
  • 文字判定系関数の戻り値型はintであり、非ゼロの戻り値が判定がtrueであることを表す
    • boolの方が寛容的
  • 一部の関数は現在のCロケールを使用するため、そのチェックのオーバーヘッドが存在する

<locale>にある関数はこれらの問題の多くを解決するものの、ロケール依存であるという点が問題となります。

この提案は、既存の<cctype>にある文字の判定・変換関数をベースとしたより使いやすいライブラリ関数を提供しようとするものです。

提案する関数は次のものです

// <ascii>ヘッダで定義
namespace std {
  constexpr bool is_ascii(character-type c) noexcept;

  constexpr bool is_ascii_digit(character-type c, int base = 10);
  constexpr bool is_ascii_bit(character-type c) noexcept;
  constexpr bool is_ascii_octal_digit(character-type c) noexcept;
  constexpr bool is_ascii_hex_digit(character-type c) noexcept;

  constexpr bool is_ascii_lower(character-type c) noexcept;
  constexpr bool is_ascii_upper(character-type c) noexcept;
  constexpr bool is_ascii_alpha(character-type c) noexcept;
  constexpr bool is_ascii_alphanumeric(character-type c) noexcept;

  constexpr bool is_ascii_punctuation(character-type c) noexcept;
  constexpr bool is_ascii_graphical(character-type c) noexcept;
  constexpr bool is_ascii_printable(character-type c) noexcept;

  constexpr bool is_ascii_horizontal_whitespace(character-type c) noexcept;
  constexpr bool is_ascii_whitespace(character-type c) noexcept;

  constexpr bool is_ascii_control(character-type c) noexcept;

  constexpr character-type ascii_to_lower(character-type c) noexcept;
  constexpr character-type ascii_to_upper(character-type c) noexcept;

  constexpr character-type ascii_case_insensitive_compare(character-type a,
                                                          character-type b) noexcept;
  constexpr bool ascii_case_insensitive_equals(character-type a,
                                               character-type b) noexcept;
}

これらの関数はすべてconstexprかつロケール非依存であり、全ての文字型(charからchar8_tまで)に対するオーバーロードが提供されています。また、これらの関数はASCII文字に対して動作するものであるため、関数名にasciiを含むようにされています。

このうち、次の5つの関数は新しく追加されたものです

  • is_ascii()
    • 文字がASCII範囲内の文字であるかを判定する
  • is_ascii_bit()
    • 文字が二進数を表せる数字であるか('0'or'1')を判定する
  • is_ascii_octal_digit()
    • 文字が八進数を表せる数字であるか('0'~'7')を判定する
  • ascii_case_insensitive_compare()
    • 大文字と小文字を区別しない比較を行う
  • ascii_case_insensitive_equals()
    • 大文字と小文字を区別しない等値比較を行う

これらの関数はいずれもそれほど実装は難しくありません。しかし、ASCII文字の処理は非常に一般的なユースケースであることと標準ライブラリの既存の関数が使いづらいことなどから、標準ライブラリで使いやすいこれらの関数を提供する事に価値がある、としています。

P3689R0 Convenience functions for Random number generation

乱数生成を手軽に行えるようにするための関数群を提供する提案。

<random>では多数の乱数生成器や分布生成器が用意されており、ユーザーはその用途に応じて最適なものを選択して使用することができます。しかし、乱数生成器や分布生成器の選択はユーザーにとって難しい場合があり、また乱数生成器と分布生成器を組み合わせて使用するためのコードも冗長になりがちです。

例えば、1から6までの整数をランダムに生成する(さいころを振る)コードは次のようになります

std::random_device rd;
const auto seed = rd();
std::mt19937 engine{seed};
std::uniform_int_distribution<int> distribution(1, 6);
auto num = distribution(engine);

わざと冗長に書いてる所もありますが、基本的なやるべきことは変わりません。この例の様なかなり単純かつありふれたタスクにおいても、このような冗長なコードを書く必要があります。

また、乱数を正しく扱うことにも知識が必要ですが、乱数生成器と分布生成器の違いやその性質、シードの取得方法など、C++の乱数APIを正しく使用するためにも多くの知識が必要です。初学者にとって、このことはAPIを容易に誤使用する原因となります。

この提案は、乱数を使用する95%のユースケースを1行で満たせるような、乱数取得関数を追加することを提案するものです。

この提案のAPIでは、先ほどのさいころの例は次のように単純化されます

auto num = std::random(1, 6);

std::random()関数はいくつかの基本的な用途に対応したオーバーロードが用意されています

// (1a)
template<typename T, typename Engine = std::convenience_random_engine>
  requires std::floating_point< T > || std::integral< T >
T random(const T & lb, const T & ub);

// (1b)
template<typename T, typename Engine = std::convenience_random_engine>
  requires std::floating_point<T> || std::is_same_v<T, bool>
T random();

// (2)
template<typename T, typename Engine = std::convenience_random_engine, std::floating_point P>
  requires std::is_same_v<T, bool>
T random(P p = P(0.5))

// (3a)
template<std::ranges::random_access_range Range, typename Engine = std::convenience_random_engine>
std::ranges::range_reference_t<Range> random(Range && range);

// (3b)
template<typename T, typename Engine = std::convenience_random_engine>
T random(std::initializer_list<T> il);

// (3c)
template<std::random_access_iterator It, typename Engine = std::convenience_random_engine>
std::iter_reference_t<It> random(It first, It last);
  • (1a) 範囲[lb, ub)の一様分布によってT型の乱数を生成する
    • 整数型の場合、乱数範囲は[lb, ub]
  • (1b) lb = T(0), ub = T(1)で固定された(1a)
  • (2) 確率pbool値を生成する
  • (3a)(3b) 指定範囲からランダムに要素を1つ選択して返す

どの関数でも、使用する乱数エンジンを一番後ろのテンプレートパラメータでカスタマイズすることができ、std::convenience_random_engineはこれらの関数が使用するデフォルトの乱数エンジンの型エイリアスです。

正規分布に従った乱数生成を行う関数として、randn()も提案されています。

template<std::floating_point T, typename Engine = std::convenience_random_engine>
T randn(const T & mu = T(0), const T & sigma = T(1));

この関数は平均mu標準偏差sigma正規分布に従ったT型の乱数を生成します。

これらの関数は次のような共通した性質があります

  • スレッドセーフ
  • 異なるスレッドは同じ乱数を生成しない
    • スレッドごとに異なるシードを使用する
  • ある1つのスレッドにおける関数の使用は、同じ乱数を生成する
  • 生成される乱数はコンパイルごとに変化する可能性がある
  • 入力型によって出力型が決定される

この性質からわかるように、これらの関数はシード値や乱数エンジンを内部状態として持っており、なおかつそれはスレッドローカルに保持されています。そのような状態に対してシード値を設定する関数も用意されています。

// (S1)
template<typename Engine = std::convenience_random_engine, typename Seed>
void seed(const Seed & s);

// (S2)
template<typename Engine = std::convenience_random_engine, typename Seed>
void seed();
  • (S1) 呼び出されたスレッドのRNG状態に、指定したシード値sを設定する
  • (S2) 呼び出されたスレッドのRNG状態に、std::random_device{}()をシード値として設定する

提案にはこれらの関数の実装例が掲載されています。

P3690R0 Consistency fix: Make simd reductions SIMD-generic

std::simd専用となっている一部の操作をジェネリックにする提案。

C++26で導入されたstd::simdは通常のスカラ数値型を扱うのと同じようにSIMDベクトル型を扱うコードを書けるようにするものです。例えば、次のようなコードはスカラ型(整数型や浮動小数点数型、複素数型)とそれらを要素とするstd::simd型の両方でインスタンス化することができます

template<class T>
auto f(const T& x, const T& y, const T& z) {
  return x + std::sqrt(y) * std::pow(z);
}

std::simdによって可能になるこのようなコードの事をSIMDジェネリックSIMD-generic)と呼びます。これは、std::simdがほぼすべての演算子オーバーロードを備えており、なおかつ周辺の関数についてもオーバーロードされていることによって可能になっています。

ほぼstd::simd固有の操作についても同様にスカラ型に対するオーバーロードが用意されていることで、SIMDジェネリックなコードを書くことができます。例えば、bool値のリダクション操作は次のように書くことができます

template<class T>
bool all_lt(const T& x, const T& y) {
  return std::datapar::all_of(x < y);
}

しかし、一部の操作についてはスカラ型のオーバーロードが提供されていないことによってstd::simd専用となっており、SIMDジェネリックなコードを書くことができません。例えば、次のようなコードはstd::simd型では動作しますが、スカラ型では動作しません

template<class T>
auto calc_contribution(const T& x, const T& y) {
  return std::datapar::reduce(x * y);
}

これは、std::datapar::reduce()関数がstd::simd型に対するオーバーロードしか提供していないためです。特に、算術リダクション関数群において、同様にスカラ型のオーバーロードが提供されていません。

この提案は、これらの算術リダクション関数群にスカラ型のオーバーロードを追加することを提案しています。これによって、これらの関数を使用したコードもSIMDジェネリックに記述できるようになります。

対象は、std::dataparにあるreduce, reduce_min, reduce_maxの3つの関数です。これらのスカラ型オーバーロードは、単純に受け取った引数をそのまま返すだけになります。

P3691R0 Reconsider naming of the namespace for “std::simd”

std::simd名前空間構造を再考する提案。

std::simdおよび関連する関数などを配置する名前空間はP3287R2で検討された結果、std::datapar以下に配置されることになり、そのままC++26に導入されています。

namespace std::datapar {
  template<class T, class Abi = native-abi<T>>
  class basic_simd;

  template<class T, simd-size-type N = simd-size-v<T, native-abi<T>>>
  using simd = basic_simd<T, deduce-abi-t<T, N>>;

  template<size_t Bytes, class Abi = native-abi<integer-from<Bytes>>>
  class basic_simd_mask;

  template<class T, simd-size-type N = simd-size-v<T, native-abi<T>>>
  using simd_mask = basic_simd_mask<sizeof(T), deduce-abi-t<T, N>>;
}

SIMDクラス型はstd::datapar::simdから利用でき、マスク型も同様です。

しかし、dataparという名前空間名については少なからず異論があるようで、この提案はそれを再検討するものです。ただし、ここでは名前空間とその配置そのものは再検討せず、名前空間名とクラス名について検討しています。

dataparという名前はdata-parallelismの略語であり、今回新しく発明された名前でもあります。このなじみの無さに加えて、データ並列性はSIMDを包含する概念であるため、SIMDクラス型以外のものも含まれていると錯覚させてしまったり、datapar::simd<T>が奇妙に映るなどの批判がされています。

P3287R2で検討されていた際に有力だったもう一つの名前は、名前空間simdにしてstd::simd::simdstd::simd::simd_maskのように綴るものです。こちらは、simdという言葉が繰り返しになるのが忌避され、最終的にdataparが選ばれました。

この提案では、std::simd名前空間が本当に悪いものなのか(std::dataparが最良なのか)?について詳細に検討しています。その詳細な説明は省きますが、結果としてここでは次の事を提案(推奨)しています。

  1. 名前空間std::simdに変更する
  2. マスク型の名前を変更する
    • std::datapar::basic_simd_mask -> std::simd::basic_mask
    • std::datapar::simd_mask -> std::simd::mask

SIMD型はstd::simd::simd<T>になります。

さらに、このような命名規則のポリシーを確立するために、LEWGにおいて名前空間とその下のエンティティ名で同じワードが繰り返しになることを悪い習慣と見做す(あるいは完全に禁止する)かどうかを議論し投票する事を提案しています。

P3692R0 How to Avoid OOTA Without Really Trying

P3064R2を要約した文書、および現在の実装ではOOTAが起こらないことを明確化する提案。

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

P3064R2 How to Avoid OOTA Without Really Trying - WG21月次提案文書を眺める 2024年07月

P3064R2は以前に提出されていたものの結果的におよそ100Pに達する長大な文章になっており、その主張を理解することすら困難でした。それは委員会においても批判されたようで、この文書はP3064R2の内容を要約したものです。

その内容については変化がなく、メモリモデルの理論上起こりうるOOTAは現在のハードウェアやコンパイラに基づく実装においては起こらないことを説明しています。

また、提案としては、標準の中で実装がOOTAを起こさないようにすることを推奨している規定([atomics.order])に対して、このことを明記することを提案しています。

P3693R0 SG14: Low Latency/Games/Embedded/Financial Trading virtual Meeting Minutes 2025/04/09-2025/05/07

2025/04/09から2025/05/07の間に行われた、SG14のオンラインミーティングの議事録。

どのようなことを議論したのかが簡単に記載されています。

P3694R0 SG19: Machine Learning virtual Meeting Minutes to 2025/03/13-2025/05/08

2025年3月13日から5月8日に行われた、SG19のオンラインミーティングの議事録。

どのようなことを議論したのかが簡単に記載されています。

P3695R0 Deprecate implicit conversions between Unicode character types

ユニコード文字型の間の暗黙変換を非推奨にする提案。

ユニコード文字型(char8_t, char16_t, char32_t)の間では暗黙変換が可能であり、3つの型を相互に行き来することができます。しかし、それはあくまで値の変換でしかなく、文字表現を保持するものではありません。これによって、分かりづらいバグが発生する可能性があります。

constexpr bool contains_oe(std::u8string_view str) {
  for (char8_t c : str) {
    if (c == U'ö') // 👈 u8'ö'とすべきところをUを使用している
      return true;
  }

  return false;
}

static_assert(contains_oe(u8"ö")); // fails?!

このコードのc == U'ö'では左辺がchar8_t、右辺がchar32_tchar8_t -> char32_tの暗黙変換が行われてchar32_tの値として比較が行われています。これは値の比較としてはc == char32_t(0xf6)と同じ比較であり、0xf6UTF-8コード単位の値としては不正なのでこの比較は文字の比較としては常に失敗します。

constexpr bool contains_nbsp(std::u8string_view str) {
  for (char8_t c : str) {
    if (c == U'\N{NO-BREAK SPACE}') // 👈 u8を使用すべき
      return true;
  }

  return false;
}

static_assert(contains_nbsp(u8"\N{CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK}")); // OK?!

CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOKすなわちԠ (U+0520)は、UTF-80xd4, 0xa0エンコードされており、NO-BREAK SPACE、U+00A0)はchar32_t(0xa0)の値になります。どちらもchar32_tに変換されると、Ԡの2文字目で一致するためこの関数はtrueを返します。これは当然、望ましい動作ではなく、誤判定です。

constexpr bool is_umlaut(char32_t c) {
  return c == U'ä' || c == U'ö' || c == U'ü';
}

...

constexpr std::u8string_view umlauts = u8"äöü";
static_assert(std::ranges::find_if(umlauts, is_umlaut) != umlauts.end()); // fails?!

ここではis_umlaut()の引数に渡す際に(すなわちアルゴリズム実装の中で)char8_t -> char32_tの暗黙変換が行われており、後は最初の例と同じ理由で比較に失敗します。

これらの例は全て、縮小変換によって情報が失われるわけではないにもかかわらずバグを誘発しています。これは、文字が単なる整数値以上の意味論を持っているためです。しかし当然、逆方向の変換(char32_t -> char8_t)も同様にバグの発生源となります(これは縮小変換としてコンパイラが警告します)。

この提案は、このようなバグを防止するためにユニコード文字型間での暗黙変換を非推奨とすることで、それが起きているときにコンパイラの警告を発するようにしようとするものです。

これらの例は筆者の方が実際に出会ったものであり、理論上のものではないとしています。また、clangにはすでにこの警告が実装されているようです。

上記の例のように、この警告に出会ったとしても意図的でない場合はUu8に修正するだけで対応でき、意図的な場合はstatic_castで明示することで対応できます。

なお、ユニコードUTF-8/16/32いずれもASCII互換なため、ASCII範囲内の文字であれば暗黙変換が起きたとしても比較は意図通りに行われます。しかし、ユニコードの詳細が漏れ出す、プログラマはそれを覚えておく必要がある、ルールが複雑になる(教えづらくなる)などの理由で区別せずに非推奨とすることを提案しています。

P3696R0 Discovering Header Units via Module Maps

どのヘッダがヘッダユニットとして利用可能かを記述するモジュールマップを導入する提案。

C++20で追加されたモジュールにおいては、通常のヘッダをモジュールとして扱うヘッダユニットという機能があります。import <header_name>;のようにヘッダをインポートすることでヘッダユニットとして使用できるほか、ヘッダのインクルードをヘッダユニットのインポートに置き換えることもできます。

しかし、全てのヘッダがヘッダユニットとして利用できるわけではなく、ヘッダユニットとして利用可能なヘッダはコンパイラ実装に依存します(ただし、標準ライブラリのヘッダはヘッダユニットとして利用できることが規定されています)。また、インクルードをインポートに置き換えることができる機能についても実装定義であり、どのヘッダでこれがいつ行われるかは不透明な部分があります。

ヘッダユニットとしてインポートされたものと通常のインクルードされたものが同じ翻訳単位から見えてしまうとODRの問題を引き起こすため、ヘッダユニット周りの仕様には移植可能で一貫した共通の認識を持つことが重要です。しかし、現状それは得られておらず、主にビルドシステムに委ねられているものの、決定権を持つものが不在になっています。

ヘッダユニットとして使用可能なヘッダは、通常次の条件を満たすものです

  • 翻訳単位に単独でインクルードできる
  • インクルード後に参照される定義と宣言は、それ以前のプリプロセッサの状態に依存しない
  • 意味を変えることなく複数回インクルードできる
  • 翻訳単位によって異なる定義が可能な#defineの直後に#includeを記述しない
  • ヘッダが自身の定義宣言をすべて所有している

ヘッダの利用者がこのような判定を行うのは困難であり、ヘッダがヘッダユニットとして利用可能かどうか、またいつ利用可能かを指定するのに最適な人物はヘッダを作成した人です。しかし、それを伝達する統一した方法はありません。

この提案は、どのヘッダがヘッダユニットとして利用可能かを記述するモジュールマップを導入することを提案するものです。モジュールマップは、コンパイラがヘッダユニットとして利用可能なヘッダを発見するために使用されます。

ヘッダユニットは次のような構造のテキストファイルです

module M {     // 全てのモジュールはグローバルモジュールとして扱われるのではなく、名前を付ける必要がある

  header "M.h" // ヘッダユニットとして利用可能なヘッダを指定
  export *     // このモジュールがインポートするモジュールはデフォルトでは再エクスポートされないため、必要なら明示的に指定する
}

モジュールマップはコマンドライン引数からコンパイラに渡すことも、対象のヘッダと同じディレクトリに置いておいてコンパイラ(ビルドシステム)に見つけてもらうこともできます。

この機能はclangモジュールにおけるヘッダユニット実装の拡張機能として10年の経験がありますが、重要なことはこのような仕組みを標準化することで全てのツールでヘッダユニットの取り扱いについて一貫した動作を得られるようにすることです。

上記のモジュールマップ例はclangモジュールにおけるものであり、それをそのまま標準に採用することは提案していません。例えば、ヘッダユニットのモジュールに名前を付ける必要があり、デフォルトでは再エクスポートをしないようになっていますが、標準仕様としてはそれが望ましくない可能性があります。そのため、現段階では特定のフォーマットを提案しているわけではありません。

P3697R0 Minor additions to C++26 standard library hardening

堅牢化された事前条件をさらに追加する提案。

この提案はP3471R4で導入された標準ライブラリの堅牢化モードの対象を拡大しようとするものです。

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

提案しているのは次のものです

  • basic_stacktrace
    • current(skip, max_depth)
    • operator[]
  • shared_ptr<T[N]>
    • operator[]
  • view_interface
    • front()
    • back()
  • counted_iterator
    • operator*
    • operator[]
    • counted_iterator(i, n)
    • operator++
    • operator+=
    • operator-=
    • iter_swap(counted_iterator)
    • iter_swap(counted_iterator, counted_iterator)
  • common_iterator
    • common_iterator(const common_iterator<I2, S2>&)
    • operator=
    • operator==(const common_iterator&, const common_iterator<I2, S2>&)
    • operator-(const common_iterator&, const common_iterator<I2, S2>&)
    • operator*
    • operator->
    • operator++
    • operator++(int)
    • iter_move(const common_iterator&)
    • iter_swap(const common_iterator&, const common_iterator<I2, S2>&)

これらの関数は堅牢化を適用するにあたっての条件

  • 事前条件に違反するとメモリ安全性の問題が発生する
  • 呼び出し元には、チェックを実行するために必要な情報が全てある
  • チェックは定数時間で実行でき、オーバーヘッドが小さい

をすべて満たしているものです。P3471では単に見落とされたものと思われます。

P3698R0 Cross-capacity comparisons for inplace_vector

std::inplace_vectorの比較がそのキャパシティ指定によらず可能になるようにする提案。

std::vectorでは==<などの全ての比較演算子が定義されており、その比較は要素の辞書式比較によって行われ、std::vectorのプロパティの一部であるキャパシティの値は比較に参加しません。

#include <cassert>
#include <vector>

int main() {
  std::vector<int> a{1, 2, 3};
  std::vector<int> b{1, 2, 3};

  a.reserve(10);
  b.reserve(100);

  assert(a == b); // ok、パスする
}

std::inplace_vectorではキャパシティをテンプレートパラメータで指定するのですが、現在の仕様だと異なるキャパシティを持つinplace_vector同士の比較ができません。

#include <cassert>
#include <inplace_vector>

int main() {
  std::inplace_vector<int, 10> x{1, 2, 3};
  std::inplace_vector<int, 100> y{1, 2, 3};

  assert(x == y);  // ng、xとyが同じ型ではないため対応する比較演算子が見つからない
}

これは、std::inplace_vectorの比較演算子が次のように定義されているためです

template<class T, size_t N>
class inplace_vector {
  ...

  friend constexpr bool operator==(const inplace_vector& x,
                                   const inplace_vector& y) = default;

  friend constexpr synth-three-way-result<T>
    operator<=>(const inplace_vector& x, const inplace_vector& y) = default;

  ...
};

これらの比較演算子はキャパシティをテンプレート化していないため、キャパシティが同じstd::inplace_vector同士でしか比較できません(正確にはdefaultで定義することを明示的に指定されていないのですが、定義の指定もされていないためデフォルト実装を意図しているようです)。

この提案は、std::inplace_vectorの比較演算子をキャパシティに依存しないように変更することを提案しています。具体的には、次のように比較演算子をテンプレート化します

template<class T, size_t N>
class inplace_vector {
  ...

  template <size_t M>
  friend constexpr bool operator==(const inplace_vector& x,
                                   const inplace_vector<T, M>& y);

  template <size_t M>
  friend constexpr synth-three-way-result<T>
    operator<=>(const inplace_vector& x, const inplace_vector<T, M>& y);

  ...
};

そのうえで、比較の定義を指定するようにします(デフォルト実装を指定しない)。ただし、キャパシティの値が比較に参加するようになるわけではなく、要素の辞書式比較によって比較が行われる点は変わりません。

これによって、std::inplace_vector<int, 10>std::inplace_vector<int, 100>のようにキャパシティが異なるinplace_vector同士でも比較が可能になります。

P3699R0 Rename conqueue_errc

P0260のconqueue_errcをリネームする提案。

conqueue_errcはP0260で提案されている並行キューにおける特定の操作の戻り値の列挙型です。これは元々、例外処理の際に使用される列挙型だったためこの名前になっています(特に、errという言葉が入っている)。しかし、並行キューの提案は現在では独自の例外型を使用しなくなっており、この列挙値もキューの操作の結果がどういう状態なのかを表すものになっており、エラー状態を表すものではなくなっています。

この提案は、conqueue_errcという名前をより操作の状態を表す様な適切な名前に変更しようとするものです。

提案では名前の候補として、queue_stateconqueue_stateの2つを提示しています。

P3700R0 Making Safe C++ happen

C++を安全な言語へと進化させていくために、その検討・進捗のための枠組みの提案。

近年のプログラミング言語に対する安全性・セキュリティの要求の高まりを受けて、C++においてもより安全な言語に進化させるための検討や提案などが活発になっています。プロファイルがその最たる例ですが、C++という歴史のある言語を安全なものに変貌させるというのは1つの機能や提案で達成できるものではなく、いくつもの機能や議論の先にあるものです。

この提案は、C++における安全性向上の取り組みを体系的に整理し、その進捗をマッピングするための枠組みを提案するものです。ただし、具体的な機能や対策を提案するものではありません。

提案では、P2687を出発点にして安全性とは何でここで対象にするのはどの種の安全性なのか、安全なC++に求められる要件、そのアプローチなどについて説明しています。これらはプロファイル関連の文書等でも言及されていたことですが、この提案で特筆すべきなのは、安全なC++に向けて実行すべきタスクの枠組みとしてグリッド(表)を提案していることです。

この表は、列に対処すべき対象の安全性の領域を置き、行に実施すべきアクションの種類と使用する特定のツールを置いたものです。列はP3081で提案されているプロファイルの分類にほぼ対応しています。

ここで提示されている行要素は次のようなものです

  1. 翻訳単位や関数を横断する情報の伝達
    • プログラムに埋め込まれた仮定を表現し、コンパイラに伝達するための注釈が必要
      • 特に、関数や翻訳単位を超えて、そのような情報をコンパイラが利用できることが重要
    • 具体的には、関数の境界に事前条件(preconditions)や事後条件(postconditions)、ポインターの寿命などをアノテーションする能力など
  2. 言語の一部を削除する
    • 安全性を確保するために、安全ではない構成要素を削除する
      • 組み込み環境での動的割り当ての回避や、モダンC++におけるユーザーコードでのnew/deleteや生配列の使用の回避など
    • 言語のサブセット化は、現在すでに多くの人々が常に行っている基本的な作業であり、安全性を高めるためには言語のサブセット化を定義する方法が必要
  3. 削除された必須機能の代替を提供する
    • 削除される構成要素の安全な代替を定義するため、または代替が何であるかを明確に示すために、新しい機能定義の必要性を認識する
  4. 実行時エラーの処理
    • 環境やソフトウェアの種類、あるいはその所有者の要望に応じて、実行時の安全性の障害を適切に処理するための方法は大きく変化する
      • テレメトリーデータの収集、既知の安全な状態への移行、またはクラッシュなど
    • それらの要望に基づいたハンドリングを可能にする仕組みが必要
  5. 安全性の命名
    • ユーザーが関連する設定、チェック、およびプロパティをグループ化し、分かりやすく一貫性のあるポータブルな方法で有効化できるようにする必要がある
    • そのために、関連する設定やチェック等をグループ化して名前を付ける方法が必要

このような表を現実のものとするためには、まず各列を実装可能にするために必要なツールをすべて整備することから始めるべき、としています。その後、各列を個別の論文として提案し、それらのツールを統合的に活用することで、安全性の特定の側面に的を絞ったアプローチを試みていくことを推奨しています。

P3701R0 Concepts for integer types, not integral types

純粋に整数型のみを表現するintegerコンセプトの提案。

std::integralコンセプトは整数型を表現するものですが、実際には文字型やbool型およびCV修飾付きの型を整数型として扱っています。

template <std::integral T>
T add_integers(T x, T y) { return x + y; }

int main() {
  add_integers(true, true);      // OK?!
  add_integers('a', 'b');        // OK?!
  add_integers<const int>(1, 2); // OK?!
}

これはstd::signed_integralstd::unsigned_integralでも同様であり、整数型を意図する場合は少なくともbool型と文字型は除外したいことが多いはずです。

この提案は、これらの型を含まない純粋な整数型を表すstd::integerコンセプトを追加しようとするものです。

namespace std {
  // C++20からあるもの
  template<class T>
  concept integral = ...;
  template<class T>
  concept signed_integral = ...;
  template<class T>
  concept unsigned_integral = ...;

  // この提案で追加されるもの
  template<class T>
  concept integer = ...;
  template<class T>
  concept signed_integer = ...;
  template<class T>
  concept unsigned_integer = ...;
}

std::integerコンセプトはstd::integralコンセプトに対して、char等の文字型やbool型を含まず、int等整数型のCV修飾も含みません。

template <std::integer T>
T add_integers(T x, T y) { return x + y; }

int main() {
  add_integers(true, true);      // ng
  add_integers('a', 'b');        // ng
  add_integers<const int>(1, 2); // ng
}

また、C++標準文書内で整数型を指定する場合にstd::integralから上記のような型を取り除いた型を指定する際に、"signed or unsigned integer type"のような言葉を使用しているところを、"integer"という言葉に置き換えることを提案しています。ここでの"integer"はstd::integerコンセプトに対応し、"integral"はstd::integralコンセプトに対応した言葉になります。

文言の変更は、既存の"integer"という言葉はすべて"integral"に置き換えられ、"signed or unsigned integer"という言葉が"integer"に置き換えられます。

std::integerコンセプトの表す整数型からCV修飾された整数型を除いているのは意図的なもので、文言で使用した時にあまり便利ではないこと(既存の"signed or unsigned integer"がCV修飾を含まない、デフォルトを含むようにしてしまうと多くの場所でCV修飾を除くという注記が必要になる)、関数テンプレートの推論時に推論されないこと、integerコンセプト使用時にユーザーがvolatile intをサポートすることを意図するケースが非常に稀であること、などの理由でstd::integerにはCV修飾を含まないようにしています。

P3702R0 Stricter requirements for document submissions (SD-7)

HTML形式の提案文書について一定のルールを設ける提案。

提案文書の形式はpdf、html、プレーンテキスト、マークダウンのいずれかである必要があるものの、その他はあまり詳細に指定されていません。特に、HTMLはJavascriptCSSを組み合わせることでかなりの自由度を持ちますが、それがどの程度許されるのかは指定されておらず、現在は紳士協定として可能な限りシンプルでインタラクティブにはしないという暗黙のルールがあるようです。

また、仮にクッキーを使用する場合、ユーザーの同意を取らないとEUGDPRに抵触する可能性があります。

この提案は、HTML形式の提案文書について最低限のルールを設けようとするものです。具体的には次の事を提案しています

  • クッキーやローカルストレージなど、法的な問題のある機能の使用を禁止する
  • ISOのAIガイダンスに従って、AI生成コンテンツの使用を禁止する
  • 一部のアクセシビリティ要件を満たす事を必須とする
  • 提案文書内のインタラクティブ性の範囲を制限する
    • 特に提案する文言の部分
  • 文書が自己完結していることを要求する
    • リンク切れになって判読不能になってはならず、内部参照は<a href=#id>のような内部参照であるべき
  • 文書のエンコーディング要件を現代化(UTF-8を全面的に採用する

これらのことをSD-7という提案文書についてのルールの様な文書に対して提案しています。

P3703R0 Constness and Locking

mutexlock()メンバ関数constメンバ関数にする提案。

クラスの値に対する並行アクセスを許可しそれを保護するために、クラスメンバにstd::mutexを持ち、メンバ関数でロックを取得してクラスの値にアクセスするようにする、というパターンはかなり一般的なものです。

その場合でも、クラスの値を読み取るだけで変更しないようなメンバ関数は通常通りconst修飾されます。また、メンバ関数const修飾しておくことでメンバ関数がスレッドセーフであることを表明する習慣があります。しかし、mutexlock()メンバ関数const関数ではないためconstメンバ関数からはロック取得できなくなり、その対策のためにmutexメンバをmutableで宣言する必要があります(これを、“Mutable comes with Mutex” M&M Rule と呼ぶらしい)。

この場合実際にはmutexメンバはそのクラスの値に寄与しておらず、並行アクセスからの保護のために必要になっているだけで、そのクラスの値のconst性とも関係がないはずです。にもかかわらず、mutableという回避機構を使用しなければならないことは初学者にとって混乱の元であり、mutableconst回避のためのハックの様な仕組みであるという誤解を招いています。

この提案は、mutexlock()メンバ関数const関数にすることで、クラスメンバとしてmutexを持つパターンにおけるmutableの必要性を失くそうとするものです。

現在 この提案
struct BankAccount {
  double getBalance() const {
    shared_lock lck(mtx);
    return balance;
  }

  void setBalance(double d) {
    unique_lock lck(mtx);
    balance = d;
  }

  mutable shared_mutex mtx;
  double balance;
};
struct BankAccount {
  double getBalance() const {
    shared_lock lck(mtx);
    return balance;
  }

  void setBalance(double d) {
    unique_lock lck(mtx);
    balance = d;
  }

  shared_mutex mtx;
  double balance;
};

ここではこの変更をstd::shared_mutexstd::mutexに適用することを提案しています。その際、ABI破壊を回避するために、lock()メンバ関数は既存のもの(非const)を維持したまま、新しくconst版を追加するようにすることを提案しています。

また、同様の考察からmutexの比較についても変更を提案しています。

C++20の宇宙船演算子<=>)導入に当たってはデフォルト比較がmutableメンバを無視するかどうかがかなり議論され、結局無視するべきではないという結論で合意されています。しかし、ことmutexの比較については上記で説明したようにmutexはクラスメンバとして保持されていても通常その値に寄与することは無いため、デフォルト比較に影響すべきではないはずです。

しかし現在は、全ての標準mutex型は比較演算子を定義していないため、クラスメンバとして保有されているとそのクラスのデフォルト比較実装を妨げます。そこで、mutex<=>演算子std::strong_ordering::equalを返すようにして定義しておくことで、クラスに保持された際のデフォルト比較を妨げないようにすることも提案しています。

この提案はSG1のレビューで支持を得られず、リジェクトされています。

P3704R0 What are profiles?

プロファイル機能(フレームワーク)についての解説書。

この文書は、プロファイル機能のアイデアや目指すところ、その必要性と標準化の理由などを簡単に説明したものです。

P3705R0 A Sentinel for Null-Terminated Strings

文字列範囲内の終端文字(\0)を見つけるとそこで終端を示す番兵型の提案。

文字列リテラルによる文字列はそのまま自然にrangeとして扱うことができ、Rangeアダプタを適用することができます。しかしこの時、rangeとしての文字列リテラルには終端のnull文字が含まれてしまい、範囲としての終端はnull文字のさらに後ろになります。

using namespace std::string_view_literals;

static_assert(std::string{std::from_range, "Brubeck" | std::views::take(5)} == "Brube"); // ✅
static_assert(std::string{std::from_range, "Dave" | std::views::take(5)} == "Dave");     // ❌
static_assert(std::string{std::from_range, "Dave" | std::views::take(5)} == "Dave\0"sv); // ✅

"Dave"という文字列リテラルは文字列としては4文字ですが、範囲としては終端のnull文字を加えた5文字になります。そのため、rangeとして扱われる文脈(Rangeアダプタやfrom_rangeコンストラクタなど)では5要素のrangeとなります。このため、2・3番目の例で構築されているstd::stringはお尻にnull文字を含む5文字の文字列として構築されています(std::stringの付加するnull文字はこれとは別にさらに付加されている)。

std::stringstd::string_viewの場合、文字列リテラルから構築した時でもお尻のnull文字を除いた部分を有効な文字列として構築されるため、このような問題は起こりません。ただしその場合、どちらのコンストラクタでも終端のnull文字を線形に探索して文字列長を確定しようとすることで、文字列の長さNに対してO(N)の時間計算量がかかります。

constexpr std::string take_five(char const* long_string) {
  std::string_view const long_string_view = long_string; // read all of long_string!
  return std::string{std::from_range, long_string_view | std::views::take(5)};
}

この提案は、文字列を範囲として扱った時にnull文字を範囲の終端として扱うための特殊な番兵型null_sentinelを提案するものです。この番兵型を用いて文字列範囲をsubrangeで構築することで終端判定を遅延評価することができます。

constexpr std::string take_five(char const* long_string) {
  std::ranges::subrange const long_string_range(long_string, std::null_sentinel); // 構築はO(1)
  return std::string{std::from_range, long_string_range | std::views::take(5)}; // null文字を含まずに先頭5文字から構築
}

さらに、このような典型的な使用例をカバーするためのnull_termCPOも提案しています。

constexpr std::string take_five(char const* long_string) {
  return std::string{std::from_range, std::null_term(long_string) | std::views::take(5)};
}

null_termCPOはnull_term(E)のように呼ばれたときに、ranges::subrange(E, null_sentinel)を返します。

null_sentinelは状態を持たずに比較演算子==)だけを定義しており、比較演算子イテレータの値がデフォルト構築された値と等しいかを調べます。このため、null_sentinelは実は文字列範囲だけではなく任意の型の範囲に対して、その値型(iter_value_t)のデフォルト構築された値を終端として扱うことができます。

namespace std {

  struct null_sentinel_t {
    // input_iterator用
    template<input_iterator I>
      requires (not forward_iterator<I>) && 
           default-initializable-and-equality-comparable-iter-value<I>
    friend constexpr bool operator==(I const& it, null_sentinel_t) {
      return *it == iter_value_t<I>{};
    }

    // forward_iterator以上のイテレータ用
    template<forward_iterator I>
      requires default-initializable-and-equality-comparable-iter-value<I>
    friend constexpr bool operator==(I it, null_sentinel_t) {
      return *it == iter_value_t<I>{};
    }
  };

  inline constexpr null_sentinel_t null_sentinel;
}

この定義例にあるように、この提案では設計の選択肢としてinput_iteratorでしかないイテレータを参照で受けるためのオーバーロードを定義することを提案しています。これは一般にinput_iteratorがコピー可能ではないためですが、標準ライブラリではそれとは関係なくイテレータは常に値渡しで受け取るようになっています。

P3706R0 Rename join and nest in async_scope proposal

P3149で提案されているasync_scope関連機能のうち、joinnestの名前を変更する提案。

joinについて

std::thread::join()という既存の関数が既に存在しているものの、 非同期スコープのjoin()とはかなり異なる振る舞いをします。特に、後者は現在のスレッドをブロックしません。

// 新しいスレッドを開始する
std::thread t1([]{ /* some work */ });

// 現在のスレッドをブロックしてt1の完了を待つ
t1.join();
namespace ex = std::execution;

my_thread_pool pool;
ex::counting_scope scope;

// 非同期スコープ内実行する処理(のsenderを作成
auto snd = ex::transfer_just(pool.get_scheduler())
         | ex::then([] { /* some work */ });

// sndをscopeに関連付け、処理を開始
ex::spawn(snd, scope.get_token());

// これは意味のない呼び出し、戻り値のsenderは捨てられている
scope.join();

// scope.join()の結果をsync_waitすることで、非同期操作の完了を待機(ブロックする
this_thread::sync_wait(scope.join());

counting_scope::joinは、非同期スコープに関連付けられたすべてのsender(処理)が終了した場合に完了を通知するsenderを返す関数で、それ自体は何かをするものではありません。

このため、現在のjoin()という関数名はその動作を表現しておらず、誤解を招くものになっています。この提案では、これを.when_completed()に変更することを提案しています。

提案が受け入れられた場合上記の最後の行は次のようになります

this_thread::sync_wait(scope.when_completed());

nestについて

P3149の非同期スコープにおけるnest()は、処理(sender)をスコープに関連付けるものの処理の実行はすぐに行わない(遅延される)操作です(ex::spawn()はすぐに実行しようとする)。確かに、非同期スコープにネストした(内部で実行される)処理を指定するものであるものの、これはネストした非同期処理(非同期処理内部で呼び出される非同期処理)を定義することを意味していません(スコープそのものは非同期処理ではない)。それは通常、let_valueなどを使用して定義されます。

こちらも名前が適切ではないため、提案ではこれをassociate(またはattach)に変更することを提案しています。

LEWGの投票では、2つ目のnestassociateに変更する提案だけがコンセンサスを得たようで、P3149に直接マージされています。

P3707R0 A std::is_always_exhaustive trait

型のレイアウトがパディングビットを持たないことを検出する型特性、std::is_always_exhaustiveの提案。

ある型がそのレイアウトにパディングを持たないことを静的に検査するためには、C++17で追加されたstd::has_unique_object_representations<T>を使用することができます。この型特性は型の値表現(その型のオブジェクトの値を保持するビット列)とオブジェクト表現(バイト列)が正確に一致する型に対してtrueを示すもので、パディングビットはオブジェクト表現に含まれるが値表現には含まれないため、パディング有無の判定に使用できます。

ただ、この型特性はパディングビット判定のために導入されたものではないため、パディングビット判定には不向きな振る舞いをします。それは特に、浮動小数点数型に対してfalseを返すことです。

std::has_unique_object_representations_v<T>の定義は、型Tの2つのオブジェクトの値が等しいならばそのバイト表現も等しい場合にtrueとなります。浮動小数点数型には値として等しくてもバイト表現が異なるものが存在するためfalseを返します。典型的な例はNaNです。

struct Int16 { // note: 16 bytes, not bits 
  int i0, i1, i2, i3; 
};

static_assert(std::is_trivially_copyable_v<Int16>); 
static_assert( 
  sizeof(Int16) == sizeof(std::declval<Int16>().i0) + 
                   sizeof(std::declval<Int16>().i1) + 
                   sizeof(std::declval<Int16>().i2) + 
                   sizeof(std::declval<Int16>().i3) 
); 

struct Float16 { // note: 16 bytes, not bits 
  float f0, f1, f2, f3; 
};

static_assert(std::is_trivially_copyable_v<Float16>); 
static_assert( 
  sizeof(Float16) == sizeof(std::declval<Float16>().f0) + 
                     sizeof(std::declval<Float16>().f1) + 
                     sizeof(std::declval<Float16>().f2) + 
                     sizeof(std::declval<Float16>().f3)
);

struct Chunk {
  alignas(16) Int16 i16; 
  alignas(16) Float16 f16;  // エラーにならない
};

static_assert(sizeof(Int16)==16); 
static_assert(sizeof(Float16)==16);

// ok 
static_assert(
  std::has_unique_object_representations_v<Int16> 
); 

// ng 
static_assert( 
  std::has_unique_object_representations_v<Float16> 
);

この例のFloat16型はstatic_assertの検査がパスすることからも分かるように、明らかにパディングを持っていません。しかしstd::has_unique_object_representations_v<Float16>falseを返すため、パディングビットの有無を検査する目的には使用できないことが分かります。

この提案は、型のパディング有無を直接検査することのできる型特性std::is_always_exhaustive<T>を追加しようとするものです。

// ng 
static_assert( 
  std::has_unique_object_representations_v<Float16> 
);

// ok
static_assert( 
  std::is_always_exhaustive_v<Float16> 
);

なお、命名mdspanで使用されている同名のプロパティ取得関数からです(型のオブジェクトのバイトとメンバの対応関係の全射性による)。

P3709R0 Reconsider parallel ranges::rotate_copy and ranges::reverse_copy

並行アルゴリズム版のranges::rotate_copyranges::reverse_copyの戻り値についての再設計の提案。

P3179R7ではRangeアルゴリズムの並行アルゴリズムオーバーロードについての提案が行われています。そこにおいて特筆すべき設計点として、別の範囲に出力するタイプのアルゴリズムの一部では出力先として出力イテレータではなく出力範囲を取るようになることです。

これは、並行アルゴリズムでは入出力のどちらか一方(あるいは両方)で境界が分かっていることが望ましい場合が多いためです。しかしこの時に、入力のサイズに比べて出力のサイズが足りない場合に、そのアルゴリズムの戻り値として何を返すかが問題になります。

単純に済む場合は、入力イテレータのうち最後に処理された位置の次の位置を指すものを返すことです。ranges::copyの場合次のようになります

std::vector input{1,2,3,4,5};
std::vector<int> output(5);         // output with sufficient size
std::vector<int> smaller_output(3); // output with insufficient size

auto res1 = std::ranges::copy(std::execution::par, input, output);

// after copy invocation res.in == input.end() is true
assert(res1.in == input.end());

auto res2 = std::ranges::copy(std::execution::par, input, smaller_output);

// after copy invocation res.in == input.begin() + 3 is true
// res.in point to element equal 4
assert(res2.in == input.begin() + 3);

これにより、出力サイズが十分な場合は非並行アルゴリズムと同じ動作が得られ、不十分な場合は処理(コピー)されなかった入力の先頭への位置が得られます。

基本的には全ての並行アルゴリズムがこれに従っていますが少し変わった結果を返すアルゴリズムがあり、それがranges::rotate_copyranges::reverse_copyです。これらでは、非並行版と同じ戻り値型を使用してこの原則に従うことで、その他の出力を行うタイプの並行アルゴリズムとは一貫性がありますが、少し問題があります。

  • ranges::rotate_copy
    • middleによって指定された位置で分割された2つの部分範囲を持ち、処理が終了したときの最後のイテレータは入力のlastにはならない
      • 戻り値は、[middle, last - 1][first, middle]のどちらかになる
  • ranges::reverse_copy
    • 逆方向に実行されるため、処理が終了したときの最後のイテレータは入力のlastにはならない(入力が空ではない場合)

どちらの場合でも、そのまま処理が終了した位置の次を返すと決して入力範囲の終端にはならず、出力範囲のサイズが十分な場合にも非並行版の動作と一致しないことが問題になります。

このことが問題となるのは、次の3つの要因があるためです

  • 非並行版ではどちらも、入力範囲の最後lastを返す
  • 並行/非並行Rangeアルゴリズムの戻り値型は同一であるものの、出力サイズが十分な場合に並行版は入力に対して異なるイテレータを返す場合がある
  • 将来的に出力として範囲を取る設計の非並行アルゴリズムの導入が想定されており、この場合処理の終了地点と入力終端の両方を返すことができる

P3179での設計目標は並行版のアルゴリズムの戻り値型を可能な限り対応する非並行版と同じに保つことでしたが、これらのアルゴリズムでは別のものを返す必要があります。そのため、この提案ではこれらのアルゴリズムの戻り値についての設計変更を提案しています。

  • ranges::rotate_copy
    • 戻り値型を専用のin_in_out_resultin1, in2, outの3つのイテレータを返す型)のエイリアスに変更する
      • 出力が2つの部分範囲についての情報を含めるようにする
      • in1: [middle, last]内の停止位置
        • 出力範囲サイズが十分な場合はlast
        • 出力範囲サイズが十分でない場合は処理の停止位置(最後に処理した要素の次
      • in2: [first, middle]内の停止位置
        • 出力範囲サイズが十分な場合はmiddle
        • 出力範囲サイズが十分でない場合は処理の停止位置(最後に処理した要素の次
      • out: 変わらず(最後の出力位置の次
  • ranges::reverse_copy
    • 戻り値型を専用のin_in_out_resultin1, in2, outの3つのイテレータを返す型)のエイリアスに変更する
      • 出力が2つの部分範囲についての情報を含めるようにする
      • in1: 常にlast
      • in2: 処理の停止位置(最後に処理した要素
        • これは逆順に見た時の終了位置、最後にコピーした要素の位置
      • out: 変わらず(最後の出力位置の次

これはRangeアルゴリズムの戻り値についての基本原則である「計算した情報をなるべくすべて返す」ということと、上記のこれらのアルゴリズムにおける問題点を考慮し、なおかつ通常の非並行アルゴリズムから移行したときに戻り値の意味が変わることをコンパイルエラーとして報告できるようにすることを意図しています(特に、reverse_copyで戻り値型が変更されるのはその側面が強い)。

現在の非並行版ではin_out_resultという2要素の型を使用しており、メンバ変数名もその数も異なるため、autoで受けて使用していたり構造化束縛していたりしてもエラーになります。

P3710R0 zstring_view: a string_view with guaranteed null termination

null終端が保証されたstd::string_viewであるstd::zstring_viewの提案。

モチベーションなどは以前のP3655R0と共通するのでそちらを参照

これはP3655R0とは独立して出された同じものについての提案であるようです。基本的なアイデアもインターフェースもほとんど同じものですが、こちらの方がコンストラクタが詳細に検討されているほか、筆者の方による実装と展開の経験について触れられています。

この提案はP3655R2にマージされ、そちらに統一して検討していくことになったようです。

P3711R0 Safer StringViewLike Functions for Replacing char* strings

char*文字列の使用を排除するためのユーティリティ関数の提案。

この提案はP3566の方針を進めて、境界の無い文字列の使用を回避するために、std::string_viewメンバ関数の一部に対応するフリー関数を提案するものです。特に、P3566R1で提案されている[Safe|Unsafe]StringViewLikeコンセプトを使用することで安全な文字列操作とそうでないものを区別しようとしています。

SafeStringViewLikeコンセプトは境界が明確なstd::string_view-likeな型(P3566で提案されているような暗黙的にstd::string_viewに変換可能な型)を表し、UnsafeStringViewLikeはコンセプトは境界が明確なstd::string_view-likeな型(char*など)を表します。

この提案での安全な関数とは、その関数の引数によって定義される有界な範囲内でその処理を実行できること、と定義します。安全ではない操作に対しては、P3566に倣ってstd::unsafe_lengthタグの明示的な指定が必要になります。

ここで提案されているのは次のもので、全てフリー関数テンプレートとして提案されています。

  • starts_with(str, prefix): std::string_view::starts_with()と同等のもの
    • 最初の引数が有界であれば、安全
    • 最初の引数が有界でなくても、2番目の引数が有界であれば、安全
    • 両方の引数が有界でない場合、安全ではない
  • ends_with(str, suffix): std::string_view::ends_with()
    • 最初の引数が有界であれば、安全
    • 最初の引数が有界でなければ、安全ではない
  • join(strs...): 文字列を連結する
    • 全ての引数が有界であれば、安全
    • 1つでも有界出ないものがあれば、安全ではない
  • is_null_or_empty(char_ptr): 文字列ポインタがnullptrであるか空文字であるかを判定する
    • const CharT* sに対して、(!s) || (!*s)

starts_with()の例

// 安全な starts_with
template<SafeStringViewLike TString, UnsafeStringViewLike TPrefix> 
bool starts_with(TString&& s, TPrefix&& p) { 
  return string_view{forward<TString>(s)}.starts_with(forward<TPrefix>(p)); 
}

// 安全な starts_with
template<UnsafeStringViewLike TString, SafeStringViewLike TPrefix> 
bool starts_with(TString&& s, TPrefix&& p) { 
  return string_view{forward<TString>(s)}.starts_with(forward<TPrefix>(p)); 
}

// 安全ではない starts_with
template <UnsafeStringViewLike TString, UnsafeStringViewLike TPrefix> 
bool starts_with(carb::cpp::unsafe_length_t, TString&& s, TPrefix&& p) {
  return string_view{unsafe_length, forward<TString>(s)}.starts_with(unsafe_length, forward<TPrefix>(p)); 
} 

このような関数群はNVIDIA Omniverse Foundation Libraryというライブラリで使用され、コードベースでP3566で提案されている文字列実装に置き換えるために重要な役割を果たしてたとのことです。

P3712R0 2025-05 Library Evolution Polls

2025年5月に行われる予定の、LEWGにおける投票の予定。

次の提案が投票にかけられる予定です

これはC++29導入を目指してLWGに転送するための投票です。

P3714R0 Virtual values have Virtual Value

P3565R0の提案についての否定的な意見書。

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

この提案では、P3565R0で提案されている新しい浮動小数点数モデルがコンパイラの最適化にとって障壁となることを短く説明しています。明確に述べてはいませんが、P3565に反対を示しているようです。

P3715R0 Tightening floating-point semantics for C++

C++における浮動小数点数セマンティクスの厳密な指定のために理解すべき現在の浮動小数点数演算周りの事についてまとめた文書。

P2746を皮切りに、C++における浮動小数点数の動作・セマンティクスを明確化しようとする動きがあり、これを受けて委員会としても浮動小数点数のセマンティクス明確化に本腰を上げ始めています。

しかし、現在提出されている提案は浮動小数点数の全体ではなく一部の側面についてのセマンティクス明確化を行おうとする提案になっています。浮動小数点数に関する問題のほとんどは互いに関連があり、それについて認識・理解しないまま進めてしまうとある問題を解決するために別の問題を深刻化してしまったり、あるいは別の問題の改善を困難にしてしまったりする恐れがあります。

この文書は、浮動小数点数に関して包括的な理解を促進するために、現在の状況の確認や将来の提案の評価のために必要な情報と浮動小数点数への理解を提供しようとするものです。

この文書の後半では、浮動小数点数のセマンティクスの問題についての解決策空間の探索と、最適な解決策と思われる案についての筆者の方の個人的な見解を提示しているものの、まだ何かを提案してはいません。

P3716R0 Subsetting

C++言語のサブセットを定義し、それを強制適用できるようにするための機能の提案。

この提案はプロファイル提案などと目的は同じであり、C++の言語機能や構成要素のうちで未定義動作に繋がりうる危険な操作や使用が推奨されないものなどの使用を制限することで、C++コードの複雑性を低下させたり、安全性を高めようとするものです。

言語のサブセット化は標準がそれを回避しようとしているだけで一般の開発者は既に行っていることです。例えば、組み込み環境で動的メモリ確保を無効化する、例外やRTTIを無効化する、コンパイラオプションで特定の警告をエラーとして扱う、特定の言語バージョンに制限する、などです。

この提案は、このような言語のサブセットを定義する標準的な方法を提供し、それを強制することのできる言語機能を提供しようとするものです。

提案によれば、サブセットは次のいずれか1つ以上の方法で定義されます

  • 言語キーワードを完全に禁止する
  • 特定の型の使用を禁止する
  • 特定の関数の使用を禁止する
  • 特定の言語機能の列挙された集合を禁止する
    • 配列のdecay、Cの可変長関数、ポインタ演算など

これに加えて、サブセットは次のような能力を持ちます

  • サブセットのサブセットを定義できるようにする
  • 各サブセットは禁止するルールが抑制可能かどうかを示す
  • サブセットの集合はオープンエンドである必要があり、他の組織(MISRA, AutoSAR, LLVM, Microsoftなど)が独自のサブセットを定義できるようにする

サブセット機能の設計原則として次のことが挙げられています

  • コードはサブセットがない場合と全く同じようにコンパイルされて動作するか、コンパイルされないかのどちらか
  • サブセットは常に直交的に結合する
    • サブセット間に相互作用はなく、動作も変化しない
  • コンパイラとリンカは、プログラムの一部にサブセットで制限されたコードがあることで特別な情報や権限を得ることがない
  • サブセットの定義は、必ずしもWG21だけが行うわけではない
  • 特定のサブセット化ルールの抑制は、任意の大きさの抑制リストを必要とせずに移植可能である必要がある

これはプロファイル提案とほとんど同じことを目指しているようにも思えますが、プロファイルは言語のサブセット化を目指すものではありません。プロファイルはあくまで、安全性を向上させるために一部の操作が禁止されたり静的解析が保証されたりする、いわばSafeブロックをC++に導入しようとするものです。一方で、ここでのサブセット化はSafeブロックを導入するようなものではなく、安全性向上だけではなくC++の複雑さの低減も目指しており、サブセット化によって言語の表現力が低下することも許容します。

P3717R0 Update Annex E onto Unicode 16

C++標準文書のAnnex E(UAX31への参照)をUnicode 16に更新する提案。

C++規格書のAnnex Eは、P1949でのUAX31対応の際にC++にどのようにそれを適用し、またどこに適用していないかなどの選択を記録し説明したものです。

P1949採択と並行してUAX31の更新が行われており、P1949のC++導入後にユニコード標準が更新されUAX31にも更新がありました。別の提案のP3658ではそれによって識別子として使用可能になる文字を拡張することが提案されています。また、空白文字についての整理も行われているため、C++の構文上の空白文字についての扱いを整理・改善する機会にもなりえます。

この提案ではそのような変更を一切提案していませんが、Annex Eの内容をユニコード16とAUX31の更新に追随させ、特にユニコード標準から削除された「UAX31-R1a Restricted Format Characters」への適合状況に関する言及を削除することを提案しています。

SG16での議論によると、ここでの主な提案事項である「UAX31-R1a Restricted Format Characters」の削除に関してはCWG Issue 2843による解決を受け入れることになったようです。ただし、こちらのイシューではユニコードへの参照の更新は行っておらず、それはこの提案を利用して引き続き行われるようです。

おわり

この記事のMarkdownソース




以上の内容はhttps://onihusube.hatenablog.com/entry/2025/12/16/230316より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14