以下の内容はhttps://onihusube.hatenablog.com/entry/2026/01/31/012640より取得しました。


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

文書の一覧

全部で37本あります。

もくじ

N5020 2026-11 Búzios Meeting Information

2026年11月にブラジルのブジオスで行われる全体会議のインフォメーション。

予定(2026年11月16日~21日)と場所、ホテルの案内などが記載されています。

P2953R2 Forbid defaulting operator=(X&&) &&

右辺値修飾されたdefault代入演算子を禁止する提案。

以前の記事を参照

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

  • EWGの投票結果を追加
  • コンパイラ毎のdefault代入演算子に対する挙動の違いの表を更新
  • その他提案と文言の修正

などです。

P3347R5 Invalid/Prospective Pointer Operations

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

以前の記事を参照

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

  • R4の改訂履歴を追加
  • 著者のメールアドレス集成

などです。

P3567R1 flat_meow Fixes

flat_xxxな連想コンテナに対するバグフィックス提案。

以前の記事を参照

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

  • 文言の修正

などです。

P3579R2 Fix matching of constant template parameters when matching template template parameters

テンプレートテンプレートの特殊化のマッチング時に、NTTPの縮小変換を許可しないようにする提案。

以前の記事を参照

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

  • 文言の改善
  • 最新のWDにリベース

などです。

P3612R0 Harmonize proxy-reference operations (LWG 3638 and 4187)

vector<bool>::referencebitset<N>::referenceの間で一貫したIssue解決を行う提案。

この提案はvector<bool>::referencebitset<N>::referenceに関する次の2つのイシュー解決の際に、追加するswap()/operator=シグネチャを共通化しようとするものです。

P2321(views::zipの提案)ではviews::zipの動作のためにvector<bool>::referenceに代入演算子が追加されており、これは

namespace std {
  template<class Allocator>
  class vector<bool, Allocator> {
    public:  
    ...
     
    class reference {
      friend class vector;
      constexpr reference() noexcept;
    public:
      constexpr reference(const reference&) = default;
      constexpr ~reference();

      // bool型への暗黙変換
      constexpr operator bool() const noexcept;

      // 既存の代入演算子
      constexpr reference& operator=(const bool x) noexcept;
      constexpr reference& operator=(const reference& x) noexcept;
      
      // P2321で追加された代入演算子
      constexpr const reference& operator=(bool x) const noexcept;

      ...
    };
   };
}

プロクシ参照型はconstでも代入可能である必要があったもののvector<bool>::referenceconst修飾された代入演算子を持っていなかったため、後方互換性を維持しつつそれを可能にするためにconst修飾された代入演算子が追加されました。この時、その戻り値型はreferenceのprvalueではなく、const lvalueとされました(なぜかはよくわかりません)。

このvector<bool>::referencestd::swapが意図通りに動作しないためswap()のカスタマイズが必要です。例えば、vector<bool> vに対してstd::swap(v[1], v[2])が機能する必要があるものの、std::swapは非const参照しか取らないため呼び出せません。また、呼び出せたとしても、std::swapのデフォルト実装であるReference t = r1; r1 = r2; r2 = t;の様な交換操作はvector<bool>::referenceのコピーは参照のコピーになるため、参照先のbool値を交換しません。

LWG Issue 3638はこれについてのイシュー報告です。

一方、vector<bool>::referenceとほぼ同じ動作をするものに、bitset<N>::referenceがあります。

namespace std {
  template<size_t N>
  class bitset {
  public:
    ...

    class reference {
    public:
      constexpr reference(const reference&) = default;
      constexpr ~reference();
      
      // bool型への暗黙変換
      constexpr operator bool() const noexcept;                  // for x = b[i];
      
      // 代入演算子
      constexpr reference& operator=(bool x) noexcept;           // for b[i] = x;
      constexpr reference& operator=(const reference&) noexcept; // for b[i] = b[j];

      ...
    };
  };
}

こちらはP2321で変更されなかった(bitsetrangeではないためと思われる)ため、const修飾された代入演算子を持たず、swapについて同様の問題があります。LWG Issue 4187は前者(const代入演算子)についてのイシュー報告です。

これら2つの型には共通点が多くあるものの、両方のイシューの解決後も型のインターフェースは一貫していません。この提案は、この両方のイシューを合わせた形の一貫した解決を両方の型に適用しようとするものです。

すなわち次の変更を適用します

  • vector<bool>::reference
    • ADL swap()を追加
  • bitset<N>::reference
    • const修飾された代入演算子を追加
    • ADL swap()を追加

提案後の両クラスのインターフェース

namespace std {
  template<class Allocator>
  class vector<bool, Allocator> {
    public:  
    ...
     
    class reference {
      friend class vector;
      constexpr reference() noexcept;
    public:
      constexpr reference(const reference&) noexcept; // 👈
      constexpr ~reference();

      // bool型への暗黙変換
      constexpr operator bool() const noexcept;

      // 既存の代入演算子
      constexpr reference& operator=(const bool x) noexcept;
      constexpr reference& operator=(const reference& x) noexcept;
      
      // P2321で追加された代入演算子
      constexpr const reference& operator=(bool x) const noexcept;
      
      // この提案によるADL swap()
      friend constexpr void swap(reference x, reference y) noexcept;  // 👈
      friend constexpr void swap(reference x, bool& y) noexcept;      // 👈
      friend constexpr void swap(bool& x, reference y) noexcept;      // 👈

      ...
    };
  };
}
namespace std {
  template<size_t N>
  class bitset {
  public:
    ...

    class reference {
    public:
      constexpr reference(const reference&) noexcept; // 👈
      constexpr ~reference();
      
      // bool型への暗黙変換
      constexpr operator bool() const noexcept;                  // for x = b[i];
      
      // 代入演算子
      constexpr reference& operator=(bool x) noexcept;           // for b[i] = x;
      constexpr reference& operator=(const reference&) noexcept; // for b[i] = b[j];
      
      // この提案によるconst 代入演算子
      constexpr const reference& operator=(bool x) const noexcept;  // 👈
      
      // この提案によるADL swap()
      friend constexpr void swap(reference x, reference y) noexcept;  // 👈
      friend constexpr void swap(reference x, bool& y) noexcept;      // 👈
      friend constexpr void swap(bool& x, reference y) noexcept;      // 👈

      ...
    };
  };
}

bool&を取る2つのswap()オーバーロードは、bool型の左辺値breferenceの左辺値rに対してswap(r, b)swap(b, r)を機能させるためのオーバーロードです。どちらのreferencebool&から暗黙変換可能ではないため、このswapを機能させるためにこの2つのオーバーロードが必要になります。

コピーコンストラクタのdefaultが削除されているのは、実装によって両クラスのデストラクタのトリビアル性に差異があったことから、標準としてそれを強制することを回避することを意図したものです。コピーコンストラクタは実際には主要3実装全てでトリビアルでしたが、ここでのトリビアル性が重要ではないことからデストラクタに合わせたようです。

P3666R0 Bit-precise integers

C23の_BitIntC++に導入する提案。

幅の大きな整数型の必要性はP3140R0で、_BitIntの性質と基本型として実装するメリットはP3639R0で、それぞれ説明されています(これらは同じ著者の方によるものです)。

これらの経験を踏まえて、C++にC23互換の_BitIntを導入しようとする提案です。

この提案の_BitIntはキーワードかつ基本型として提案されており、特にCとの互換性を重視したものになっています。また、ここでは_BitIntの最小限のもの(MVP: Minimal Viable Product)をまず導入することを目指しており、最初から完全なものを入れることを目指していません。

言語機能としてはCの_BitIntをほぼそのまま導入します

  • 型名は_BitInt(N)もしくはunsigned _BitInt(N)
    • _BitIntはキーワード
  • Nの値は少なくとも64までをサポートする
    • BITINT_MAXWIDTHで最大値を取得できる
  • wb, uwbなどのリテラルサフィックスをサポート
  • 整数昇格の対象外
  • 標準整数型と同じ幅であれば、標準整数型の方が変換ランクが低くなる
    • _BitInt(32)int(32ビット幅とする)はintの方が変換ランクが高い
    • 幅が大きい場合にのみ変換ランクが高くなる
      • _BitInt(33)int(32ビット幅とする)は_BitInt(33)の方が変換ランクが高い
  • 符号が混在した比較や暗黙変換などの寛容さがある
  • 符号付_BitIntオーバーフローは未定義動作

ただし一部C++側で拡張している部分があります

  • 浮動小数点数型、bool型や文字型への暗黙変換を禁止
  • _BitInt(N)Nを関数テンプレートにおける引数推論の対象にする
    • template <size_t N> void f(std::bit_int<N> x);のような関数をf(100wb)のように呼ぶと、N = 8が推論される
  • _BitInt(1)をサポート
    • C23では符号なし_Bitint(1)をサポートしていない

標準ライブラリでも_BitInt対応が行われます

  • std::bit_int<N>std::bit_uint<N>エイリアステンプレートの追加
  • 文字列変換サポート
    • std::format
    • std::to_chars/std::from_chars
    • std::to_string/std::to_wstring
  • <cmath>の数学関数のサポート
  • std::is_integral/std::integralのサポート
  • std::atomic_BitInt特殊化サポート
  • iota_viewの内部整数型IOTA-DIFF-Tの定義を調整
    • 128ビットを超える幅の整数型が使用された際にもABI破壊が起こらないように考慮する
  • <simd>
    • ここではまず、標準整数型と一致する幅の_BitIntについてのみサポートする

ライブラリの規定では多くの場所で既に包括的な整数型の指定によって暗黙的に任意の整数型をサポートするようになっているため、文言の変更が無くても多くの場所で自動的に対応が行われます(実装が不要になるわけではありませんが)。

#include <cstdint>

void large_integer_example() {
  // 128ビットの符号なし整数を定義
  std::bit_uint<128> a = 0xFFFF'FFFF'FFFF'FFFF'FFFF'FFFF'FFFF'FFFFuwb;

  std::bit_uint<128> b = 2uwb;
  std::bit_uint<128> result = b - a;
  // オーバーフローはUBになるのでより広い幅で行う必要がある
  std::bit_uint<256> prod = std::bit_uint<256>(a) * std::bit_uint<256>(b);
}

ただし、64を超えるNのサポートは実装定義です。

P3688R3 ASCII character utilities

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

以前の記事を参照

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

  • §3.6. Why no function objects? セクションを追加
  • N5014にリベース

などです。

P3695R1 Deprecate implicit conversions between Unicode character types

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

以前の記事を参照

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

  • char8_tを含む変換のみを非推奨とする
    • char16_tchar32_tの間の変換については手を付けない
  • N5014にリベース

などです。

char16_tUTF-16)はサロゲートペアを利用して一部のコードポイントの文字をエンコードすることにより、char32_tとの比較で誤検出(異なるコードポイントの文字に対応する値の比較がtrueになる)が発生することは基本的に無いため、char16_tchar32_tの間の暗黙変換はそのままにすることにしたようです。

P3702R2 Stricter requirements for document submissions (SD-7)

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

以前の記事を参照

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

  • プレゼンテーションスライドに関する例外を追加

などです。

P3754R1 Slides for P3100R2 presentation to EWG

P3100R2の紹介スライド。

以前の記事を参照

このリビジョンでの変更はよくわかりませんが、これは提案ではありません。

P3776R0 More trailing commas

P3776R1 More trailing commas

末尾カンマを許可する場所を広げる提案。

関数の引数リストや初期化子リスト、列挙値の宣言など、C++コードの中でカンマ区切りリストを記述する必要のある場所は多数あります。そのような場所では場合によってはリスト末尾のカンマを付けたままにすることができることがありますが、別の場所ではできない場合もあります。

この提案は、なるべく多くの場所で末尾カンマを許容するようにすることを提案しています。

同様の提案は以前にもP0562R2で提案されていました。P0562R2では基底クラスリストとコンストラクタ初期化子リストでの末尾カンマを許容することを提案していましたが、コンストラクタ初期化子リストに関してはコンストラクタ本体のパースにおける曖昧性の問題が指摘されており、それを受けて提案の追及は止まってしまっているようです。

この提案もP0562R2とモチベーションを同じくしており、次の事をモチベーションとして挙げています

  • エディタのテキスト編集機能への対応
    • 例えばVSCodeのAlt+↑などのようにテキストの行を入れ替える機能において、カンマ有無のコンパイルエラーを誘発するのを防止できる
  • Gitなどのバージョン管理における差分の改善
    • カンマ区切りリストに要素を追加する場合、元のリストの最後の要素にカンマを追加してから要素を追加する
    • この時、1行1要素のようにフォーマットされていると、差分が2行に及ぶ
    • 末尾カンマを許容するとこの場合を1行だけ(追加した分だけ)にでき、レビューの容易化・git blameを汚染しない・コンフリクト発生の最小化、などが期待できる
  • コード生成機能の簡素化
    • 可変長マクロにおける__VA_OPT__のように、末尾カンマを許容しないコンテキストに対する特殊対応が必要になることが良くある
    • C++26のリフレクションを用いたコード生成機能(P3294R2)においても末尾カンマに対する特殊対応が必要になることが予想されている
    • 末尾カンマを許容することで、これらの対応や考慮が不要になり、コード生成するコードが簡素化される
  • フォーマッタの制御
    • clang-formatにおいては末尾カンマを利用してリストのフォーマットを制御できる機能があるが、末尾カンマを許容しない場所ではこれを使えない
    • 末尾カンマを許容することでこれを使用できる場所が増える
  • 言語の一貫性の向上
    • 末尾カンマを許容している場所としていない場所が混在しており、どこで許容されてどこで許容されないかわかりづらい
    • 単純なリファクタリングなどの際に問題になることがある
  • 文字列リテラルのカンマ区切りリストにおけるバグの防止
    • 関数引数に複数の文字列リテラルを渡すとき、カンマを忘れても(関数側の引数の期待する数と合っていれば)エラーにはならないが、文字列リテラルの結合によって意図通りに動作しなくなる
    • 末尾カンマを許容しておけば、あとから末尾に文字列リテラルを追加する際にも忘れが無くなる

この提案ではセミコロンで終了するリストを除いて可能な限りのリストにおいて末尾カンマを許容するようにすることを提案しており、おおむね{}, (), [], <>のいずれかによるかっこで囲まれたリストにおいて末尾カンマを許容するようにしています。

提案している末尾カンマを許容するリストの例

[: ... :]<A, B,>                    // template-argument-list in splice-specialization-specifier
[]<A, B,>{}                         // template-parameter-list in lambda-expression
[a, b,]{}                           // capture-list in lambda-introducer
d[a, b,]                            // expression-list in subscript operator
f(a, b,)                            // expression-list in call operator
T(0,)                               // expression-list in function-style cast
typename T(0,)                      // expression-list in function-style cast with typename
new (a, b,) T                       // expression-list in new-placement
new T(a, b,)                        // expression-list in new-initializer
template for (int _ : { a, b, })    // expression-list in expansion-init-list
auto [a, b,]                        // sb-identifier-list in structured-binding-declaration
T f(a, b,)                          // parameter-declaration-list in parameter-declaration-clause
T x(a, b,);                         // expression-list in initializer
[[=a, =b,]]                         // annotation-list in attribute-specifier
S() : m(a, b,)                      // expression-list in mem-initializer
template<a, b,>                     // template-parameter-list in template-head
template<C<a, b,> T>                // template-argument-list in type-constraint
template<template<A, B,> concept>   // template-parameter-list in concept-tt-parameter
T<A, B,>                            // template-argument-list in simple-template-id
operator()<A, B,>                   // template-argument-list in template-id

ちなみに、次のリストでは既に末尾カンマが許容されています

{ a, b, }                 // initializer-list in braced-init-list
{ .a=0, .b=0, }           // designated-initializer-list in braced-init-list
enum { a, b, }            // enumerator-list in enum-specifier
[[a,b,,,,]]               // attribute-list in attribute-specifier

なお、P0562R2で提案されていた末尾カンマの許容については、構文解析を曖昧にする問題が(どちらにも)ありそうなため、ここでは提案していません。ここで提案しているリストは末尾カンマの後には閉じかっこしか来ないため構文解析を曖昧にはしないはずです。

この提案はEWGのレビューにおいてC++29に向けてCWGに転送するためのコンセンサスを集めることができずにリジェクトされています(その理由は特に書かれていません)。

P3784R1 range-if

範囲if文の提案。

以前の記事を参照

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

  • ライブラリベースの代替案についての詳細な議論を追加

などです。

ライブラリベースの解決策は次の2つが挙げられています

  • アルゴリズム
    • break, continueなどの制御構文がサポートされない
  • ranges::nonempty_subrange
    • Redditで提案されたもの
    • 構文が冗長
    • ダングリングを防止するためには、borrowed_rangeに制限するかowning_subrange(P2644/P2718のライブラリソリューション)を使用する必要がある

いずれも言語機能よりも優れてはいないとしています。

P3786R0 Tuple protocol for fixed-size spans

固定サイズstd::spanにタプルプロトコルサポートを追加する提案。

タプルプロトコルは固定サイズの多くのライブラリ型ですでにサポートされており、構造化束縛のカスタマイズポイントとなります。std::spanはまだサポートしていないため、追加しようとする提案です。

std::span<int, 3> s{...}; 
auto & [x, y, z] = s; // ok、この提案後

std::vector<std::span<int, 3>> ss{...}; 
auto firsts{ss | std::views::elements<0> 
               | std::ranges::to<std::vector>()};  // ok、この提案後

実は以前にもP1024でこれは提案されており、C++20で採択されていたのですが、std::tuple_element_t<const std::span<T, 3>>const Tになる(Tになってほしい)という問題が報告された結果、P2116で削除されています。ここでの設計はこの問題をstd::spanが参照セマンティクスを持つ型として扱うことで解決しています。

すなわち、std::spanに対するトップレベルの修飾子はすべて無視されます。

  • tuple_size<cv1 span<cv2 T, N>>::value == N
  • tuple_element<I, cv1 span<cv2 T, N>>::type == cv2 T
  • decltype(get(span<cv T, N>)) == cv T

当然ですが、タプルプロトコルサポートは固定サイズ(N != dynamic_extent)の場合のみです。

P3811R0 default comparison memory safety

デフォルト比較演算子の実装に対してメモリ安全であることを要求するようにする提案。

C++20で追加された比較演算子のデフォルト実装において生成されるコードはかなり単純かつボイラープレート的なコードであり、UBフリーで実装できるはずです。この提案は、標準としてそのような実装を行うことを要求するようにする提案です。

デフォルト比較演算子の実装は例えば次のようになります

class TotallyOrdered : Base {
    string tax_id;
    string first_name;
    string last_name;
public:
  // auto operator<=>(const TotallyOrdered&) const = default; の実装イメージ
  std::strong_ordering operator<=>(const TotallyOrdered& that) const {
    if (auto cmp = (Base&)(*this) <=> (Base&)that; cmp != 0) return cmp;
    if (auto cmp = last_name <=> that.last_name; cmp != 0) return cmp;
    if (auto cmp = first_name <=> that.first_name; cmp != 0) return cmp;

    return tax_id <=> that.tax_id;
  }

  // bool operator==(const TotallyOrdered&) const = default; の実装イメージ
  bool operator==(const TotallyOrdered& that) const {
    if (!((Base&)(*this) == (Base&)that)) return false;
    if (!(last_name == that.last_name)) return false;
    if (!(first_name == that.first_name)) return false;

    return tax_id == that.tax_id;

  }
};

実際にコンパイラがこのようなコードを生成するわけではないものの、これと大きく異なるコードが生成されることもないはずです。このようなコードにおいては、使用する各サブオブジェクトの比較演算子== <=>)を除いて未定義動作を混入させることなく実装ができるはずです。また、<=> ==がそのようになっていれば、そこから生成される他の比較演算子でも同様の保証を提供できます。

P3812R0 const and & in default member functions

const/参照メンバを持つクラスの代入演算子のデフォルト実装を可能にする提案。

クラスの非静的メンバ変数としてconstあるいは参照メンバを持つと、そのクラスの代入演算子はデフォルト実装できなくなります。

// least privilege
class leastp final {
public:
  leastp(std::vector<int>& v, int i)
      : v{v}, i{i} {}

  // コンストラクタはデフォルト実装できる
  leastp(const leastp&) = default;
  leastp(leastp&&) = default;

  // 代入演算子はデフォルト実装できない(deleteされる
  leastp& operator=(const leastp& other) = default;
  leastp& operator=(leastp&& other) = default;

  // least privilege
  constexpr size_t size() const {
      return v.size();
  }

private:
  std::vector<int>& v;
  const int i;
};

これは代入演算子を手書きすることを考えると分かりやすいと思われますが、参照やconstメンバは基本的に置換可能ではないためです。

これを実装しようとすると*thisを配置しなおす必要があります。例えば次のような実装になります

class leastp final {
  ...

  leastp& operator=(const leastp& other) {
    if (this != &other) {
      // *thisを一旦破棄
      this->~leastp();

      // コピーコンストラクタを用いてthisの場所に新しいオブジェクトを構築
      new (this) leastp(other);
    }

    return *this;
  }

  leastp& operator=(leastp&& other) {
    if (this != &other) {
      // *thisを一旦破棄
      this->~leastp();

      // ムーブコンストラクタを用いてthisの場所に新しいオブジェクトを構築
      new (this) leastp(std::move(other));
    }
    
    return *this;
  }

  ...
};

このような実装は複雑であり、正しく記述することが困難です(少し間違えるとUBに突入する)。C++コアガイドラインにおいてもコピー/ムーブ可能なクラスにconstメンバや参照メンバを含めないことが推奨されています。

この提案は、const/参照メンバを非静的メンバに持つクラスにおいて、コピー/ムーブコンストラクタがdefault指定されているならば、透過的に置換可能(transparently replaceable)として、対応する代入演算子default実装できるようにするものです。

これによって、const/参照メンバを保持したい場合に諦めたり代わりにポインタを保持するなどの必要がなくなり、上記のような実装を取る場合のコードの必要性もなくなり、コアガイドラインも不要になります。

P3813R0 execution::task::valueless()

execution::taskvalueless()メンバ関数を追加する提案。

C++の標準ライブラリの型には空の状態を取るものは珍しくありません。その中には空の状態が回復不可能である型があります。それは、indirect, polymorphic, generator, taskの4つです。これらの型ではほかの全てのメンバ関数が事前条件として空ではない事を要求するため、代入と破棄くらいしかできることがありません。

これらの型の空の状態は、通常のプログラムではほぼ発生しないと思われるものの場合によっては出会うかもしれません。そのために、indirect, polymorphicでは.valueless_after_move()によってこの空の状態をチェックできるようにしています。しかし、generator, taskにはそのような関数がありません。

generatorについては既存の実装を考慮すると複雑になるため別の提案で行うことにして、ここではtaskに対して空の状態チェック関数を追加することを提案しています。

taskが回復不可能な空になるのは、ムーブの前後とconnect呼び出し後です。この2つの状態は区別する必要がなさそうなので、ここでは状態が空かどうかをチェックする関数.valueless()のみを追加しています。

namespace std::execution{
  template <class T = void, class Environment = env<>>
  class task {
    ...

    bool valueless() const noexcept;

    ...
  }
}

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

task<int> t = ...;

// 状態チェックを行えるようになる
contract_assert(not t.valueless()); 

auto res = sync_wait(t);

P3815R0 Add scope_association concept to P3149

P3149の非同期スコープ機能に、scope_associationコンセプトを追加する提案。

P3149の非同期スコープ機能は、2つのスコープ型(simple_counting_scopecounting_scope)とassociatespawnspawn_futureなどの基礎的な操作によって構成されており、これらのものはscope_tokenというコンセプトを中心として設計されています。

namespace ex = std::execution;

// ウィンドウを表現する簡単な型
struct my_window {
  class close_message {};

  ex::sender auto some_work(int message);

  ex::sender auto some_work(close_message message);

  void onMessage(int i) {
    ++count;
    // onによってschのコンテキストでsome_workを実行し
    // その操作はscopeに対応するスコープに関連付けられる
    ex::spawn(ex::on(sch, some_work(i)), scope);
  }

  void onClickClose() {
    ++count;
    ex::spawn(ex::on(sch, some_work(close_message{})), scope);
  }

  my_window(ex::system_scheduler sch, ex::counting_scope::token scope)
    : sch(sch)
    , scope(scope) {
    // このクラスを何らかの方法でWindowフレームワークに登録し
    // `onMessage()`と`onClickClose()`の呼び出しを受け付けられるようにする
  }

  ex::system_scheduler sch;
  ex::counting_scope::token scope;
  int count{0};
};

int main() {
  // keep track of all spawned work
  ex::counting_scope scope;
  ex::system_context ctx;
  
  my_window window{ctx.get_scheduler(), scope.get_token()};

  // scopeに関連付けられた全ての操作の完了を待機する
  std::this_thread::sync_wait(scope.join());
  
  // すべてのリソースは安全に破棄できる
  // =スコープに関連付けられた操作はすべて完了している
  return window.count;
}

スコープ型は、そのスコープに関連付けられた非同期処理(sender)による処理が全て終わるまで、そこで使用されうるリソースを保護するためのものです。スコープへの非同期操作の関連付けはexecution::spawnによって行われており、ここに処理を表すsenderとスコープから取得したトークン(上記例だとcounting_scope::token)を渡すことでスコープと非同期処理を関連付けています。

スコープは.join()によってスコープ自身のsenderを取得でき、そのsenderは関連付けられた処理が全て終わるまで完了しないものです。スコープオブジェクトを適切に管理することで、スコープ内で実行される非同期処理で使用されるリソース(上記例だとsystem_contextwindow自身)をその完了まで適切に保護することができます。

この提案はscope_associationというコンセプトを導入し、これを用いてP3149で提案されているこれらの機能の内部設計を行うことで、現在の実装より良い実装を取ることができるとして、scope_associationコンセプトとそれによる再設計を提案するものです。

とはいえ、ここでの再設計は内部に閉じたものでインターフェースを変更するものではありません。これにより次の利点が得られるとしています

  • スコープ型と基礎操作のバイナリサイズの削減
  • ムーブ/コピーの回数を減らせる
  • 実装モデルが簡素化され、カスタマイズしやすくなる

提案されているscope_associationコンセプトは次のようなものです

template <class Assoc>
concept scope_association =
  movable<Assoc> &&
  default_initializable<Assoc> &&
  requires(Assoc assoc) {
    { static_cast<bool>(assoc) } noexcept;
    { assoc.try_associate() } -> same_as<Assoc>;
  };

scope_associationのモデルとなる型は、senderと非同期スコープ間の関連付けを表すRAIIハンドル型となります。コンセプトから読み取れる性質は次のようになります

  • ムーブ可能
    • コピー不可能であることを意図
  • デフォルト構築可能
  • boolへの変換が可能
    • 文脈的bool変換可能であることを意図
  • .try_associate()を持つ

scope_associationな型のオブジェクトassocは、bool変換されてtrueを返した場合(“engaged”な状態)は何らかのsenderとスコープの間で関連付けが成立していることを表し、falseを返した場合(“disengaged”な状態)は関連付けが成立していないことを表します。

.try_associate()についてはP3149のscope_token(のモデルとなる)型のそれと全く同じ意味論となります。

scope_association型はRAIIハンドルであり、そのデストラクタで関連付けの開放処理(scope_token.disassociate()相当)を行うことで関連付け解除とそのクリーンアップ処理を自動化します。

これを用いて、P3149のscope_token, associate, spawn, spawn_future, simple_counting_scope, counting_scopeを変更します。

execution::scope_token

P3149の中心概念であるscope_tokenは次のように定義が変わります

P3149R11 この提案
template <class Token>
concept scope_token =
  copyable<Token> &&
  requires(Token token) {
      { token.try_associate() } -> same_as<bool>;
      { token.disassociate() } noexcept -> same_as<void>;
      { token.wrap(declval<test-sender>()) } -> sender_in<test-env>;
  };
template <class Token>
concept scope_token =
  copyable<Token> &&
  requires(Token token) {
    { token.try_associate() } -> scope_association;
    { token.wrap(declval<test-sender>()) } -> sender_in<test-env>;
  };

.try_associate()scope_association型を返すようになります。これまでは関連付けの成否を表すbool値を返していましたが、scope_association型を返すことで関連付けそのものを表現するものを返すようになります。

scope_association型はデストラクタで開放処理を自動化するため、.disassociate()は不要になります。

execution::associate

associateはCPOであり、sender auto associate(sender auto&&, scope_token auto) noexcept(...);の様なシグネチャを持つものです。これは、senderとスコープをscope_tokenを介して関連付けを行おうとするものです。

インタフェースや役割は変化しませんが、返されるsenderのコピー動作が変化します。

associate()に渡されるsenderがコピー可能な場合、結果のassociate-senderもコピー可能となり

  • 関連付けられていないassociate-senderをコピーすると、必ず新しい関連付けられていないassociate-senderが生成される
  • 関連付けられているassociate-senderをコピーするには、それに含まれるassociate-dataをコピーする必要があり、そのコピーコンストラクタは次のように動作する
    1. ソースのassociation.try_associate()を呼び出した結果が宛先のassociate-dataに渡される
    2. 結果の関連付け(scope_associationオブジェクト)がengaged状態ならば、ラップされたsenderをソースから宛先のassociate-dataにコピーする
      • 宛先のassociate-senderは関連付けられている
    3. そうでない場合、宛先のassociate-senderは関連付けられていない

さらに、operation-stateのデストラクタは次の事を保証します

  • 独自の関連付け(scope_associationオブジェクト)を持つoperation-stateoperation-stateのデストラクタの最後のステップとして、関連付けのデストラクタを呼び出す必要がある

execution::spawn/execution::spawn_future

内部状態でscope_tokenオブジェクトを保持する代わりにscope_associationオブジェクトを保持するようになり、関連付けの解除がデストラクタによって行われるようになります。

execution::simple_counting_scope/execution::counting_scope

関連付けの解除がtoken.try_associate()から返されるscope_associationオブジェクトのデストラクタによって行われるようになります。

これらの変更は前述のようにユーザー向きのAPIにはほとんど影響がありません。そのうえで、内部実装を改善しようとするものです。

P3816R0 Hashing meta::info

meta::infoのハッシュサポートを追加する提案。

meta::infoはリフレクション機能において^^(リフレクション操作)の戻り値となるリフレクションオブジェクトの型であり、コンパイル時にのみ使用可能な不透明型です。コンパイル時のハッシュサポートはリフレクションにおいても考慮されていなかったため、meta::infoはハッシュ化可能ではありません。

一方、P3372R3では連想コンテナを含むほぼすべてのコンテナがconstexpr対応しており、そのためにハッシュサポートが重要となります。

そのために、この提案はmeta::infoコンパイル時ハッシュサポートを追加しようとするものです。

しかしmeta::infoコンパイル時にのみ使用可能な型であり、std::hashの特殊化による対応は実行時要件が多く課せられるため適していません。そのほかのコンパイル時にのみ使用可能な型のコンパイル時ハッシュサポートのために、std::consteval_hash<T>を追加し、そのmeta::info特殊化としてコンパイル時ハッシュサポートを追加することを提案しています。

リフレクションによるmp_unique実装の、コンパイルunordered_setを使用する例

template <typename... Types>
struct type_list {};

template <typename TypeList>
consteval auto mp_unique_reflected() {
  static_assert(std::meta::has_template_arguments(^^TypeList), "mp_unique requires a type_list");
  static_assert(std::meta::template_of(^^TypeList) == ^^type_list, "mp_unique requires a type_list");

  std::unordered_set<std::meta::info, std::consteval_hash<std::meta::info>> seen;
  std::vector<std::meta::info> unique_types;

  for (auto type_info : std::meta::template_arguments_of(^^TypeList)) {
    if (const bool is_unique = seen.insert(type_info).second; is_unique) {
      unique_types.push_back(type_info);
    }
  }

  return std::meta::substitute(^^type_list, unique_types);
}

template <class TypeList>
using mp_unique = [:mp_unique_reflected<TypeList>():];

using input = type_list<int, char, int, string, double, char>;
using filtered = mp_unique<input>;
using expected = type_list<int, char, string, double>;

static_assert(is_same_v<expected, filtered>);

std::hashコンパイル時に利用可能なようになっておらず、その予定もないため、非順序連想コンテナをコンパイル時に使用するためにはハッシュのカスタマイズがどのみち必要になります。そのため新しいコンパイル時専用ハッシュ型を追加することはコンパイル時の非順序連想コンテナの使用感を今より悪くすることはありません。このことは問題ではあるものの、この提案はその解決を行うことを意図していません。

ConstevalHash要件は次のように指定されています

  • 関数オブジェクト型
  • コピー構築可能かつ破棄可能
    • Cpp17CopyConstructibleかつCpp17Destructible
  • constevalオンリー型
  • Hの2つの実体は、同じ引数に対して同じ値を生成することを保証しない
    • HConstevalHash
    • 特に、翻訳単位間で値が異なることがある
  • Key型をHの関数呼び出し引数型、hHのオブジェクト、uKeyの左辺値、kKeyに変換可能な型の値として、次の操作が有効かつ指定されたセマンティクスを持つ
    • h(k) -> std::size_t
      • 返される値はkのみに依存する
      • 値はコンパイルを繰り返し実行しても安定している
      • 2つの異なる値t1, t2について、h(t1) == h(t2)となる確率は非常に小さく、1.0 / std::numeric_limits<size_t>::max()に近くなる
    • h(u) -> std::size_t
      • uを変更しない

提案文書より、実装例

template<>
struct consteval_hash<meta::info>
{
  consteval consteval_hash() = default;
  consteval consteval_hash(const consteval_hash<meta::info>&) = default;
  consteval consteval_hash(consteval_hash<meta::info>&&) = default;

  consteval auto operator()(meta::info r) const noexcept -> size_t {
    return __metafunction(meta::detail::__metafn_reflection_hash, r);
  }

private:

  // This unused variable is here to make consteval_hash<> a
  // consteval-only type.
  [[maybe_unused]] const meta::info unused = ^^::;
};

P3818R0 constexpr exception fix for potentially constant initialization

P3818R1 constexpr exception fix for potentially constant initialization

現在の例外をチェックする関数を特定の定数評価コンテキストで評価しないようにする提案。

C++26では定数式での例外送出が可能になっており、それに伴ってstd::exceptionとその派生クラスなどの例外関連のユーティリティが定数式で実行可能になっています。例えば、std::current_exception()によってstd::exception_ptrを定数式で取得して処理することができます。

constexpr auto maybe_throw(int n);

consteval void f() {
  try {
    maybe_throw(1);
  } catch (...) {
    auto exptr = std::current_exception();  // ok
  }
}

std::current_exception()は定数式で呼ばれる場合、その定数評価コンテキスト内で送出されている例外オブジェクトを取得します。

ところで、定数評価はconstexpr変数の初期化やconsteval関数呼び出しなどの明示的な評価の開始以外でも行われることがあります。その一つはconstな整数型の変数の初期化式が定数評価可能な場合で、このような変数をpotentially constantな変数(定数かもしれない変数)と呼びます。

constexpr int f() {
  return 1;
}

int main() {
  const int n = f();  // nはpotentially constantな変数
}

定数かもしれない変数には正確にはconstexpr変数も含みますがここではそれは除外するとします。

この定数かもしれない変数はその初期化子が定数評価可能であれば、その結果を用いてコンパイル時に初期化され、定数式のコンテキストで使用可能になります。定数かもしれない変数であっても初期化式が定数評価可能でなければ、その初期化及び初期化式の評価は実行時に行われます。

std::current_exception()などの呼び出しはC++26から定数評価可能な式です。以前は定数評価可能ではなかったためこの変更が下位互換性を損ねることは無いはずでしたが、これらの式が定数かもしれない変数の初期化式に使用されている場合に動作が変化します。

try {
  // 例外を投げうるコード
    ...
} catch (const std::exception & exc) {
  // has_exceptionは定数かもしれない変数だが、C++23まではその初期化式は定数評価可能ではなかった
    const bool has_exception = (std::current_exception() != nullptr); // 定数評価された時にはそのコンテキストに現在の例外は存在しない
    static_assert(has_exception == false); // ✅、has_exceptionは定数式で使用可能
}

constな整数型(boolを含む)は定数かもしれない変数ですが、std::current_exception()の呼び出しはC++23までは定数評価不可能だったため定数評価されることはありませんでした。しかしC++26では定数評価可能になることによってこのhas_exceptionの初期化式は定数式としてコンパイル時に実行されます。この変数の初期化によって開始する定数評価コンテキストでは例外は送出されていないため、定数式ではcatchの内部であるにもかかわらずstd::current_exception()nullptrを返します。

これによって、has_exceptionは実行時の例外状態と全く関係なく常にfalseで初期化されることになります。

提案文書より、他の例

struct transaction {
    // ...
    void cancel() { /* revert changes */ }
    
    ~transaction() {
    // unrollingは定数かもしれない変数
        const bool unrolling = std::uncaught_exceptions() > 0;  // falseでコンパイル時に初期化される
        
        if (unrolling) { // this will never be evaluated
            log("exception was thrown in a transaction => cancel()");
            cancel();
        }
    }
}

定数かもしれない変数unrollingC++26から定数式によって初期化されるようになります。しかも、ユーザーの意図とは全く異なる結果によって初期化されます(常にfalse)。

これらのように、変数の初期化式から開始される定数評価コンテキストではその評価の内部で例外が送出されない限り現在の例外オブジェクトが存在する状態になりません。そのため、定数かもしれない変数の初期化式でstd::uncaught_exceptions()std::current_exception()が使用されていて初期化式全体が定数評価可能だと、これらの関数は常に現在の例外が無い状態で評価され結果を取得します。

この提案はこの問題を解消しようとするもので、std::uncaught_exceptions()std::current_exception()constexprは維持したうえで、これらの関数が定数かもしれない変数の初期化のために定数評価される場合に呼び出しを定数式ではなくすることを提案しています。

これにより、上記の2例のようなケースではstd::uncaught_exceptions()std::current_exception()の呼び出しが定数式ではなくなることで、定数かもしれない変数の初期化はC++23までと同じく実行時に行われるようになります。

この提案の内容に対応するNBコメントが3件提出されているようで、それを受けてLEWGでこの提案とこの問題について審議された結果、この提案の変更は大きすぎるとしてstd::uncaught_exceptions()std::current_exception()から単にconstexprを取り除くことに若干の合意があったようです。これを受けてこの問題の解決はP3842R0に委ねられたようです。

P3819R0 Remove evaluation_exception() from contract-violation handling for C++26

std::contracts::contract_violation::evaluation_exception()を削除する提案。

C++26 Contracts機能における違反ハンドラの引数型であるstd::contracts::contract_violation型には、その違反ハンドラが契約述語からの例外送出によって呼び出されていた場合にその例外オブジェクトを取得するための関数.evaluation_exception()が用意されています。

void handle_contract_violation (const contract_violation& cv) {
  if (auto exptr = cv.evaluation_exception(); exptr != nullptr) {
    // 例外処理
    ...
  }
}

違反ハンドラを呼び出した契約違反が契約述語がfalseを返したことによって起きていた場合、.evaluation_exception()nullptrを返します。

契約違反が例外によって起きたかどうかということ自体は.detection_mode()std::detection_mode::evaluation_exceptionであるかを調べることによっても行えます。こちらの場合は再スローを行うことで現在の例外をキャッチすることができます。

void handle_contract_violation (const contract_violation& cv) {
  if (cv.detection_mode() == detection_mode::evaluation_exception) {
  try {
    throw;
  } catch (std::exception& e) {
    // 例外処理
    ...
  }
}

この方法は.detection_mode()のチェックを忘れた場合に失敗するか、悪い場合は関係ない例外を処理してしまう可能性があります(違反ハンドラ自体はcatch節の中でも呼ばれる)。.evaluation_exception()はこのような冗長なチェックを回避して、違反ハンドラの呼び出しの原因となった例外オブジェクトに簡単にアクセスするために追加されたものです。

.evaluation_exception().detection_mode()を用いた方法の構文糖衣として提案されましたが、現在の例外ではなく違反ハンドラの呼び出しの原因となった例外オブジェクトを常に取得するものであり、このことに実装上の問題がありました。

void handle_contract_violation (const contract_violation& cv) {
  // ...
  try {
    // ...
    throw X;
  } catch (...) {
    if (cv.detection_mode() == detection_mode::evaluation_exception) {
      // 現在の例外オブジェクトをXであり、契約述語から送出されたものではない
      auto evaluation_exception_ptr = cv.evaluation_exception();
      // evaluation_exception_ptrはXではなく元の例外を処理する
    }
  }
}

このような動作を実現するためには、契約違反の原因となった例外オブジェクトを通常の例外状態から退避しておく必要があります。一部の実装ではそのために例外オブジェクトのコピーを行う必要があり、そのコピーコンストラクタを通して例外送出による契約違反と違反ハンドラ呼び出しの間にユーザーコードが実行されてしまう可能性があります。

違反ハンドラは契約違反が起きている状況(スタック破損などが起きている可能性がある)で実行することが想定されているため、そのような状況に対してある程度堅牢に記述することができる一方、例外オブジェクトのコピーコンストラクタはそうではなく、スタックトレースを取得しようとしてスタックを走査する可能性があります。そして、これがセキュリティ上の脆弱性につながる可能性があります(破壊されたスタックを走査するコードを通して任意の場所にジャンプさせることで、任意のコードを実行する)。

Itanium ABIを採用するプラットフォームでは例外オブジェクトのコピーを回避する実装が知られている一方、Windows ABIでは現時点でそのような実装を確認できていません。.evaluation_exception()を今のまま導入しようとすると、このようなセキュリティリスクを回避するための要件をC++の例外システムに新たに課すことになります。

これらの問題のため、この提案では.evaluation_exception()が全てのプラットフォームで安全に実装可能であることが確認されるまで標準に導入しないようにすることを提案しています。

前述のように、問題はあるものの.detection_mode()のチェックとstd::current_exception()という代替手段があるため、.evaluation_exception()を削除しても同等のことができなくなるわけではありません。こちらの方法の場合でも例外のコピーは発生しえますが、それは違反ハンドラ内のユーザーコードで見えている物であり制御可能なものです。

この提案はNBコメント解決として2025年11月の全体会議でC++26向けに採択され、C++26に適用されています。

P3820R0 Split constexpr uncaught_exceptions into distinct runtime and consteval functions

P3820R1 Fix constexpr uncaught_exceptions and current_exception

std::uncaught_exceptions()std::current_exception()からconstexpr指定を削除する提案。

この提案の指摘する問題やモチベーションは少し上のP3818R1と共通しているのでそちらをご覧ください。

この提案はP3818R1とは異なり問題に対して次の解決策を提案しています

  • std::uncaught_exceptions()からconstexpr指定を削除する
  • 定数式で使用できる専用のstd::consteval_uncaught_exceptions()を追加する
    • 呼び出された定数評価の中での例外をカウントする
  • std::current_exception()は、呼び出されたのと同じ定数評価の中で送出され現在処理中の例外が存在する場合にのみ定数式となる、ように変更
  • 定数式で使用できる専用のstd::consteval_current_exception()を追加する
    • (呼び出された時点での)現在の定数評価内で送出された現在処理中の例外オブジェクトへのポインタを返し、それ以外の場合はnullptrを返す

この提案の場合、std::uncaught_exceptions()は定数式で使用できなくなり、定数式で使用するには名前の違うstd::consteval_uncaught_exceptions()を使わなければなりません。これによって、実行時とコンパイル時でコードを共通する場合はif constevalによる分岐が必要になります。std::current_exception()は少し異なり、定数式で呼ばれたときにnullptrを返さない場合にのみ定数評価可能になります。

P3818R1のところでも述べたように、結局どちらの提案もC++26のNBコメント解決フェーズに導入するには適さないとして、P3842R0にてstd::uncaught_exceptions()std::current_exception()からconstexprを削除することのみをC++26向けの修正とするようです。

P3822R0 Conditional noexcept specifiers in compound requirements

複合要件において条件付きnoexcept指定をサポートする提案。

複合要件とは、主にコンセプト定義中のrequires式内部で制約を記述する形式の一つで、{expr} -> return-type;のような形式で記述する要件の事です。{expr} noexcept -> return-type;のようにすると式exprnoexceptであることを指定できますが、ここのnoexceptには条件を指定できません。これにより、関数テンプレートでよく使用される条件付きnoexceptを制約としては表現できません。

この提案は、複合要件のnoexcept制約においてnoexcept(condition)の形式を許可して、条件付きnoexcept指定を制約で表現できるようにするものです。

現在 この提案
template<typename F, bool noexc>
concept invocable = noexc
  ? requires(F f) { { f() } noexcept; }
  : requires(F f) { f(); };

template<bool noexc>
struct callable_ref {
  callable_ref(invocable<noexc> auto&& fn);
  
  ...
};
template<typename F, bool noexc>
concept invocable = requires(F f) {
  { f() } noexcept(noexc);
};

template<bool noexc>
struct callable_ref {
  callable_ref(invocable<noexc> auto&& fn);
  
  ...
};

{expr} noexcept(condition) -> return-type;のように記述でき、conditiontrueと評価されているのにexprが例外を送出しうる場合、制約は満たされません。conditionfalseと評価されている場合はexprは例外を送出する可能性があることを表現します(この場合exprが例外を送出するかはチェックされない)。

条件なしのnoexceptnoexcept(true)と等価であり、noexcept(false)noexceptなしと等価です。

提案ではconditionが文脈的にboolに変換できない場合でもハードエラーにはせず、単に制約が満たされないようにすることを提案しています。これは、コンセプトはオーバーロード解決で使用されるため、予期しない入力に対しては他の候補が考慮されるという動作が期待されるからです。

P3823R0 Wording for US NB comment 10

trivially_relocatable_if_eligibleなどの文脈依存キーワードを削除する提案。

これは提出されたNBコメントの一つに対応するものです。ここでの主張はP1144での主張の一部でもあります。

trivially_relocatable_if_eligiblereplaceable_if_eligibleはリロケーションに関連して追加されたもので、クラスの宣言に対して付与して、そのクラスがリロケーション(特にトリビアルなリロケーション)が可能であることをクラスのプロパティとして追加するものです。

struct Y trivially_relocatable_if_eligible {};

これには次のような問題点が指摘されています

  • 在野の主要なプロジェクト/ライブラリにおいて使用されている・求められている意味論と整合しないため、使用できない
  • 現在リロケーションを利用している実装は、C++26モード以外ではこのキーワードを使用できない
  • C++26モードでも、optionalinplace_vectorをサポートするには不十分なことが分かっている

このような理由から、これらの文脈依存キーワードはC++26では一旦削除してC++29以降に向けて再検討することを求めるのが、NBコメント(US NB comment 10)の内容です。ただし、リロケーションというコア言語およびライブラリの仕様そのものを削除することは提案していません。

P3824R0 Static storage for braced initializers NBC examples

初期化子リストの補助配列に対して、静的記憶域期間を義務付ける提案。

P2752R3の採択によって、C++23(DRなので実装済みのコンパイラ)ではその初期化子が全て定数であるような初期化子リストの補助配列(backing array)を静的ストレージに昇格させて保持して、静的記憶域期間を与えることが許可されています。

#include <initializer_list>

const char& first() {
  std::initializer_list<char> il = { 'h', 'w' };
  return *il.begin();
}

const char& first(std::initializer_list<char> il)
{
  return *il.begin();
}

int main() {
  const char& rilm = first();
  const char& pilm = first({ 'h', 'w' });
  return 0;
}

rilm, pilmはどちらも、コンパイラが対応する初期化子リストを静的ストレージに配置した場合にダングリング参照ではなくなります。しかし、P2752の仕様はあくまでコンパイラがそれを行うことを許可するためのもので、これを保証してはいません。

P2752は#embedが初期化子リストを利用する際にスタック領域に読み込んだバイト列を配置してしまうのを防止するためのものでした。

// こうすると2MBの補助配列がスタックに置かれる
std::vector<char> v = {
  #embed "2mb-image.png"
};

// こうすると回避できる
static const char backing[] = {
  #embed "2mb-image.png"
};
std::vector<char> v = std::vector<char>(backing, std::end(backing));

結局P2752の後でも初期化子リストの静的ストレージへの昇格は保証されていないため、プログラマはこのような回避策を書かざるを得ません。特に、移植可能なコードを書くときはそうですし、同じコンパイラでもコンパイルオプションで結果が変わる可能性があるため結局こう書くしかなくなります。

この提案は、P2752で導入された使用を許可ではなく保証することで、これらの問題を解消することを提案しています。これにより、最初の例では常にダングリング参照が生成されなくなり、2つ目の例の様な回避策を書く必要がなくなります。

また、一要素の初期化子リストが静的ストレージに配置されることを許可している延長として、定数リテラルで初期化される単一のオブジェクトに対しても同様に静的ストレージへ昇格させることを提案しています。

// P2752によって静的ストレージへ昇格することが許可されている
std::initializer_list<int> x1 = { 1 };

// P2752の適用外だが、これらを含めることを提案している
const int& x1 = {1};
const int& x2{1};
const int& x3 = 1;

// x4をstatic変数にすることを提案しているわけではない
const int x4{1};

この場合、静的ストレージに昇格される対象はx1, x2, x3の参照に束縛されているオブジェクトであって、定数リテラルで初期化されるオブジェクトに対応する変数(x4)を静的ストレージに昇格することを提案しているわけではありません。初期化子リストも文字列リテラルもどちらも参照セマンティクスを持つものであり、静的ストレージに昇格されるのは参照そのものではなく背後にある参照されている物です。

P3827R0 Wording for US NB comment 9

リロケーションによって導入された"replaceability"という用語とその仕様箇所を削除する提案。

この提案は"replaceability"という概念に反対するNBコメントに応じて書かれたものの様です。

この概念は既存のreplaceabilityという言葉を使用しているところと言葉が被っており、対応する型特性を除いてライブラリ機能では使用されていないようです。

リロケーションを利用するライブラリ実装者が使用することを意図しているようですが、その定義についても問題や反対意見があるため概念および関連する専門用語ごと削除しようとしています。

P3829R0 Contracts do not belong in the language

Contracts機能をそれ専用に設計するのではなく、より汎用的に設計された機能の組み合わせによって実現するようにする提案。

P2900で提案されC++26に導入されたContracts機能の言語機能の側面は汎用的な価値を持つ機能の特殊なケースとなっているため、それらを分解してより汎用的な個別機能としてC++に導入し、それを組み合わせてC++ Contractsを構成することで、P2900が抱えているような問題を解決してより豊富な契約APIをライブラリ機能として提供することができるとしています。

そのためにC++26のContractsをリジェクトして、このような汎用機能を個別並行的に設計しC++29で追加して評価するようすることを提案しています。

この提案が挙げている、Contractsがその要件によって導入している専用の言語機能であり、分解してより汎用的な機能として考えられるものはつぎの4つです

  1. ODRの緩和
    • あるヘッダに記述された同じ関数の定義の契約アサーションが、翻訳単位ごとに異なる評価セマンティクスを持つことを許可している(ことがODRの緩和
    • P2900ではこれについて実装定義としているが、広く有用なためこのODR緩和のセマンティクスを明確に規定する必要がある
  2. 関数デコレータ
    • pre/postアサーションは関数呼び出しの前後で処理を実行する専用の構文になっている
    • これはpythonではデコレータとしてより汎用的な機能になっており有用であるため、このデコレータ機能によってpre/postアサーションを実現する
      • 特に、リフレクションを使用することでライブラリラベルでContractsを提供できるようになる
  3. 遅延評価
    • 契約アサーションを関数で実装する場合、引数の評価が回避できず、これが言語機能としてのアサーションの必要性の大きな部分だった
    • [[lazy]]属性の様な引数の遅延評価機能によって通常の関数で利用できるようにする
  4. 深いconst
    • P2900 Contractsの暗黙const化は浅いconstであり、ポインタの参照先までconstにならない
    • 深いconstを実現する言語機能を導入して通常のコードでも使用するようにできる
      • 仮にこのような言語機能が後から導入された場合、後方互換性によって契約アサーションのデフォルトを変更できない

違反ハンドラに関しては、そもそも統一的なエラーハンドラは使いづらいものであるとして重要視していないようです。

議論の過程は明らかではありませんが、この提案はリジェクトされています。

P3830R0 NB-Commenting is Not a Vehicle for Redesigning inplace_vector

NBコメントを通したinplace_vectorの再設計の試みに反対する提案。

C++26は2025年6月の全体会議の時点で機能追加は終了しており、あとはNBコメント(各国委員会からのC++26CDへのフィードバックコメント)への対応のみが実質的な変更が入るフェーズとなっています。

そこで、inplace_vectorに対する設計変更を求めるコメントがいくつか来ているようで、この提案はそれらを採択しないようにすることを強く主張するものです。

来ているNBコメントは主に次の事項についてのものの様です

  • アロケータ対応
    • P3160R2
    • 要素型に対してアロケータを伝播させるAllocator aware性をサポートさせる
  • キャパシティも参加する比較
    • P3698R0
    • 異なるキャパシティを持つinplace_vectorの比較ができないのを修正する
  • optional<T&>の使用
    • P3739R4
    • try_*_back()の戻り値をT*ではなくoptional<T&>にする

この提案ではいずれの設計変更についてもC++26のスコープでは反対しており、LEWGで合意済みの設計をLEWGで合意を得られなかった設計によってこのC++26スコープの最後で変更すべきではなく、NBコメントを利用してそのような再設計をしようとすべきではないとしています。

アロケータとoptional<T&>については否定的ですが、比較の問題についてはC++29以降で修正の可能性があるものの検討に時間が必要としています。

P3831R0 Contract Labels Should Use Annotation Syntax

契約アサーションに対するラベルの指定のために、アノテーション構文を使用するようにする提案。

P3400R1ではC++29以降のContracts機能拡張の大きなものの一つとして契約アサーションに対するラベルの指定を提案しています。契約アサーションに対するラベルは、契約アサーションのセマンティクス(評価セマンティクスや違反処理など)をよりローカルに制御するためのものです。

struct my_label_t {};

constexpr my_label_t my_label;

int f(int i)
    pre<my_label>(i > 0)
    post<my_label>(r: r > 0)
{
    contract_assert<my_label>(i > 0);
    // ...
}

P3400R1ではpre<my_label>(expr)のように、アサーションを導入するキーワードに続く<>の中にラベルを指定します。これは各アサーション構文を関数と見立てた時に、関数をテンプレートによってパラメータ化することを模倣した構文です。しかし、これにはいくつか問題があります

  • NTTPに対するクラス型の要件とラベルオブジェクトの要件が異なる
    • NTTPではクラス型は構造的な型である必要がある一方、ラベル型は単にクラス型であればよい
    • 要件が異なるものが同じ構文を使用していると混乱を招く
  • このラベル指定構文のセマンティクスを個別に指定する必要がある
    • ラベル指定構文という契約アサーション専用の構文を導入すると、そのセマンティクスを個別に管理し、言語の他の部分との相互作用を常に考えなくてはならなくなる
  • 違反ハンドラからラベルオブジェクトを取得する構文が使いづらい
    • void* contract_violation::control_object()を提案しているが、戻り値は型情報が欠落している
    • 戻り値はラベルオブジェクトが多態的である場合のみ非nullptrを返し、dynamic_castで復元する必要があり、危険
  • 環境ラベル(ambient-control objects)に関して未解決の設計上の問題がある

契約アサーションに対するラベル指定は本質的に、無視できない属性構文を導入しようとしています。C++26にはこのためのソリューションが導入されており、それはリフレクションにおけるアノテーションです。

P3394R4で導入されたリフレクションにおけるアノテーションは、宣言にアノテーションを付加しそれを読み取れるようにする機能です。このようなアノテーションはリフレクションによるコード生成において便利なものであり、専用の構文とAPIによって扱えるようになっています。

struct C {
  [[=1]]  int a;
};

無視できない属性としてアノテーションを利用することには次のようなメリットがあります

  • 属性構文の構文スペースを利用しているため、構文スペースを確保する必要がない
  • アノテーションは無視できない属性そのものであるため、属性構文を利用することには妥当性がある
  • アノテーションをクエリするAPIはすでに用意されているためそれを利用でき、リフレクションの他の部分との一貫性が向上する

これらにより、この提案では契約アサーションに対するラベル指定構文として独自の構文を発明するのではなくアノテーションを利用する事を提案しています。

P3400R1 この提案
struct my_label_t {};

constexpr my_label_t my_label;

int f(int i)
  pre<my_label>(i > 0)
  post<my_label>(r: r > 0)
{
  contract_assert<my_label>(i > 0);
  ...
}
struct my_label_t {};

constexpr my_label_t my_label;

int f(int i)
  pre [[=my_label]] (i > 0)
  post [[=my_label]] (r: r > 0)
{
  contract_assert [[=my_label]] (i > 0);
  ...
}
P3400R1 この提案
struct my_label_2_t {};
constexpr my_label_2_t my_label_2;

void g(int i)
  pre<my_label | my_label_2>(i > 0);
struct my_label_2_t {};
constexpr my_label_2_t my_label_2;

void g(int i)
  pre [[=my_label | my_label_2]] (i > 0);
// or, optionally
  pre [[=my_label, =my_label_2]] (i > 0);

違反ハンドラにおけるラベルの取得のAPIは、annotations()というmeta::infoのシーケンスを取得する関数をcontract_violationに追加することを提案しています。

namespace std::contracts {
  class contract_violation {
  public:
    ...

    consteval std::vector<std::meta::info> annotations() const noexcept;
    
    ...
  }
}
struct my_dynamic_tag {
  constexpr my_dynamic_tag() = default;
};
inline constexpr my_dynamic_tag my_dynamic_label;

void handle_contract_violation(const contract_violation& violation) {
  // ラベルの取得
  constexpr auto annotations = std::define_static_array(violation.annotations());
  template for (constexpr auto annotation : annotations) {
    if constexpr (std::meta::is_same_type(
      std::meta::type_of(annotation), ^^my_dynamic_tag
    )) {
      std::cout << "Dynamic Tag!\n";
      return;
    }
  }
  std::cout << "No Dynamic Tag\n";
}
void f()
  pre<my_dynamic_label>(false)  // prints "Dynamic Tag!"
  pre(false);                   // prints "No Dynamic Tag"

さらに、P3400R1の環境ラベルの指定(Ambient-Control Objects)については次のような設計上の決定事項が未解決であると指摘しています

  • ラベルは対象の契約アサーションのラベルリストのどこに追加するのか?
    • |による合成が対称的ではない場合、問題になる
  • 暗黙の制御オブジェクト宣言(環境ラベルの注入宣言)はなぜ複数回の宣言ができないのか?
  • 基底クラスや囲む名前空間などで指定された環境ラベルを継承することはできるか?

また、P3394R4ではアノテーションにコールバックを持たせて、宣言からそれを自動的に呼び出すようにする方向性の有用性について言及されており(提案はされていない)、このような機能とリフレクションによるコード生成機能(C++29以降のもの)を検討すると、ラベルの自動的な付与が可能になる可能性があります。例えば、名前空間やクラスなどに特定のアノテーション(例えば[[=attach(label1, label2)]])を付加することで、それに属するすべての宣言に対して自動的にラベルを付加することができるかもしれません。

これらのことから、より汎用的なアノテーションコールバック機能を検討するために、環境ラベルの指定については延期することを推奨しています。

P3832R0 Timed lock algorithms for multiple lockables

タイムアウト付きでミューテックスのロックを取得する関数の提案。

複数のロック可能オブジェクト(ミューテックス)に対してロックを獲得する処理を簡素化するために、std::lockstd::try_lockstd::scoped_lockなどが用意されています。これらの操作はBasicLockableLockable要件の範囲でロック取得を行いますが、TimedLockable要件を満たすオブジェクトのtry_lock_*に対応するロック取得を行う操作がありません。

これにより、複数のミューテックスの時間制限付きのロック取得を行おうとするユーザーは、try_lock(), unlock()とリトライを使用して独自のデッドロック回避ロック取得操作を記述する必要があります。

この必要をなくすために、この提案では複数のミューテックスの時間制限付きのロック取得を行うための関数を追加することを提案しています。

追加しようとしているのは次の2つの関数です

namespace std {
  template <class Clock, class Duration, class... Ls>
  int try_lock_until(const chrono::time_point<Clock, Duration>& abs_time,
                     Ls&... ls);

  template <class Rep, class Period, class... Ls>
  int try_lock_for(const chrono::duration<Rep, Period>& rel_time, Ls&... ls);
}

これはそれぞれTimedLockable要件にある操作のtry_lock_until()try_lock_for()に対応するもので、try_lock_until()は指定された時刻までの間にすべてのミューテックスに対してロック取得を試みる関数で、try_lock_for()は指定された時間内ですべてのロック取得を試みる関数です。戻り値はstd::try_lock()と同じ意味です。

std::lockstd::try_lockと異なる点として、受けるミューテックスの個数は0個からになっています。これは、unique_lockscoped_lockを組み合わせたunique_multilockの様な汎用の関数(0個以上のミューテックスを取る)を作成する際に簡単に使用できるようにするためです。

std::timed_mutex m1, m2;
if (std::try_lock_for(100ms, m1, m2) == -1) {
  // success
  std::scoped_lock sl(std::adopt_lock, m1, m2);
  ...
} else {
  // failed to acquire within timeout
  ...
}

P3834R0 Defaulting the Compound Assignment Operators

複合代入演算子をデフォルト実装できるようにする提案。

複合代入演算子+=-=など)には対応する二項演算子を用いた典型的な実装が広く知られており、異なる意味でオーバーロードされるのは稀です。そのため、この典型的な実装を言語におけるデフォルトとして、複合代入演算子default指定による実装に利用しようという提案です。

例えば整数型のラッパクラスの場合、四則演算に対応する演算子をすべて実装すると次のようになります

struct int_wrapper{
  int data_;

  int_wrapper(int in) : data_{in} {}
};

int_wrapper operator+(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ + rhs.data_};
}
int_wrapper& operator+=(int_wrapper& lhs, int_wrapper rhs){
  return lhs = lhs + rhs;
}
int_wrapper operator-(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ - rhs.data_};
}
int_wrapper& operator-=(int_wrapper& lhs, int_wrapper rhs){
  return lhs = lhs - rhs;
}
int_wrapper operator*(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ * rhs.data_};
}
int_wrapper& operator*=(int_wrapper& lhs, int_wrapper rhs){
  return lhs = lhs * rhs;
}
int_wrapper operator/(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ / rhs.data_};
}
int_wrapper& operator/=(int_wrapper& lhs, int_wrapper rhs){
  return lhs = lhs / rhs;
}
int_wrapper operator%(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ % rhs.data_};
}
int_wrapper& operator%=(int_wrapper& lhs, int_wrapper rhs){
  return lhs = lhs % rhs;
}

ここでの複合代入演算子の実装はすべて対応する二項演算子を用いた典型的なものであり、これをデフォルトとしてdefault実装できるようにする場合次のように書き直せます

struct int_wrapper{
  int data_;

  int_wrapper(int in) : data_{in} {}
};

int_wrapper operator+(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ + rhs.data_};
}
int_wrapper operator-(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ - rhs.data_};
}
int_wrapper operator*(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ * rhs.data_};
}
int_wrapper operator/(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ / rhs.data_};
}
int_wrapper operator%(int_wrapper lhs, int_wrapper rhs) {
  return int_wrapper{lhs.data_ % rhs.data_};
}

int_wrapper& operator+=(int_wrapper& lhs, int_wrapper rhs) = default;
int_wrapper& operator-=(int_wrapper& lhs, int_wrapper rhs) = default;
int_wrapper& operator*=(int_wrapper& lhs, int_wrapper rhs) = default;
int_wrapper& operator/=(int_wrapper& lhs, int_wrapper rhs) = default;
int_wrapper& operator%=(int_wrapper& lhs, int_wrapper rhs) = default;

この提案は、複合代入演算子についてこのようなdefault実装を可能にすることを提案しています。

この提案における複合代入演算子(以下+=で代表)のデフォルト実装では、次のシグネチャを許容します

// AとBは同じ型であってもよい
A& operator+=(A& lhs, const B& rhs) = default;
A& operator+=(A& lhs, B rhs)        = default;
A& operator+=(A& lhs, B&& rhs)      = default;

// P2953が禁止しようとしているシグネチャ(ひとまずサポートを提案
A& operator+=(A&& lhs, const B& rhs) = default;
A& operator+=(A&& lhs, B rhs)        = default;
A& operator+=(A&& lhs, B&& rhs)      = default;

これらのシグネチャはそれぞれ意味が異なるため、ある型Aに対して同時に定義することができます。いずれのシグネチャにおいても、その関数本体は次のような実装と同等になります

{
  return lhs = std::move(lhs) + std::move(rhs);
}

このデフォルト宣言は通常のメンバ関数、フリー関数、明示的オブジェクト引数を取る関数、でサポートすることを提案しています。

struct S1{
  int value_;
  S1 operator+(S1 rhs) const { /*...*/ }
  S1& operator+=(S1 rhs) = default; // メンバ関数
};

struct S2{
  int value_;
  S2 operator+(S2 rhs) const { /*...*/ }
  S2& operator+=(this S2& lhs, S2 rhs) = default; // 明示的オブジェクト引数を取る関数 
                                                  // 引数型はそのクラスと同じ型(のCV参照修飾)でなければならない
};

struct S3{
  int value_;
  S3 operator+(S3 rhs) const { /*...*/ }
};
S3& operator+=(S3& lhs, S3 rhs) = default;  // フリー関数

このような複合演算子のデフォルト実装に使用される対応する二項演算子は、このデフォルト実装(の関数本体のコンテキスト)からアクセス可能であればよく、非メンバやメンバなど定義方法を問いません。

デフォルト実装における一番右側の代入演算子の戻り値型がA&(第一引数の素の型の左辺値参照型)ではない場合、デフォルト実装のコンテキストでABのどちらかが完全型ではない場合、コンパイルエラーとなります。また、デフォルト実装のコンテキストから互換性のある引数型を持つ対応する二項演算子が見つからない(アクセスできない)場合、そのデフォルト実装は暗黙的にdeleteされます。これらの事は、通常の代入演算子default実装の規則と一致しています。

P3835R0 Contracts make C++ less safe -- full stop

現状のContracts機能は安全性を低下させるのでC++26から削除する提案。

C++26のContracts機能にはセマンティクスを個別に制御する方法がなく、特定のコンポーネント(翻訳単位)にだけ特定のセマンティクスを持たせるということができません。それにより、あるプログラム内で契約チェックを確実に行おうとする時でも、翻訳単位を跨いだ別のコンポーネントや共有ライブラリまでそれを強制する方法がありません。

これにより、ライブラリ出荷側が契約チェックを有効化して出荷しており特定の契約違反が検出されることを期待している場合でも利用者側が契約チェックをオフにしていれば検出されなくなる可能性があります。

この提案ではこのことによって契約機能によりC++コードの安全性がむしろ低下するとして、C++26から削除することを提案しています。

議論の過程は明らかではありませんが、この提案はリジェクトされています。

P3836R0 Make optional<T&> trivially copyable

std::optional<T&>トリビアルコピー可能であることを規定する提案。

C++26に導入されたstd::optional<T&>std::optionalの部分特殊化として別に実装され、特にポインタのラッパとして実装されることが想定されます。そのためstd::optional<T&>トリビアルコピー可能であるはずですが、明確に規定されてはいません。

標準ライブラリの概要では次のようなクラス型として説明されています

namespace std {
  template<class T>
  class optional;

  template<class T>
  class optional<T&> {
    ...

    public:
      // [optional.ref.ctor], constructors
      constexpr optional() noexcept = default;
      
      ...

      constexpr ~optional() = default;

      ...

    private:
      T* val = nullptr; // exposition only
  };
}

これはあくまで説明のための例であり、実装はここにメンバを追加することができるため、純粋なT*のラッパとなりトリビアルコピー可能性が満たされるかは実装の裁量によります。とはいえ、それを要求することは過剰な制約にはならないはずです。

これは単に仕様の見落としであると思われるため、std::optional<T&>トリビアルコピー可能であることを規定し保証しようとする提案です。

これには

  • プライマリテンプレートであるstd::optional<T>との一貫性が保たれる
  • トリビアルコピー可能性により、std::optional<T&>をプロセス間やCPUとGPU間でコピーされる構造体に格納できる
  • トリビアルコピー可能性により、std::optional<T&>は暗黙的な生存期間を持つ型(implicit-lifetime class type)になり、代入演算子を用いて共用体内で生存期間を開始できるようになる

などのメリットがあります。

この問題には対応するNBコメントが提出されているようです。

P3838R0 Restoring Private Module Fragments

プライベートモジュールフラグメントの宣言が文法違反にならないようにする提案。

プライベートモジュールフラグメントは一つのファイルでモジュールのインターフェースと実装を記述するための機能で、プライベートモジュールフラグメント宣言によってファイルをインターフェース部と実装部に分割します。

module M;

...

module : private; // プライベートモジュールフラグメント宣言

...

P3034R1ではモジュール名としてマクロを使用できないようにプリプロセッサの文法調整が行われましたが、この時の文法調整によってこのプライベートモジュールフラグメントの宣言が名前の無いモジュールパーティションとして解釈されるようになっていました。

module M:part;  // モジュールパーティションの宣言

無名のモジュールは許可されないため、これはプリプロセッサより後の翻訳フェーズにおいてエラーになります。

モジュール宣言はプリプロセッサで特別に処理された後ユーザーが記述できないキーワードによる宣言に置き換えられてC++コードとしてコンパイルされます。P3034R1のモジュール宣言のプリプロセッサ文法はおおむね次のようなものでした

pp-module:
  export(opt) module pp-tokens(opt) ; new-line

このpp-tokensの部分は次のような文法です

pp-module-name pp-module-partition(opt) pp-tokens(opt)

pp-module-partition:
  : pp-module-name-qualifier(opt) identifier

この定義に照らすと、module : privateは名前が無いモジュールパーティションとしてプリプロセッサでは解釈されてしまいます。プライベートモジュールフラグメントには専用のプリプロセッシングディレクティブ定義が用意されていますが、文法的にはこちらにかかってしまうようです。

この提案はこの問題の解決のために文法を調整しようとするものです。

修正はモジュール名とパーティション名の文法定義を分割することによって行われています

pp-module:
  export(opt) module pp-module-name(opt) pp-module-partition(opt) pp-tokens(opt) ; new-line

モジュール名が必須であることはpp-module-nameの文法で定義しています。

なお、この提案はモジュール名やパーティション名をマクロで導入できるようにはしていません。

理由は明確ではないですが、この提案はリジェクトされたようです。

おわり

この記事のMarkdownソース




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

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