[C++]std::start_lifetime_as()によるtype punning

C++ type punning環境最前線における最新のtype punning手法の紹介です。

std::start_lifetime_as()

std::start_lifetime_as()はC++23で追加された関数で、指定されたストレージでimplicit-lifetime typeな型のオブジェクトの生存期間を明示的に開始させるための関数です。

#include <memory>

// 独自アロケータのようなもの
namespace my_alloc {
  alignas(alignof(void*)) std::byte storage[1000];

  template<typename T>
  auto allocate_memory() -> T* {
    // 適当実装です
    return static_cast<T*>(static_cast<void*>(storage));
  }
}

// Xはimplicit-lifetime type
struct X { int a, b; };

static_assert(std::is_implicit_lifetime_v<X>);  // ✅

int main() {
  X* memory = my_alloc::allocate_memory<X>();

  // Xはimplicit-lifetime typeではあるものの、生存期間が開始されていない(allocate_memory()がユーザー定義関数であるため)
  X* ub_ptr1 = reinterpret_cast<X*>(memory);  
  ub_ptr1->a = 1; // 💀 UB
  ub_ptr1->b = 2; // 💀 UB

  // start_lifetime_as()を通すことで生存期間を開始する
  X* valid_ptr = std::start_lifetime_as<X>(memory);
  valid_ptr->a = 1; // ok
  valid_ptr->b = 2; // ok
}

このオブジェクトの生存期間の開始はコンストラクタ呼び出しを伴うものではなく、論理的なものです。std::start_lifetime_as()は実質的には何もしない関数です。

std::start_lifetime_as()の効果についての規定

std::start_lifetime_as()がオブジェクトの生存期間を開始する仕組みは関数の効果(Effects)として次のように規定されています

Implicitly creates objects ([intro.object]) within the denoted region consisting of an object a of type T whose address is p, and objects nested within a, as follows: The object representation of a is the contents of the storage prior to the call to start_lifetime_as. The value of each created object o of trivially copyable type ([basic.types.general]) U is determined in the same manner as for a call to bit_cast<U>(E) ([bit.cast]), where E is an lvalue of type U denoting o, except that the storage is not accessed. The value of any other created object is unspecified.

翻訳(Powered by PLAMO翻訳)

指定された領域内に、アドレスが p である型 T のオブジェクト a 、および a 内にネストされたオブジェクトを暗黙的に作成します。
a のオブジェクト表現は、start_lifetime_as() 関数呼び出し前のストレージの内容となります。
トリビアルコピー可能な型([basic.types.general]) U の作成された各オブジェクト o の値は、bit_cast<U>(E)([bit.cast])への呼び出し時と同様に決定されます。ここで、 E は評価結果が o となる型 U の左辺値式ですが、この場合(pの)ストレージにはアクセスしません。
その他の場合の作成されたオブジェクトの値は未規定です。

ここでのpTstd::start_lifetime_as<T>(p)のように呼び出した時の引数のポインタと作成対象のオブジェクト型です。

implicit-lifetime typeが言語の仕組みとして特定の操作(malloc()memcpy()など)において暗黙的にオブジェクトが作成される場合は単にそのコンテキストで適切なオブジェクトが作成されるというだけでそれ以上の事はせず、特に作成されたオブジェクトの持つ値やオブジェクト表現についての保証は何もありません。

しかし、std::start_lifetime_as()はトリビアルコピー可能な型に関して言語の仕様よりも拡張された保証を持っています。つまり次の二文です

a のオブジェクト表現は、start_lifetime_as() 関数呼び出し前のストレージの内容となります。

トリビアルコピー可能な型([basic.types.general]) U の作成された各オブジェクト o の値は、bit_cast<U>(E)([bit.cast])への呼び出し時と同様に決定されます。

そして、その動作はstd::bit_castを参照する形で指定されています。

std::bit_castの規定は様々なケースを考慮してさらに複雑に書かれていますのでここでは割愛します。基本的にはビットレベルの再解釈キャストが可能であるということを理解しておけばよいでしょう。

興味ある型はこちらを参照してください

なお、オブジェクト表現というのは簡単に言えば型をバイト配列として読み出した際のビット列の事です。このビット列のうち、その型の値として有効であるビット列の事を値表現と呼びます。構造体のパディングはオブジェクト表現に含まれるものの値表現には含まれません。

type punning

結果的に、T* vp = std::start_lifetime_as<T>(p)の様な呼び出しにおいては次のことが保証されています

  • *vpのオブジェクトのオブジェクト表現はpのストレージの内容と一致する
  • Tがトリビアルコピー可能である場合、*vpのオブジェクトの値はpのストレージのビット列を再解釈して決定される

すなわち、Tがトリビアルコピー可能な型であればstd::start_lifetime_asは合法的にtype punningが可能です。

#include <stdfloat>
#include <cstring>
#include <memory>
#include <bit>
#include <array>

int main() {
  const std::float32_t f = 3.14f;
  alignas(alignof(std::int32_t)) std::byte storage[sizeof(std::int32_t)];  

  // オブジェクト表現のコピー
  std::memcpy(storage, &f, sizeof(std::int32_t));

  // std::int32_tの生存期間開始
  auto* p = std::start_lifetime_as<std::int32_t>(storage);

  // bit_cast()
  auto rv = std::bit_cast<std::int32_t>(f);

  std::println("start_lifetime_as: {}", *p);
  std::println("bit_cast         : {}", rv);
}
start_lifetime_as: 1078523331
bit_cast         : 1078523331

この*pの値がstd::bit_cast<std::int32_t>(f)として再解釈キャストを行った場合と一致することが保証されます。

これを利用するには、std::start_lifetime_asする際の型Tがimplicit-lifetime type(多分こっちを違反するとコンパイルエラーになる)かつトリビアルコピー可能である必要があります。

std::bit_castが有効ではない場合、不定値やEVなど

std::start_lifetime_asによるtype punningの保証はstd::bit_castに委ねられているため、そちらの規定で未定義だとか不定値だとか言われている場合は同様に有効ではなくなります。

例えばstd::bit_castの規定では、ソースのオブジェクト表現を再解釈した結果がキャスト先の型の値表現と対応しない場合(ビットキャスト後のビット列がその型で有効なビット列ではない場合)には結果が未定義とされています。また、不定値を持つビットをbit_castした結果が値表現のビットに含まれてしまう場合はその値も不定値となります。さらにはerroneous valueも伝播します。

パディングビットを再解釈しようとする例

#include <stdfloat>
#include <cstring>
#include <memory>
#include <bit>
#include <array>

// パディングがある
struct X {
  std::int8_t b;
  std::int32_t n;
};

static_assert(std::is_implicit_lifetime_v<X>);      // ✅
static_assert(std::is_trivially_copyable_v<X>);     // ✅
static_assert(sizeof(X) == sizeof(std::float64_t)); // ✅

int main() {
  const X x{ .b = 0, .n = 0 };
  alignas(alignof(std::float64_t)) std::byte storage[sizeof(std::float64_t)];

  // オブジェクト表現のコピー
  std::memcpy(storage, &x, sizeof(std::float64_t));

  // std::float64_tの生存期間開始
  auto* p = std::start_lifetime_as<std::float64_t>(storage);
  
  // *pの値は不定値
  auto ub = *p; // 💀 UB
}

この例では*pを読み出すと不定値を読み出すことになり未定義動作になります。std::float64_tオブジェクトそのものは作成済みであるため、上書きすることは問題なくできます。

利点

基本的にはstd::bit_castを使った方がほとんどの場合に良いでしょう。implicit-lifetime typeに限定されず、定数式で使用でき、手動memcpyステップが無いので使いやすいなどがあります。

あえてこの方法の利点を挙げるなら、ビット再解釈のためにコピー的操作が不要であることがあります。

std::bit_castT r = std::bit_cast<T>(src)のように呼び出してsrcからrへビットコピーを行ったうえでrのオブジェクトの生存期間を開始するような動作をします。これは操作としてコピーを暗に含んでいます。

もしあるストレージにすでに所望のオブジェクト表現が存在していることが分かっていて、単にその領域をそのまま再解釈したい場合、std::bit_castのコピー動作は不要であり、省きたいかもしれません。

その場合は、std::start_lifetime_asのこの方法が有効であるかもしれません

#include <stdfloat>
#include <cstring>
#include <memory>
#include <bit>
#include <array>

int main() {
  {
    std::float32_t f = 3.14f;

    // 暗にコピーを含む
    std::int32_t r = std::bit_cast<std::int32_t>(f);
  }
  {
    std::float32_t f = 3.14f;

    std::destroy_at(&f);  // 多分不要

    // ストレージを再利用してstd::int32_tへ再解釈
    auto* p = std::start_lifetime_as<std::int32_t>(&f);
    
    std::int32_t v = *p; // ok

    std::destroy_at(&p);  // 多分不要
  }
}

とはいえstd::bit_castの呼び出し程度は最適化で簡単に消せる気がするので、このことが本当に有用であるかはよく分かりません・・・

std::start_lifetime_as_array()

std::start_lifetime_as_array()は指定領域に指定サイズの配列型の生存期間を開始する関数で、その動作は要素ごとにstd::start_lifetime_as()によって指定されているため、同様のことが可能になります。配列全体ではなく配列の各要素ごとにですが、配列の場合各要素間にパディングが無いためある型を別の型の配列として読み替えるということもできます。

#include <stdfloat>
#include <cstring>
#include <memory>
#include <bit>
#include <span>

int main() {
  const std::float64_t f = 2.718;

  alignas(alignof(std::int16_t)) std::byte storage[sizeof(std::float64_t)];
  
  // オブジェクト表現のコピー
  std::memcpy(storage, &f, sizeof(std::float64_t));

  // std::int16_t[4]の生存期間開始
  auto* p = std::start_lifetime_as_array<std::int16_t>(storage, 4);

  std::println("{}" , std::span{p, 4});
}
[14680, -14156, -16778, 16389]

ちなみにこの場合、配列型を返却できないことによりstd::bit_castが素直には使用できないため、こちらも利点となりえるかもしれません。

int main() {
  const double f = 2.718;

  auto rv1 = std::bit_cast<std::int16_t[4]>(f); // ng、一般的に関数は生配列を返せないことによるエラー

  auto rv2 = std::bit_cast<std::array<int16_t, 4>>(f);  // ok、std::arrayはreturnできる
}

参考文献

この記事のMarkdownソース

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

文書の一覧

全部で26本あります。

もくじ

N5034 WG21 Agenda 23-28 March 2026, Croydon, UK

2026年3月に予定されている全体会議のアジェンダ。

開催日時や大まかなスケジュールが記載されています。

P1000R7 C++ IS Schedule (proposed)

C++29策定までのスケジュールなどを説明した文書。

このリビジョンではC++29策定までの3年間のスケジュールが追記されています。それ以外の部分はR6と同じ内容のようです。

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

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

以前の記事を参照

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

  • P3834の提案を採択する場合でもこの提案が役立つことを動機に追加
  • C++26に含まれなかった標準バージョンや機能への参照を更新
  • "ambitious"としていた提案をデフォルトにする

などです。

P3039R1 Automatically Generate operator->

<=>演算子の様な書き換えによってoperator->を導出する提案。

以前の記事を参照

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

  • 著者の追加
  • operator->を右辺値に適用することに関する2つ目の動機を追加
  • 提案する文言の追加
  • operator->を生成するのではなく書き換えすることを説明する表現に変更

などです。

P3373R2 Of Operation States and Their Lifetimes

P2300のstd::executionにおいて、構成された非同期アルゴリズムの内部状態の生存期間を特定の場合に限り短くする提案。

以前の記事を参照

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

  • execution::splitが削除されたことに伴って、その言及を削除
  • 文言の追加
  • 呼び出し可能なsenderファクトリ生存期間に関する議論を追加
  • 実装経験を追加

などです。

P3795R1 Miscellaneous Reflection Cleanup

リフレクション関連提案の間での文言の整合性を調整する提案。

以前の記事を参照

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

  • annotations_ofdata_member_specの記述漏れを修正
  • 最新のWDにリベース
  • is_inline, is_constexpr, is_constevalに関する提案を削除

などです。

P3826R3 Fix Sender Algorithm Customization

senderアルゴリズムのカスタマイズ方法を修正する提案。

以前の記事を参照

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

  • R2に寄せられたフィードバックへ対応
  • get_completion_domain<>(attrs, env...)クエリを追加
    • デフォルトはget_completion_domain<set_value_t>(attrs, env...)と同等になる
  • アルゴリズムのカスタマイズをC++29へ延期する提案を削除

などです。

この提案は以前はsenderアルゴリズムのカスタマイズ機能をC++29まで延期しようとしていましたが、このリビジョンからはC++26で修正する方向に舵を切ったようです。

P3865R1 Class template argument deduction (CTAD) for type template template parameters

型テンプレートテンプレートパラメータに対するCTADを許可する提案。

以前の記事を参照

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

  • constant template parameterに関する議論を追加
  • P3863R0からの文言変更を追加

などです。

P3873R0 2025-10 Library Evolution Poll Outcomes

2025年10月に行われたLEWGにおける投票の結果。

次の提案が投票にかけられました

P3774はC++26、P3798はC++29を目標としてLWGに転送されました。投票の際のコメントが記載されています。

P3911R1 RO 2-056 6.11.2 [basic.contract.eval] Make Contracts Reliably Non-Ignorable

P3911R2 RO 2-056 6.11.2 [basic.contract.eval] Make Contracts Reliably Non-Ignorable

契約アサーションの評価セマンティクスから、ignoreの選択を制御できるようにする提案。

以前の記事を参照

R1での変更は

  • EWGの指示事項の実施に注力する
    • Option1の方向性を追求
  • 謝辞の追加
  • TL;DRセクションの追加
  • EWGの合意があるまで、セクション7の文言を削除する
  • §8 Frequently Asked Questions の追加

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

  • Section §1 (introduction): 動機を補足
  • Section §3 (proposal): §3.2 (prior work) と §3.3 (ABI implications)を追加
  • FAQの更新
    • ヘッダファイルへの依存が不要であることを明記
    • §8.11 (tooling), §8.12 (unchanged semantics), §8.13 (predicate evaluation count), §8.14 (previous implementation experience)の追加

などです。

EWGの議論においては、この提案の方向性(enforceセマンティクス固定アサーション)には強い合意があったものの、結局リジェクトされています。C++26 Contractsに関してはこの提案の変更は適用されません。

P3927R0 task_scheduler Support for Parallel Bulk Execution

task_schedulerparallel_schedulerを介してバルク実行する場合に並列化されるようにする提案。

execution::task_schedulerexecution::taskのために導入されたスケジューラのラッパ型で、execution::taskがその直接のコルーチンの実行に使用するスケジューラ(current scheduler)を保存するための型消去ラッパです。その性質上、task_schedulerはラップするschedulerで可能な操作を可能な限りそのまま提供できる必要がありますが、parallel_schedulerexecution::bulkによる並列バルク実行に関してはそうなっておらず、適切にラップできていません。

この提案は、execution::task_schedulerparallel_scheduler実装をラップしている状態でexecution::bulkで使用された時でも、ラップしない場合と同等のサポート(すなわち並列バルク実行)がされるように改修するものです。

改修のためにまず、execution::task_schedulerの型消去方法を見直します。execution::task_schedulerは次のような構造になっており、shared_ptr<void>メンバによってschedulerの型消去を行っています。

namespace std::execution {
  class task_scheduler {
    class sender; // exposition only
    template <receiver R>
    class state;  // exposition only

  public:
    using scheduler_concept = scheduler_t;

    template <class Sch, class Allocator = allocator<byte>>
      requires (!same_as<task_scheduler, remove_cvref_t<Sch>>)
        && scheduler<Sch>
    explicit task_scheduler(Sch&& sch, Allocator alloc = {});

    sender schedule();

    friend bool operator== (const task_scheduler& lhs, const task_scheduler& rhs) noexcept;

    template <class Sch>
      requires (!same_as<task_scheduler, Sch>)
      && scheduler<Sch>
    friend bool operator== (const task_scheduler& lhs, const Sch& rhs) noexcept;

    private:
      shared_ptr<void> sch_; // exposition only 👈
  };
}

提案ではこのsch_の型をshared_ptr<void>からshared_ptr<parallel_scheduler_backend>に変更することを提案しています。parallel_scheduler_backendは同様に型消去ラッパであるparallel_schedulerが型消去を行う際のbulkschedulerのインターフェース基底クラス型で、バルク実行の3つのsenderアダプタに対応した操作を備えています。

namespace std::execution {
  struct parallel_scheduler_backend {
    virtual ~parallel_scheduler_backend() = default;

    virtual void schedule(receiver_proxy&, std::span<std::byte>) noexcept = 0;
    virtual void schedule_bulk_chunked(size_t, bulk_item_receiver_proxy&, std::span<std::byte>) noexcept = 0;
    virtual void schedule_bulk_unchunked(size_t, bulk_item_receiver_proxy&, std::span<std::byte>) noexcept = 0;
  };
}

これは具体的なscheduler型によって、例えば次のように継承して使用します

template<scheduler Sch>
struct task-scheduler-backend : parallel_scheduler_backend {           // exposition only
  explicit task-scheduler-backend(Sch sch) : sched_(std::move(sch)) {}

  void schedule(receiver_proxy& r, span<byte> s) noexcept override;
  void schedule_bulk_chunked(size_t shape, bulk_item_receiver_proxy& r,
                             span<byte> s) noexcept override;
  void schedule_bulk_unchunked(size_t shape, bulk_item_receiver_proxy& r,
                               span<byte> s) noexcept override;

  Sch sched_;
};

task_schedulerに対してschedule()を呼んだ段階では、task_schedulerが後の処理のためのsenderを返すだけでこれらのクラスはまだ何も呼ばれません。task_schedulersenderが他のsenderとチェーンされ、最後にreceiverconnectされて生成されたoperation_stateオブジェクトに対してstartされた時に、task_schedulersendertask_schedulerが保持するshared_ptr<parallel_scheduler_backend>メンバを介してこのparallel_scheduler_backend実装が呼ばれます。

parallel_scheduler_backendの実装クラスでは、3つの操作はいずれも型消去対象のschedulerに処理を転送します。その際、task_schedulersenderconnectされたreceiverbulk_item_receiver_proxy経由で渡して(これも型消去ラッパ)、そのbulk_item_receiver_proxyと型消去対象のschedulerschedule()の結果senderとをconnectしてstartします。

schedule_bulk_chunked()などの場合は、型消去対象のschedulerexecution::bulkに渡されるように構成することによって、型消去対象のschedulerexecution::bulkの最適化を提供している場合(parallel_schedulerの場合)にその最適な実装が選択されるようになります(そうではない場合でもexecution::bulkのデフォルト実装である逐次実装にフォールバックする)。

なお、これらのparallel_scheduler_backendおよびその実装クラスはその名前やメンバ関数とは裏腹にschedulerではありません。これらのクラスはあくまで型消去schedulerにおいて型消去を行い、型消去対象のschedulerに巧妙にディスパッチを行うレイヤを担う部品です。

もう一つ、task_schedulerのドメインにおいて、bulk_chunked/bulk_unchunked操作に対応したsenderを適切に変換するためのtransform_senderを追加します。これはこれらの様なバルクsenderのカスタマイズを検出し、task_scheduler(のsender)においてsch_->schedule_bulk_chunked(...)/sch_->schedule_bulk_unchunked(...)を使用するように変換するものです。

namespace std::execution {
  class task_scheduler::ts-domain : public default_domain {
  public:
    template<class BulkSndr, class Env>
      static constexpr auto transform_sender(set_value_t, BulkSndr&& bulk_sndr, const Env& env)
        noexcept;
  };
}

transform_sendersenderアルゴリズムの呼び出し時とsenderreceiverconnect時のそれぞれにおいてその時点のドメインを用いてsenderを適切に型変換する仕組みです。これは、senderアルゴリズムのカスタマイズ検出とディスパッチを担っています。senderとドメインと環境を用いて、execution::transform_sender(domain, sndr, env)のように呼ばれ、ドメインのものが使用される場合はdomain.transform_sender(sndr, env)のように呼ばれて、sndrの型が変わらなくなるまで再帰的に呼び出しを行います。

この大きく2つの変更によって、task_schedulerは型消去を行いながらバルクschedulerへの対応を行うことができるようになります。

この提案の内容はNVIDIAのCCCLというライブラリにおいて実装されているようです。また、これには関連するNBコメントがあるようです。

P3941R1 Scheduler Affinity

execution::affine_onアルゴリズムの改善提案。

以前の記事を参照

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

  • 提案する文言の追加

などです。

P3950R0 return_value & return_void Are Not Mutually Exclusive

コルーチンのプロミス型において、.return_value().return_void()を同時に宣言できるようにする提案。

プロミス型の.return_value().return_void()関数はコルーチンにおけるco_returnの2つの形式(オペランドのあり無し)に対応しており、プロミス型においてはどちらか片方しか書くことができません。この制限はオーバーロード解決やコンセプトなどよりも強い部分で行われており、この2つの名前の関数が同時に宣言されているとコンパイルエラーとなります。

template<typename ReturnType> 
struct some-promise-type { 
  
  void return_void() requires std::is_same_v<void, ReturnType> {
    /* ... */
  } 

  template<typename T = ReturnType> 
    requires std::is_same_v<T, ReturnType> 
  void return_value(T t) {
    /* ... */
  } 

  ...
}; 

このコードはテンプレートパラメータReturnTypeに関わらずコンパイルエラーとなります。

co_returnはそのオペランドのあるなしによって.return_value().return_void()の呼び出しに展開されるため、あるコルーチン内ではco_returnの2つの形式を同時に使用することができません。

my_coroutine<T> coro_f() {

  co_return;

  co_return ...;  // ng
}

この制限によりまず、上記のように型に対して適応的なテンプレートなプロミス型を定義することができません。

また、コルーチンではプロミス型に.return_void()が定義されていない場合にコルーチンの制御フローの終端に到達すると未定義動作となるのですが、.return_value()を定義してしまっているとそれを回避することができなくなります。

// my_coroutine<T>のプロミス型は.return_value()を定義しているとすると
my_coroutine<T> coro_f() {

  ...

  co_yield ...;

  ...
} // コルーチン終端への到達は未定義動作

.return_value().return_void()を同時に使用できない制限はおそらく、通常の関数のreturnでオペランドあるなしを同時に使用できないことから来ています。しかし、コルーチンは通常の関数とは明確に異なるもので、標準でもそれは認識されており、コルーチン定義の直接の関数は呼び出されるとプロミス型からコルーチンハンドルをラップする型を返す薄いラッパにすぎません。

コルーチン宣言の直接の戻り値型はコルーチンが返す値の型とは無関係であり、コルーチンおよびプロミス型はすでに1つのコルーチンで様々な値を返すことができるようになっています。

このことはC++26のstd::executionの存在によって特に重要になります。std::executionにおける処理の抽象であるsenderはまず関数の終了に3種類の方法があるうえ、値を返す場合でも複数の戻り値型を取ることができるようになっています。そして、コルーチンは変換を介してsenderとして扱うことができ、また逆にsenderもコルーチンとして扱えるため、std::executionにおいて非同期処理を記述する際はコルーチンが使用されることが多くなっていくと予想されます。また、execution::taskというそれを補助するライブラリ機能も提供されます。

その際に、senderは値完了において戻り値なしと戻り値ありを同時に宣言(完了シグネチャとして宣言)することができますが、上記の制限によってコルーチンではこれはできません。そのため現在はコルーチン -> senderにおいては

  • co_returnの様々な型のオペランドでの呼び出しは.return_value()の呼び出しが有効である限り許可され、異なる型を受ける.return_value()の呼び出しを介して内部的に異なるexecution::set_value完了シグネチャに変換することができる
  • タプルプロトコルを適切に使用すれば、異なる型の.return_value()の呼び出しを一つのexecution::set_value完了シグネチャに解決できる

一方で、sender -> コルーチンでは

  • execution::set_value_t()(値無し値完了)と他の形式のexecution::set_value_t(...)(値完了、異なる型を含みうる)が同時に完了シグネチャにある場合、コルーチンでは受け入れられない

このことはコルーチンの意味論的あるいは概念的な制約によるものではなく、単に.return_value().return_void()の同時宣言が禁止されている(オペランドありなしのco_returnを同時に使用できない)ことによるようです。これによって、コルーチンはstd::executionの強力な機能(通常の関数を超えている部分)を十分にサポートできなくなっています。

この提案は、これらのモチベーションより、コルーチンのプロミス型において.return_value().return_void()を同時に使用可能にすることを提案しています。そしてこれにより、1つのコルーチン内でco_returnのオペランド有無両方の形式が利用可能になります。

この提案はすでにC++29に向けてCWGでレビューを受けています。

P3951R0 String Interpolation Objects

フォーマット文字列補完を行うためのテンプレートリテラル(t"...")の提案。

この提案は、P3412で提案されているfリテラルと同じ方向性の機能を異なるアプローチで提案するものです。モチベーションなどについては以前の記事を参照して下さい。

P3412R3では、言語に深く組み込む形でfリテラルを導入しようとしています。f"{name}"のようなリテラルは、文脈に応じてstd::stringになったり、std::printに渡すことのできる引数列になったりします。その処理はコンパイルフェーズ中にリテラル文字列をパースしてフォーマット文字列と対象の式列を区別しておき、オーバーロード解決フェーズでstd::format呼び出しまたは引数列への展開が行われます。

このアプローチは非常に複雑かつほとんどstd::format/std::print専用の機能となり、それらライブラリ機能へ強く依存する機能をコア言語機能として組み込むことになります。P3412の現在の設計は現在のstd::format/std::printに強く依存していますが、これらの関数が何かしら変更されたときに言語機能の変更も同時に必要になる可能性があります。

また、f""のような一文字のリテラルプリフィックスがstd::stringの生成というコストのかかる操作を隠蔽していることにも問題があります。

この提案はそれに対して、文字列補完(フォーマット文字列補完)を行うオブジェクトを生成するためのテンプレートリテラルt"..."によるアプローチを提案するものです。

この提案のテンプレートリテラルはPythonのテンプレートリテラルを参考にしたもので、t"{name}"のようなフォーマット文字列を含むテンプレートリテラルは、このフォーマット文字列をある程度解析して分解した結果を保持するオブジェクトを返します。

例えば次のようなテンプレートリテラルの使用は

auto get_result() -> int { return 42; }

auto example() -> void {
  auto interp = t"The result is {get_result()}\n";
}

次のようなコードに展開されます

auto example() -> void {
  struct Template {
    static consteval auto fmt() -> char const* { return "The result is {}\n"; }
    static consteval auto num_interpolations() -> size_t { return 1; }
    static consteval auto string(size_t i) -> char const* {
      constexpr char const* data[] = {"The result is ", "\n"};
      return data[i];
    }
    static consteval auto interpolation(size_t i) -> std::interpolation {
      constexpr std::interpolation data[] = {{"get_result()", "{}", 0, 1}};
      return data[i];
    }

    int _0;
  };

  auto interp = Template{get_result()};
}

型名のTemplateは仮のものです。このテンプレートリテラルオブジェクト型からは次の公開情報が提供されます

  • fmt(): リテラルをパースして置換フィールド内の式を取り除いた後のフォーマット文字列
  • num_interpolations(): 置換フィールドの数
  • string(i): 置換フィールドを区切りとしたときの、i番目の部分文字列
    • 置換フィールドで始まっている/終わっている場合は、先頭/最後の文字列が空文字になる
  • interpolation(i): i番目の置換フィールドの補完情報
    • 1つの置換フィールドについて、fmt()と同様の文字列、式の文字列、置換フィールドのインデックス、ネストしたものも含めた式の数、を保持する集成体オブジェクトを返す
  • データメンバ: 各置換フィールドから抽出した式を評価した結果を保持する
    • メンバ名は未規定
    • 順番は置換フィールドの登場順と一致する
    • 型は対応する式をEとするとdecltype((E))

このようにテンプレートリテラルは文字列リテラルやフォーマット後文字列、あるいはフォーマット引数列へ展開されるのではなく、テンプレートリテラルをある程度分解した情報を保持した型のオブジェクトに展開されます。

std::printstd::format()では、オーバーロードを追加してテンプレートリテラルが生成するオブジェクトを受け取るようにし、その中でパースされたフォーマット文字列や式の情報を取得してから通常のオーバーロードを呼び出すようにして対応することができます。

template <TemplateString S>
auto print(S&& s) -> void {
  auto& [...exprs] = s;
  std::print(s.fmt(), exprs...);
}

テンプレートリテラルの字句解析

テンプレートリテラルのパースにおいては少なくとも置換フィールドを認識してそこから式に対応する文字列を抽出する必要があり、これはライブラリではなくコンパイラによって行われます。

テンプレートリテラルにおいての置換フィールドは次の2つの形式に分類されます

replacement-field:
  { expr }
  { expr : format-spec }

1つ目はそのままt"{expr}"のような形で、2つ目は{var:s}のようにフォーマットオプションが指定されたものです。ここではこれ以上の解析(フォーマットオプションの解析)は行われず、通常のフォーマット文字列とは異なり:の前にインデックス指定が来ることはありません。

このexprに対応する部分には任意の式(balanced token sequence)を記述することを許可するものの、()に囲まれない:が出現すると置換フィールド内の式の終了として認識されます。

テンプレートリテラルと字句解析結果の例

テンプレートリテラル フォーマット文字列部分 式部分
t"{x}" "{}" x
t"{[]{ return 42; }()}" "{}" []{ return 42; }()
t"{co_await f(x) + g(y) / z}" "{}" co_await f(x) + g(y) / z
t"{a::b}" "{::b}" a
t"{(a::b)}" "{}" (a::b)
t"{cond ? a : b}" "{:b}" cond ? a
t"{(cond ? a : b)}" "{}" (cond ? a : b)

テンプレートリテラルの字句解析はプリプロセッサの実行(翻訳フェーズ4)よりも前に行われます。この時、字句解析の結果(:の発見など)として無効な式が抽出される場合でも、字句解析フェーズではエラーにしません(テンプレートリテラルの展開後のオブジェクト初期化式のパース時にエラーになる)。

また、:は文字としてかっこに囲まれない最初の出現が式の終端として認識されます。トークン単位(:::<など)の判定ではありません。これは、rangeフォーマットのフォーマットオプションなどにおいて::の指定に意味があるためです(あるいは、:+記号のような将来のオプション拡張を受け入れることができます)。

namespace v { int x = 2; }

auto example() -> void {
  std::vector<int> v = {10, 20, 30};

  std::println(t"{v::x}");    // [a, 14, 1e]
                              // "{::x}", v としてパースされる
                              
  std::println(t"{(v::x)}");  // 2
                              // "{}", (v::x) としてパースされる
}

プリプロセッサの実行前に字句解析が行われてテンプレートリテラルが展開されることによって、テンプレートリテラル内にはマクロを含めることができます。ただし、マクロの展開はテンプレートリテラルの字句解析とオブジェクト初期化式への展開よりも後であり、マクロの展開結果に含まれる:が式終端として認識されることはありません。

namespace v { int x = 2; }

auto example() -> void {
  #define SCOPED v::x
  std::println(t"{SCOPED}");  // 2([a, 14, 1e]ではなく)
                              // "{}", SCOPED としてパースされる
}

デバッグオプション

このテンプレートリテラルにおいては、単純な拡張によってPythonのようなデバッグフォーマットとの短縮指定({x=})を実現できます。

std::println(t"{x=}, {y=}, {z=:?}");

この実装は、字句解析時に置換フィールド内の式部分が=で終わる場合は=を削除して削除前の式を文字列化したものを置換フィールドの前に追加することで行えます。すなわち、t"Hello {name=}"t"Hello name={name}"として書いたのと同じになります。

ただし、Pythonのこの機能においては出力される文字列はデバッグ形式になりますが、C++のstd::formatではデバッグ形式の一般定義がないことやデバッグオプションが一部の型でしか使用できないなどの制限があるため、デバッグ形式で出力せず、そのためのオプションも追加していません。デバッグ形式をサポートしている型の場合は、フォーマットオプションとして?を指定することでこれを有効化できます。

この提案はこれを含んでいます。

ネストする置換フィールド

フォーマットオプションの中には置換フィールド内で置換フィールドを指定することでオプションの引数として動的な値を取ることができるようになっているものがあります。例えばstd::format("{:>{}}", name, width)name変数のフォーマット時の幅の指定としてwidthを使用します。これはテンプレートリテラルで記述するとt"{name:>{width}}"のようになります。

std::formatにおいて置換フィールド内のフォーマットオプションの構文は実はほぼ自由であるため(例えばchronoのオプションなどのように)、このようなネストは任意に現れる可能性があります。そのオプション構文はstd::formatter:parse()で指定されているため、テンプレートリテラルの字句解析中にオプション構文を認識することはできません。

そのため、テンプレートリテラル内で置換フィールド内にネストしている置換フィールドは、それもまた一つ置換フィールドとしてパースされます。すなわち、置換フィールド内で{が現れると、必ず:or}で終わる式が開始されたものとして扱われます。

仮にtupleのフォーマットオプションを拡張して要素に対応する{}によって各要素のオプションを個別に指定するようにしている場合(The Surprising Complexity of Formatting Ranges で紹介されている)、次のようになります

int main() {
  auto elems = std::tuple(10, 20, 30);

  fmt::print(t"{elems:{x}{}{x}}\n");    // fmt::print("{:{}{}{}}", elems, x, x); と展開される(識別子xが無いのでエラー
  fmt::print(t"{elems:{:x}{}{:x}}\n");  // fmt::print("{:{:x}{}{:x}}", elems);   と展開される、ok
}

テンプレートリテラルオブジェクトの構造

テンプレートリテラル内の置換フィールドに指定された式は、その評価結果がテンプレートリテラルオブジェクトのメンバ変数として保存されます。その型はテンプレートリテラル内の置換フィールドの式Eに対してdecltype((E))になり、順番は置換フィールドの出現順に並びます。decltype((E))によって型を取得することで、不要なコピーを回避する最適な型を得ることができます。

置換フィールド内にネストした置換フィールドの式も同様に保存されるため、メンバの数はテンプレートリテラル内の補完数(num_interpolations())よりも多くなる場合があります。

補完数(num_interpolations())はテンプレートリテラルに含まれる直接の置換フィールドの数であり、ネストしたものを含みません。interpolation()が返すのはそれらの置換フィールド(補完部分)に関する詳細な情報で、次のような集成体型によって表されます。

struct interpolation {
  char const* expression;
  char const* fmt;
  size_t index;
  size_t count;
};

interpolation(i)i番目の補完部分に関してこのような型のオブジェクトを返します。

fmtはテンプレートリテラルオブジェクトの同名のメンバ関数と同じく、対応する置換フィールドから式部分を除いた文字列です。それに対して、expressionは式部分の文字列になります。

indexはテンプレートリテラル内でその補完部分の現れる順番(0-index)であり、countはその補完部分内でネストしている置換フィールドの数です。ネストしている置換フィールドの式の結果もテンプレートリテラルオブジェクトの直接のメンバとして直列化されて保存されているため、これらの情報はネストしている置換フィールドを正しく処理するために必要となります

例えば、"{name=:>{width}}"は次のように展開されます

struct Template {
  static consteval auto fmt() -> char const* { return "name={:>{}}"; }
  static consteval auto num_interpolations() -> size_t { return 1; }
  static consteval auto string(size_t n) -> char const* {
    constexpr char const* data[] = {"name=", ""};
    return data[n];
  }
  static consteval auto interpolation(size_t n) -> std::interpolation {
    consteval std::interpolation data[] = {{
      .expression = "name",  // note that "name" is preserved
      .fmt = "{:>{}}",       // but "width" is not
      .index = 0,
      .count = 2
    }};
    return data[n];
  }

  std::string const& _0;
  int const& _1;
};

string(i)が返すのは、補完部分を文字列の区切りとして分割したときの、i番目の補完部分ではない文字列です。

t"{name=:>{width}}"の場合は次のようになります

  • 文字列リテラル: "name="
  • 補完部分: "{:>{}}"と2つの式name, width
  • 文字列リテラル: ""

string(i)は0-indexでi番目の文字列リテラルを返し、補完部分の文字列は返しません。例えば、string(0)"name="であり、string(1)""を返します。

テンプレートリテラルオブジェクトの利用例

テンプレートリテラルオブジェクトはフォーマット文字列が記述されたリテラルについての情報を提供するため、std::format/print以外のもの(ユーザー定義のもの)でも利用することができます。テンプレートリテラルオブジェクトから提供される情報はstd::format/print以外のユースケースを満たすために必要性が想定されるものについて定義されています。

補完部分の文字列(引数からフォーマットされた部分)を強調表示(緑+太字)する例

template <TemplateString S>
auto highlighted_print(S&& s) -> void {
  constexpr size_t N = s.num_interpolations();

  auto& [...exprs] = s;

  template for (constexpr int I : std::views::indices(N)) {
    fmt::print(s.string(I));

    constexpr auto interp = s.interpolation(I);
    constexpr auto [...J] = std::make_index_sequence<interp.count>();
    fmt::print(fmt::emphasis::bold | fg(fmt::color::green),
               interp.fmt,
               exprs...[interp.index + J]...);
  }

  fmt::print(s.string(N));
}

テンプレートリテラルをJSONにシリアライズする例

template <TemplateString S>
auto into_json(S&& s) -> boost::json::object {
  constexpr size_t N = S::num_interpolations();

  auto& [...exprs] = s;

  boost::json::object o;
  template for (constexpr int I : std::views::indices(N)) {
    constexpr auto interp = s.interpolation(I);
    o[interp.expression] = exprs...[interp.index];
  }
  return o;
}

テンプレートリテラルでprintf()をラップする例

template <TemplateString S>
auto with_printf(S&& s) -> void {
  constexpr char const* fmt_string = [&]() consteval {
    std::string fmt = s.string(0);
    auto nsdms = nonstatic_data_members_of(remove_cvref(^^S), std::meta::access_context::current());

    for (size_t i = 0; i < s.num_interpolations(); ++i) {
      // in theory, this logic would be more interesting
      auto interp = s.interpolation(i);
      auto type = remove_cvref(type_of(nsdms[interp.index]));
      if (type == ^^int) {
        fmt += "%d";
      } else {
        if (interp.fmt == std::string_view("{:?}")) {
          fmt += "\"%s\"";
        } else {
          fmt += "%s";
        }
      }

      fmt += s.string(i + 1);
    }

    return std::meta::define_static_string(fmt);
  }();

  auto adjust = []<class T>(T const& arg){
    if constexpr (std::same_as<T, std::string>) {
      return arg.c_str();
    } else {
      return arg;
    }
  };

  auto& [...exprs] = s;
  constexpr auto [...Is] = std::make_index_sequence<s.num_interpolations()>();
  std::printf(fmt_string, adjust(exprs...[s.interpolation(Is).index])...);
}

これらの例それぞれではテンプレートリテラルオブジェクトの情報の一部だけしか使用していませんが、それぞれで必要とする情報は異なっています。テンプレートリテラルオブジェクトはこのように、ユーザーがそれを自由に使用できるように関連する全ての情報を公開しようとしています。

また、これらの例の関数は全て共通のテンプレートリテラルオブジェクトを用いて呼び出すことができます

auto verb() -> std::string { return "die"; }

auto main() -> int {
  std::string name = "Inigo Montoya";
  int width = 5;

  // テンプレートリテラルオブジェクト
  auto msg = t"Hello, my name is {name:?}. You killed my {relation}. Prepare to {verb():*^{width}}.\n";

  // msgを使用して呼び出せる
  std::print(msg);
  highlighted_print(msg);
  std::println("{}", into_json(msg));
  with_printf(msg);
}

TemplateStringコンセプト

例で使用されていたTemplateStringコンセプトはテンプレートリテラルオブジェクトを識別するために必要なものですが、これをどのように識別すべきかはまだ未定です。

テンプレートリテラル型はユーザーが任意に定義して使用できた方が便利なユースケースがあるため(テンプレートリテラル型の変換を行う場合など)、少なくともユーザーが定義できる必要がありそうだとしています。そのためコンパイラの組み込みの不透明型のような型を判定するようなコンセプトにはならないようです。

この提案は著者の方によってclangをフォークして静的リフレクションを実装しているブランチをさらにフォークして実装されているようです。このことは、P3412と比較しても実装可能性が高いことを示しています。

P3952R0 is_pointer_in_range

ポインタがあるメモリ範囲の内側にあるかどうかを調べるis_pointer_in_range()の提案。

このis_pointer_in_range()のような関数は在野のライブラリで度々再実装されており、WG21のメーリングリストでもたびたび必要性が言及されます。そして、すでに何回か提案が提出されています(P3234R1, P3852R0)。

この一見単純な関数の問題点は、正確な実装が難しいことにあります。特に、未定義動作や偽陽性/偽陰性(FP/FN)を排除しようとすると実装難易度が高くなり、正しい実装がされない傾向にあります。

例えば、あるポインタpが配列の先頭と終端のポインタb, eの間にあることを判定するのにb <= p && p < eと記述するのは一見問題なさそうに見えます。しかし、これはpが実際に[b, e]の範囲外にある時に比較そのものが未定義動作とされます(ポインタの比較は配列内でのみ有効)、

また、std::lessが実装定義の狭義全順序による比較を提供していることを利用して、std::lessによって比較を行う方法も知られています。このことは連想コンテナがポインタをキーとして扱うために必要な要件でもあります。

この場合、p[b, e)の間を指しているかを判定するのに!std::less<T const*>{}(p, b) && std::less<T const*>{}(p, e)という比較が行われます。これは狭義全順序があくまで実装定義のものであるためにFP/FNの可能性を抱えています。例えば、ある配列の外側にあるオブジェクトのアドレスpが、配列の先頭と終端のアドレス[b, e)の間にある場合であっても、それが実装定義の狭義全順序に従っていれば合法であるためです。

これはセグメント化ポインタやセキュリティタグ付きポインタなどのポインタ値のインターリーブによっておこりえます。ポインタの全順序といえば素朴な連続アドレス空間を想定しがちですが、実装においてメモリ領域がそうなっていることとポインタ値の順序がどう定義されるかは別の話です。

ここで例に挙げたような実装でも、現在の多くの環境においては意図通りに動作はします。しかし、それは将来にわたって保証されたものではなく、移植性がありません。

その一方で、このようなポインタの包含判定は主に配列外参照を検出する目的でニーズが高く、それを受けて在野のライブラリでよく実装されています。

そのため、正確な実装を移植性がある状態にするために、is_pointer_in_range()のような関数は標準化に値します。

提案ではこの関数の提案に当たって、次の3つの要件を定義しています

  1. すべてのポインタ値に対してwell-definedであること
  2. FP/FNが発生しないこと
  3. 定数式でも使用可能であること

is_pointer_in_range()はポインタがある範囲内にある場合にtrueを返し、上記の要件を満たす関数です。

namespace std {
  template<typename T> 
  constexpr bool is_pointer_in_range(T const* p, std::span<T const> s) noexcept; 
}

この提案では、std::spanによってあらわされた範囲s内にポインタpが包含されているか、を判定するインターフェースになっています。これはstd::spanが暗黙的にポインタ範囲の有効性を要求していることと、3つのポインタを取るインターフェースではその順序を間違えやすいなどの問題があることによります。ポインタペアで利用したい場合でも、is_pointer_in_range(ptr, {begin, end})とすれば簡単に利用可能です。

実装に関しては、コンパイラの提供する組み込み関数を用いるか、std::lessによる実装が有効な環境ではそれによっても実装可能だとしています。

P3953R0 Rename std::runtime_format

std::runtime_format()std::dynamic_format()にリネームする提案。

C++26のstd::runtime_format()は実行時文字列による実行時のフォーマットの(コンパイル時のフォーマット文字列チェックを実行時に遅延させる)ために用意されたフォーマット文字列をラップする関数です。

一方、C++26に向けてはstd::formatconstexpr化が導入されており、これに伴ってstd::runtime_format()そのものもコンパイル時実行可能になっています。

constexpr auto f(std::string_view fmt, int value) {
  return std::format(std::runtime_format(fmt), value);  // ok
}

こうなると、std::runtime_format()という名前は適切ではない可能性があります、std::runtime_format()はフォーマットがいつ行われるかではなくフォーマット文字列がどのように取得され検証されるかを制御するためのAPIであるため、定数式でも実行可能であることは正当であり(文字列リテラルではないフォーマット文字列を使用可能になるため)、runtimeというワードは誤解を生む可能性があります。

そのため、この提案ではstd::dynamic_formatという名前に変更することを提案しています。これにより

  • フォーマット文字列が動的に提供されること
  • フォーマット文字列がコンパイル時定数ではない事
  • その検証が遅延されること
    • ただし結果的にコンパイル時に行われることもある

を表現できるようになります。

constexpr auto f(std::string_view fmt, int value) {
  return std::format(std::dynamic_format(fmt), value);
}

この提案はその性質上C++26に向けて提出されており、2026年3月の全体会議でC++26への適用が承認されたようです。

P3955R0 It's Scopes All the Way Down

std::executionにおいて、非同期RAIIを導入する提案。

C++の通常の同期処理のコードにはスコープがあり、RAII(コンストラクタとデストラクタ)によってスコープの入り口と出口で任意の処理を挿入することができます。これは特にリソース管理において非常に強力な機能であることは今更説明するまでもありません。

C++26 std::executionsenderによる非同期処理チェーンの記述は、非同期処理専用の関数とその呼び出しフローを定義するものと見ることもできます。その場合のsender/receiverによる非同期関数呼び出しグラフには非同期スコープの概念はあっても非同期RAIIの概念はありません。

そのため、sender/receiverによる非同期関数呼び出しグラフにおいて、ある非同期スコープ内でしか使用しない非同期リソースの生存期間を適切に管理するためには、そのスコープの入り口と出口で手動の非同期構築・解放が必要になります。これは同期コードに直してみれば、RAIIが存在しないCの世界でのコーディングに近いものです。

非同期RAIIは、TCPの二段階初期化(ソケットの初期化が完了した後、接続が確立されるまでは初期化されたとみなされない)やio_uringのIORING_OP_CLOSEの様な非同期クリーンアップ処理を適切に扱うためにも必要になります。

この問題を解消するために以前にP2849R0という提案において非同期オブジェクトという概念を中心にしたsender/receiverチェーンにおける非同期RAIIの設計が提案されていましたが、これにはいくつか問題点がありました。

  • const左辺値の非同期オブジェクトしか扱えない
    • 構築のための引数をムーブできない
    • 非同期オブジェクトをムーブして消費できない
  • 引数の数が固定されていない
    • 非同期コンストラクタはカリー化しておくこともしておかないこともできたため、非同期オブジェクトを表現するために2つのコンセプトが必要になっていた
    • 非同期オブジェクトと言っても二種類あるため、ジェネリックな扱いが困難になっていた
    • 責任の分離が完全ではなく、std::executionの他の部分の設計と一貫しない
  • オブジェクト至上主義
    • 従来の同期的なRAIIにとらわれすぎている
    • スコープガードイディオムのように、RAIIへの要求は必ずしもオブジェクトの構築と破棄を必要としない
    • 非同期オブジェクトとそのストレージが一体になっていることで、スコープ出入りでのコードの実行とオブジェクトの構築と破棄が密接に結びつけられていた
    • senderが戻り値に関して通常の(同期)関数の制約を継承していないように、非同期RAIIもまた同期RAIIの制約に固執すべきではない

この提案は、これらの問題を解消した新しい非同期RAIIのための設計を提案するものです。

この提案の設計の中心となる概念はオブジェクトではなくスコープです。以前の提案とは異なり、スコープはストレージやオブジェクトとは関連付けられてはおらず、スコープに入る時に何らかの処理を実行し、出るときにもまた何らかの処理を実行します。このアクションはsenderで指定されるため非同期処理を実行することができます。

そして、スコープに入るためのsenderenter scope sender: スコープ入り口で実行するアクションを表す)は、その処理が完了(値完了)するとスコープから出るためのsenderexit scope sender: スコープ出口で実行するアクションを表す)を返します(このsenderは高階senderです)。

スコープを確立するためのものがスコープアルゴリズムです。スコープアルゴリズムはenter scope senderとスコープ内でネストさせる処理(sender)を受け取り、enter scope senderを実行してexit scope senderを取得し、ネストさせる処理を実行してから取得したexit scope senderを実行します。これにより、senderチェーンで非同期スコープを導入します。

非同期オブジェクトはスコープとストレージを組み合わせたものです。スコープに入るとそのストレージにオブジェクトが構築され、スコープから出るときにそのストレージのオブジェクトが破棄されます。非同期オブジェクトは次のようなシンプルなインターフェースを持ちます。

struct async_object { 
  using type = /* ... */; 
  execution::enter_scope_sender auto operator()(type*); 
}; 

この呼び出し演算子で受け取っているポインタがストレージのポインタであり、typeは非同期オブジェクトの実際の型です。この呼び出し演算子が返しているものがenter scope senderであり、このsenderがオブジェクトを構築した後でそれに応じてexit scope senderを返すため、これによりムーブによる消費やムーブ構築を行ってもスコープ出口でそれを考慮することができます。

このような設計をコンセプトとそれを使用するもので構成しています。

コンセプト

template<typename Sender>
concept exit_scope_sender = execution::sender<Sender> && ...;

Senderは次の場合にexit_scope_senderのモデルとなる

  • Senderexecution::senderのモデルである
  • Senderはdecay-copyおよびムーブにおいて例外を送出しない
template<typename Sender, typename Env>
concept exit_scope_sender_in = 
  exit_scope_sender<Sender> &&
  execution::sender_in<Sender, Env> && ...;

Sender, Envは次の場合にexit_scope_sender_inのモデルとなる

  • Senderexit_scope_senderのモデルである
  • Senderexecution::sender_in<Env>のモデルである
  • Senderは環境型Envを持つreceiverに例外を送出せずにconnect可能であること
  • execution::completion_signatures_of_t<Sender, Env>execution::completion_signatures<execution::set_value_t()>であること
template<typename Sender>
concept enter_scope_sender = execution::sender<Sender>;
template<typename Sender, typename Env>
concept enter_scope_sender_in = 
  enter_scope_sender<Sender> &&
  execution::sender_in<Sender, Env> && ...;

Sender, Envは次の場合にenter_scope_sender_inのモデルとなる

  • Senderenter_scope_sender_inのモデルである
  • Senderexecution::sender_in<Env>のモデルである
  • execution::completion_signatures_of_t<Sender, Env>には値完了シグネチャがちょうど一つだけ含まれており、その完了シグネチャは単項であり、唯一の引数の型がexecution::exit_sender_in<Env>のモデルとなる
    • このexit_scope_senderはそれを送信したenter_scope_senderのアクションを取り消す
template<typename O>
concept object = ...;

非同期オブジェクトを表すコンセプト。

  • (CV修飾と肩修飾を削除した後で)、オブジェクト型を示すネストされた型エイリアスtypeをもつ
  • type*型で呼び出し可能であり、その呼び出しによってenter_scope_senderのモデルとなる型が返される
template<typename O, typename Env>
concept object_in = 
  object<O> && ...;
  • 単項呼び出しの結果はexecution::enter_scope_sender_in<Env>のモデルとなる型を生成する
  • 呼び出し結果のenter scope senderは、提供されたポインタが示すストレージ内にtype型のオブジェクトを構築する非同期操作である

型エイリアス

template<enter_scope_sender Sender, typename Env>
  requires enter_scope_sender_in<Sender, Env>
using exit_scope_sender_of_t = ...;

enter_scope_senderであるSenderの非同期操作が完了した時に返されるexit_scope_sender型を取得する

template<typename T>
using type_of_object_t = std::remove_cvref_t<T>::type;

非同期オブジェクトの実際のオブジェクト型を取得する。

template<typename T>
using enter_scope_sender_of_object_t = std::invoke_result_t<T, execution::type_of_object_t<T>*>

非同期オブジェクトがその呼び出し結果として返すenter_scope_sender型を取得する。

senderファクトリ

  • enter_scopes

enter_scope_senderを受け取るN項senderファクトリ。結果のsenderオブジェクトは次のような動作をする

  • 接続時: 提供された各子senderを接続し、子operation_stateを構築する
  • operation_state開始時: 各子operation_stateを開始する
  • 各子操作の完了時
    • 値完了の場合: 結果のexit_scope_senderを保存する
    • それ以外の場合: 最初の非値完了の場合: この完了を保存し、他の全ての子操作に停止要求を行う
  • すべての子操作の完了時: 最後に完了した子操作に対して完了時処理を行った後
    • 非値完了で完了した子操作が無い場合
      • execution::set_valueで完了し、保存されているすべてのexit_scope_senderに対してexecution::when_allの結果を送信する
    • それ以外の場合: 保存されている非値完了を送信して終了

呼び出しシグネチャ

template<enter_scope_sender... Senders>
auto enter_scopes(Senders&&... s) -> enter_scope_sender auto;
  • nest_scopes

enter_scope_senderを受け取るN項senderファクトリ。N個のenter_scope_senderを一つのenter_scope_senderに合成し、N個のenter_scope_senderが表すスコープに順番に入る。結果のsenderオブジェクトは次のような動作をする

  • 接続時: 提供された各子senderを接続し、子operation_stateを構築する
  • operation_state開始時: 最初の子operation_stateを開始する
  • 各子操作の完了時
    • enter_scope_senderから作成された操作の場合
      • 値完了の場合: 結果のexit_scope_senderを保存し、次の子operation_stateを開始する
      • それ以外の場合: この完了を保存して、保存されているすべてのexit_scope_senderを接続し、最後のexit_scope_senderから開始する
    • exit_scope_senderから作成された操作の場合
      • 一つ前のexit_scope_senderを開始する(逆順で実行していく)
  • すべての子操作の完了時:
    • すべてのenter_scope_senderの処理が失敗しなかった場合: 全てのexit_scope_senderを逆順で開始する
      • exec::sequenceと同等のexit_scope_senderで完了する
    • それ以外の場合: 保存されている非値完了で完了する

呼び出しシグネチャ

template<enter_scope_sender... Senders>
auto nest_scopes(Senders&&... s) -> enter_scope_sender auto;
  • within

enter_scope_sender1つとsender1つ(他のsender)を受け取るsenderファクトリ。enter_scope_senderのスコープで他のsenderの非同期処理を実行するsenderを構成する。

結果のsenderオブジェクトは次のような動作をする

  • 接続時: enter_scope_senderを接続したoperation_stateに、他のsenderをdecayコピーする
  • operation_state開始時: 子operation_stateを開始する
  • enter_scope_senderの完了時
    • 非値完了の場合: その完了で全体の操作を直ちに完了する
    • 値完了の場合: 結果のexit_scope_senderをdecayコピーし、必要に応じて他のsenderをムーブし、他のsenderを開始する
  • 他のsenderの完了時: 完了を保存して、必要に応じて他のsenderをムーブし、exit_scope_senderを接続し、そのenter_scope_senderを開始する
    • exit_scope_senderの処理が完了した場合: 保存済みの完了情報を送信

呼び出しシグネチャ

template<enter_scope_sender Sender, sender OtherSender>
auto within(Sender&& s1, OtherSender&& s2) -> sender auto;
  • lifetime

最初の引数が呼び出し可能オブジェクト、末尾引数が非同期オブジェクトであるN項senderファクトリ。渡された非同期オブジェクトの生存期間の管理と、それらのスコープ内で実行される非同期操作(呼び出し可能オブジェクトが返すsenderによる)を実行するsenderを返す。

各非同期オブジェクトの::typeTs...とすると、呼び出し可能オブジェクトはTs&...で呼び出し可能である必要がある。結果のsenderオブジェクトは次のような動作をする

  • 接続時
    1. 提供されたすべての非同期オブジェクトを、operation_state内の適切なストレージへのポインタで呼び出す
      • 非同期コンストラクタの呼び出しを行い非同期オブジェクトの指定する型のオブジェクトを構築する、enter_scope_senderを返す
    2. 1で生成されたenter_scope_senderをすべてexecution::enter_scopesに渡す
      • 提供された非同期オブジェクトの並列構築とそれによるスコープを作成するenter_scope_senderを返す
    3. 接続時及び起動時に次のような動作をするsenderを構成する
      1. 前述のストレージ内の各オブジェクトを参照する参照パックを用いて、呼び出し可能オブジェクトを呼び出す
      2. 生成された`sender`を接続
      3. 生成された`operation_state`を開始
      
    4. 2と3で構成されたsenderをそれぞれwithinに渡す
      • 非同期オブジェクトのスコープ内(2)で非同期操作(3)を実行するsenderを返す
        • まず2のsenderを開始することで非同期オブジェクトの構築が行われ
        • その後3のsenderが実行される
          • 構築済みの非同期オブジェクト(の実体への参照)を用いて呼び出し可能オブジェクトからsenderを取得し
          • そのsenderを実行する
    5. 4で生成されたsenderを接続する
  • operation_state開始時: 子operation_stateを開始

この操作のconnectに渡されるreceiverはステップ5でwithinsenderの接続時にそのまま使用される。

呼び出しシグネチャ

template<typename F, object... Objs>
  requires std::invocable<F, type_of_object_t<Objs>&...> &&
           requires(F& f, type_of_object_t<Objs>&... objs) {
             {f(objs...)} -> sender;
           }
auto lifetime(F&& f, Objs&&... objs) -> sender auto;

クラス

  • sync_object

通常のコンストラクタ/デストラクタを持つ型をlifetimeで管理できるように変換する。objectおよびobject_inコンセプトの表現するものを最小限に実装した型。

template<typename T, typename... Args> 
  requires 
    is_constructible_v<T, Args...> && 
    is_destructible_v<T> 
struct sync_object {
  using type = T; 
 
  template<typename... Ts> 
    requires (is_constructible_v<Args, Ts> && ...) 
  constexpr explicit sync_object(Ts&&... ts) noexcept( (is_nothrow_constructible_v<Args, Ts> && ...) )
    : args_((Ts&&)ts...) 
  {} 
 
  template<typename Self> 
  constexpr execution::enter_scope_sender auto operator()( 
    this Self&& self, 
    type* storage) 
      noexcept( 
        is_nothrow_constructible_v< 
          tuple<Args...>, 
          decltype(std::forward_like<Self>(declval<tuple<Args...>&>()))
        >
      )
  {
    return 
      execution::just(std::forward<Self>(self).args_) | 
      execution::then([storage](tuple<Args...>&& tuple) noexcept(is_nothrow_constructible_v<T,Args...>) 
      { 
        const auto ptr = new(storage) T(make_from_tuple<T>(std::move(tuple)));

        return 
          execution::just() | 
          //  It's important we capture ptr not storage because storage 
          //  just points to storage whereas ptr actually points to an 
          //  object 
          execution::then([ptr]() noexcept { 
            ptr->~T(); 
          }); 
      }); 
  }

private: 
  tuple<Args...> args_; 
};

execution::lifetime(..., execution::sync_object<...>(...))は、lifetimeの最初の引数の呼び出し可能オブジェクトによって生成されるsenderの非同期操作のスコープに変数をアタッチする。

提案文書より、非同期ミューテックスの例。

この提案無しでの単純な実装

struct async_mutex { 
  template<std::execution::sender Sender> 
  std::execution::sender auto with(Sender&& sender); 
}; 

この実装は.with()の引数のsenderの処理の前後でミューテックスのロック取得と解放を行おうとするものです。しかし、.with()が引数無しではないことにより、このAPIでは複数のミューテックスを並列に扱うことは困難です。

この提案を利用した単純な実装は次のようになります

struct async_mutex { 
  std::execution::enter_scope_sender auto acquire(); 
}; 

これは次のように使用できます

async_mutex m;

auto critical_section = 
  std::execution::just() | 
  std::execution::then([&]() { 
    std::cout << "Holding the async mutex" << std::endl; 
  }); 

auto snd = std::execution::within(m.acquire(), std::move(critical_section)); 

最初の実装とは異なり、複数のミューテックスを同時に扱うこともできます

async_mutex a, b, c;

auto critical_section = 
  std::execution::just() | 
  std::execution::then([&]() { 
    std::cout << "Holding all async mutexes" << std::endl; 
  }); 

auto snd = std::execution::within( 
  std::execution::enter_scopes(a.acquire(), b.acquire(), c.acquire()), 
  std::move(critical_section)); 

しかし、これはasync_mutexオブジェクトが安全に存在できる同期スコープの存在を必要としています。つまり、a, b, cの生存期間内でsndの接続~完了が完了する必要があります。

この提案の機能抜きでこの問題を解決しようとすると、execution::just(...) | execution::let_value(...)を使用することができます。

auto snd = 
  std::execution::just(async_mutex{}, async_mutex{}, async_mutex{}) | 
  std::execution::let_value([]( 
    async_mutex& a, async_mutex& b, async_mutex& c) 
  { 
    auto critical_section = 
      std::execution::just() | 
      std::execution::then([&]() { 
        std::cout << "Holding all async mutexes" << std::endl; 
      });

    return std::execution::within( 
      std::execution::enter_scopes(a.acquire(), b.acquire(), c.acquire()), 
      std::move(critical_section)); 
  }); 

しかしこれは、async_mutex型に対して次の操作が可能である事を暗黙的に要求しています

  • execution::justsender内に格納できる
  • execution::justexecution::let_valueを合成できる
  • それに対して接続できる
  • execution::justoperation_stateからexecution::let_valueoperation_stateにミューテックスを伝播できる。

これには何度かのムーブを伴いますが、通常ミューテックスはムーブ不可能です。

この提案のsync_objectによってこの問題を解決できます

auto snd = std::execution::lifetime( 
  [](async_mutex& a, async_mutex& b, async_mutex& c) { 
    auto critical_section = 
      std::execution::just() | 
      std::execution::then([&]() { 
        std::cout << "Holding all async mutexes" << std::endl; 
      }); 
    return std::execution::within( 
      std::execution::enter_scopes(a.acquire(), b.acquire(), c.acquire()), 
      std::move(critical_section)); 
  }, 
  std::execution::sync_object<async_mutex>{}, 
  std::execution::sync_object<async_mutex>{}, 
  std::execution::sync_object<async_mutex>{}
);

execution::sync_object<async_mutex>{}async_mutexを構築せず、その構築のための引数無し非同期コンストラクタを提供するだけのラッパ型です。この非同期コンストラクタはlifetimesenderが接続されそのoperation_stateが開始されるまで実行されません。

lifetimeはそのoperation_state内のストレージに非同期オブジェクト(が指定する型のオブジェクト)を非同期的に構築・破棄するため、ここでのasync_mutexオブジェクトは一切のムーブを受けず、ムーブ可能であることは要求されません。

提案文書にはio_uringを扱う例などが掲載されています。

P3959R0 Let layout_stride::mapping with zero extent(s) accept zero strides

layout_stride::mappingの変換コンストラクタの事前条件を緩和し、エクステントがゼロであることを許容するようにする提案。

layout_stride::mappingstd::mdspanのレイアウトクラスのうちストライドによるレイアウトを表現するクラスであるlayout_strideのマッピング(指定されたインデックスから配列上インデックスへの変換)を行うクラスです。

mdspanやストライドレイアウトに関しては別の記事などを参照してください

layout_strideはC++26時点で定義されている標準の他レイアウトから情報損失無しで変換することができるように設計されています。その変換を行う変換コンストラクタの事前条件では、すべてのエクステント(次元)のサイズが正であること(0より大きい)を要求しています。

この要件によって、layout_strideにおいてはエクステントの一部が0の場合でも対応するストライドを0ではなく1にする必要があります。例えばエクステントが(3, 5, 0, 11)の場合、ストライドは(1, 3, 1, 105)のようにする必要があります。

この提案は、この要件を削除してlayout_stride::mappingにおいて、あるエクステントが0である場合にそれに対応するストライドを0にできるようにする提案です。

この提案の利点・モチベーションは次の2つです

  1. 空のレイアウトをlayout_strideへ変換できるようにする
  2. Pythonとmdspanの相互運用性向上

1について、layout_strideは現在標準に存在する他のレイアウトからほとんど任意に変換することができるため、レイアウトクラスのある種の型消去をサポートしています。このような利用法は、mdspanを引数型に取る時などにABIの安定化を図る際に有効となる可能性があります。

しかし、layout_left/layout_rightがストライド0を生成する可能性があるため、現在のlayout_strideはこの用途を完全にサポートできていません。

例えば、1つ以上のエクステントが0のlayout_left/layout_rightを作成すると、1つ以上のストライドが0のレイアウトマッピングが生成される場合があります(これは実装の選択次第ではあります)

#include <cassert>
#include <mdspan>
#include <print>

template<class Layout>
using mdspan_2d = std::mdspan<float, std::dims<2>, Layout>;

int main() {
  mdspan_2d<std::layout_right> mr(nullptr, 1, 0);
  std::print("{} {}\n", mr.stride(0), mr.stride(1));
  assert(mr.stride(0) == 0);  // ストライドが0になる(実装次第
  assert(mr.stride(1) == 1);

  mdspan_2d<std::layout_left> ml(nullptr, 0, 1);
  std::print("{} {}\n", ml.stride(0), ml.stride(1));
  assert(ml.stride(0) == 1);
  assert(ml.stride(1) == 0);  // ストライドが0になる(実装次第


  // この提案の目指すところ、現在は変換コンストラクタの事前条件違反
  mdspan_2d<std::layout_stride> mrs(mr);
  assert(mrs.stride(0) == 0u);
  assert(mrs.stride(1) == 1u);

  mdspan_2d<std::layout_stride> mls(ml);
  assert(mls.stride(0) == 1u);
  assert(mls.stride(1) == 0u);
}

少なくとも、kokkosのリファレンス実装とlibc++の実装ではこれが起こるようです。そして、現在どちらの実装でもこの事前条件をチェックしていないため、layout_strideへの変換は期待通りに動作するようです。

2について、mdspanのカバーする多次元配列の計算処理は近年Pythonが利用されることが増えたことで、PythonをユーザーインターフェースとしてC/C++の処理を呼び出すという使われ方が増加しており、このためPythonが定義する多次元配列のレイアウトをC++側でも受け入れ可能にすることの重要性が増加しています。

このため、Pythonにおいては多次元配列データのための共通バイナリインターフェースが定義されるようになっています。例えば、Buffer Protocol・numpyのndarray Protocol・DLPack format・CUDA Array interfaceなどです。

これらのレイアウトはlayout_strideが表現可能なレイアウトよりもより広いレイアウトを表現可能になっています。

  1. layout_strideは要素ストライドなのに対して、DLPackを除いてすべてバイトストライドを使用している
  2. 4つのレイアウトにおいて、0または負のストライドおよび、一意ではないレイアウトを許容している
    • layout_strideは常にそのマッピングが一意(単射)である必要がある
  3. 4つのレイアウトにおいて、要素0の配列(エクステントの積が0)についてストライドの値に要件が無い
    • layout_strideは0を許容しない

この提案ではこのうち3のみが解決されます。残りの2つはlayout_strideの設計選択による意図的な制限であるため変更しません。

Pythonの多次元配列フォーマットでは、エクステントが0の場合対応するストライドを0にするという慣習があるようで、layout_strideのこの制約はPython開発者にとって直観的ではない可能性があります。

layout_strideのこの制約は、BLAS/LAPACKなどのライブラリが通常0ストライドを許容しないことから来ているようです。そのため、この制限を緩和した場合<linalg>の実装やmdspanによるBLAS/LAPACKライブラリラッパでは0ストライドをもつlayout_strideが入力されることを考慮する必要があります。

一方、この要件を緩和した後でもlayout_strideの設計要件が壊れることはありません。提案ではその簡単な証明がなされています(layout_strideによるマッピングは全単射である必要があり、0ストライドを許容しても空集合から空集合の写像が全単射であることからこれが維持される)。

P3960R0 Define copy-constructibility-from-bytes

バイト列を単にコピーするだけでオブジェクトを有効にコピーすることができるクラス型を判別する型特性の提案。

C++26では並行アルゴリズムのRange版が追加されたことで、Rangeアダプタを適用した結果のviewを用いて並行アルゴリズムを利用しやすくなっています。並行アルゴリズムはExecutionPolicyで指定した実行方法によって、ホストのCPU以外のHW(アクセラレータ)で実行される可能性があります。最も典型的な例はGPUです。

GPUの様なアクセラレータはほとんどの場合、ホストのCPUとは独立したメモリを持っています。そして、アクセラレータとホストはお互いのメモリ領域に相互的にアクセスすることができないか、できても著しく速度が遅い場合がほとんどです。

このようなアクセラレータ上で並行アルゴリズムを実行しようとする場合、実行ステップの最初にまず並行アルゴリズムの引数をすべて(範囲の要素列そのものやイテレータ、viewオブジェクト、追加の値引数やcallable引数など)アクセラレータのメモリにコピーする必要があります。このコピーはmemcpyと同様の動作をする実装定義の関数を用いて行われ、これはバイト単位かつビットを保ったコピーになります。

このようなコピーが不可能である場合、並行アルゴリズムの起動時にその引数のシリアライズとデシリアライズを行ってアクセラレータのメモリ上で引数列を再構成する必要があります。C++26のリフレクションが無い場合このようなことは標準的な方法では行えず、またmemcpyと比較するとはるかに大きなオーバーヘッドがあります。

結果として、アクセラレータ向けの並行Rangeアルゴリズム実装においては、引数のバイトコピーが可能ではない場合にホストのCPUによる実行にフォールバックする実装を取ることになります。範囲の要素がすでにGPUメモリにある場合、これは逆方向のオーバーヘッドとなります。

特に、transform_viewzip_transform_viewなどのview型において、受け取るラムダ式がキャプチャをしているかどうかによってそのview型のトリビアルコピー可能性が変化してしまうことが問題となっています(この点については、少し下のP3963R0で詳しく解説しています)。

並行アルゴリズムの起動を試みる前にそのすべての引数が参照するデータそのものはアクセラレータのメモリにコピーされているものとすると、並行アルゴリズムの引数列(イテレータ/view、関数オブジェクト)は実データに対してその処理対象を表すメタデータとなります。このメタデータに対して、アクセラレータ上で並行アルゴリズムを動作させる場合は次のようなことが行われます

  1. アルゴリズムの引数列を格納するためのメモリ領域を確保
  2. 引数をバイト単位でアクセラレータメモリにコピー
  3. 並行アルゴリズムのカーネルメイン関数をアクセラレータ上で実行
    • この関数内では、ホスト上で保持されていた時と同じ値を持ち、かつ通常通りに生存期間が開始された状態で引数を取得する

ステップ3において、アクセラレータ上でコピーされた引数列を取得するための要件は次の2つです

  1. ホスト上で保持していたものと同じ値を持つ
  2. 生存期間が開始されている

1の要件に最も合致するのはトリビアルコピー可能性です。しかし、2の要件を加味すると少し考慮すべきことが増えます。

トリビアルコピー可能性は2つのオブジェクトの間でバイト単位のコピーを行えることは保証するものの、コピー先のオブジェクトの生存期間が開始されることは保証しません。そのためには、あらかじめオブジェクトの生存期間を開始しておくか、コピーしてから生存期間を開始する必要があります。

あらかじめオブジェクトの生存期間を開始しておく場合はデフォルトコンストラクタを呼び出す必要がありますが、そのためにはさらにデフォルト構築可能性を要求することになります(さらにはデフォルトコンストラクタが何もしない事を要求するためにはトリビアルデフォルト構築可能性を要求する必要がある)。

C++20からはこのような場合に最適なimplicit-lifetime typesというカテゴリの型があり、implicit-lifetime typeはストレージが確保された後特定の操作(標準関数によるメモリ確保、memcpystd::start_lifetime_asなど)の後に生存期間が自動的に(コンストラクタ呼び出しを必要とせずに)開始されます。

transform_viewzip_transform_viewなどのview型がしばしばトリビアルコピー可能ではなくなるのは、単純にはCallableの保持のために使用しているmovable-boxのコピー代入演算子の削除基準にあります。保持対象のコピー代入演算子が削除されている場合、movable-boxはトリビアルではないコピー代入演算子を定義します。この非トリビアルコピー代入演算子によって、movable-boxはトリビアルコピー可能ではなくなります。ただしこのことは、movable-boxがより多くの場合にcopyable性を確立するために重要な性質であり、view型の扱いやすさに直結しています。

この提案では、view型のシリアライズ/デシリアライズの様な複雑かつ課題の多いソリューションもmovable-boxの現在の有用な性質を破壊する変更も行わず、かつシンプルな解決策として代入演算子の存在に左右されずにバイトコピー可能性を保証したimplicit-lifetime typesに含まれる、新しい型のカテゴリを追加することを提案しています。

提案ではこのカテゴリの型のことをcopy-constructible-from-bytesと呼んでいます(trivial copy-constructibleが最適ですが、それはすでに使われているためこの名前になった)。あるクラス型が次の条件を満たす場合、その型はcopy-constructible-from-bytesクラス型となります

  1. 少なくとも1つの資格のあるコピーコンストラクタを持つ
  2. それらの資格のあるコピーコンストラクタはすべてトリビアルである
  3. 資格のあるムーブコンストラクタが存在する場合、それらはすべてトリビアルである
  4. トリビアルで削除されていないデストラクタを持つ

そして、スカラ型、copy-constructible-from-bytesクラス型、あるいはそれらの配列型(またはこれらのCV修飾型)をcopy-constructible-from-bytes typesと定義します。

この条件はトリビアルコピー可能性から代入演算子の要件を無くして条件を緩和したものであり、copy-constructible-from-bytes typesな型はトリビアルコピー可能な型でもあります(逆はかならずしも成り立ちません)。

このcopy-constructible-from-bytes typesは自然にimplicit-lifetime typesになります(implicit-lifetime class型の非集成体の場合の要件を常に満たすため)。

そして、copy-constructible-from-bytes typesはバイト列からコピー構築可能であることを保証します。これにより、次のように既存のオブジェクトからコピーされた値表現のみから暗黙的にオブジェクトを作成できるようになります

void* dst_mem = std::malloc(sizeof(T)); // ストレージ確保
std::memcpy(dst_mem, &src, sizeof(T));  // 値表現のバイトコピー
T* dst_ptr = std::start_lifetime_as<T>(dst_mem);  // オブジェクト作成(コンストラクタを呼び出していない

この特性を持つ型の検出のためにstd::meta::is_copy_constructible_from_bytes()メタ関数とstd::is_copy_constructible_from_bytes型特性を追加することも提案しています。

最後に、std::start_lifetime_as()およびstd::bit_cast()の動作をcopy-constructible-from-bytes typesに対して拡張します。これらの関数の保証は、現在トリビアルコピー可能な型についてのみ制約されているため、これをcopy-constructible-from-bytes typesに緩和します。

copy-constructible-from-bytesクラス型においては、資格のあるコピーコンストラクタがトリビアルであることが要求されており、これが満たされるためにはそのメンバの(その資格のあるコピーコンストラクタから参照される)コピーコンストラクタが全てトリビアルである必要があります。これにより、copy-constructible-from-bytes typesだけをメンバに持つ型はcopy-constructible-from-bytes typesになります。

P3961R0 Less double indirection in function_ref (RU-220)

std::function_refが二重化するのを抑制する提案。

std::function_refは関数引数などで使用して、Callableの型消去を可能にする軽量なCallableラッパ型です。これは名前にあるように、渡されたCallableを所有せずに参照することで、std::function等に比べて軽量かつオーバーヘッドの小さい型消去を可能にします。

現在の仕様だと、std::function_refstd::function_refを渡すとstd::function_refのアンラップを行わずに参照の参照のような状態で保持してしまう問題があります。std::function_refは再代入可能な参照をモデルとしている点で言語参照とは異なりますが、言語参照と同様に参照は参照を参照するべきではありません。

LWG Issue 4264においてその解決のベースラインが提示されており、この提案は、その変更を調整してnoexceptの互換性を考慮した変換を行うようにするものです。

まず、LWG Issue 4264の変更においてはstd::function_refstd::function_refを用いて初期化する場合(呼び出しシグネチャの互換性はあるとして)、初期化元のstd::function_refをアンラップして(そのメンバを直接的にコピーして)構築するかどうかは未規定としています。これはstd::function_refの二重化を防止することは意図しているもののこの領域において将来別の種類の最適化を導入する余地を残すためのものです。未規定としているため、std::function_refの利用者はstd::function_refが二重(あるいはそれ以上)になっている挙動に依存することは利用者の責任となります(実装や将来のバージョンで変更されうる)。

void f1(std::string);
void f2(std::string);

std::function_ref<void(std::string)> r1(&f1);
std::function_ref<void(std::string&&)> r2(r1);

r2("");   // f1が呼ばれる
r1 = &f2;
r2("");   // f1とf2のどちらが呼ばれるかは未規定

この提案ではそれに加えて、noexceptの互換性がある場合のstd::function_refからの構築・代入を許可するようにしています。

void foo(int);
void bar(int) noexcept;

// 素の関数ポインタにおける変換
void core_language() {
  auto p1 = foo;
  auto p2 = bar;

  p1 = p2;  // ok
  p1(3);    // barが呼ばれる
  p2 = p1;  // error
}

// function_refにおける変換
void library() {
  std::function_ref r1 = foo;
  std::function_ref r2 = bar;

  r1 = r2;  // ok(この提案
  r1(3);    // barが呼ばれる
  r2 = r1;  // error
}

この提案の変更により、このように素の関数ポインタの変換動作と一致するようになります。また、おそらくこのケースにおいてはstd::function_refの二重化防止は未規定ではなく指定された動作になります。

関数ポインタ/std::function_refの関数シグネチャにnoexceptが指定されている場合noexceptな関数ポインタ/Callableしか代入できなくなり、指定されていない場合はnoexceptあり無しどちらも代入可能になります。

具体的にはまず、次のような判定を行うメタ関数を導入します

template<class F>
static constexpr bool is-convertible-from-specialization = see below;

このis-convertible-from-specialization<F>

  • Fが任意のcv2noexc2によるfunction_ref<R(Args...) cv2 noexcept(noex2)>の特殊化である場合
    • is_convertible_v<R(&)(Args...) noexcept(noex2), R(&)(Args...) noexcept(noex)> && is_convertible_v<int cv&, int cv2&>
  • そうではない場合
    • false

となるbool値です。cv2noexc2はコンストラクタ/代入演算子の引数に渡されているstd::function_refのもので、cvnoexは構築/代入しようとしているstd::function_refのものです。

このis-convertible-from-specialization<F>trueとなる場合というのはFstd::function_refの呼び出しシグネチャのnoexcept指定(ありにはありのみ変換可能、なし/noexcept(false)はどちらからも変換可能)とCV修飾(constありはどちらも、なしはなしからのみ変換可能。Vも同様)に互換性がある場合です。

これを任意のF&&から構築するコンストラクタの構築時に使用して、falseならばアンラップ等せずに通常通りに引数の参照を保持し、trueとなる場合はアンラップ動作を指定します。

ただし、is-convertible-from-specialization<F>falseとなる場合でもremove_cvref_t<F>std::function_refの特殊化である場合(シグネチャは問わない)は、構築されたstd::function_refが引数のstd::function_refをアンラップしているかどうかは未規定としています(書き方的にはもしかしたら、is-convertible-from-specialization<F>の結果によらず未規定かもしれません。読み取れず...)。

P3962R0 Implementation reality of WG21 standardization

C++処理系実装者による標準仕様とその実装においての課題と、改善策の提案。

この文書は、2025年11月に行われたKona会議において、C++の処理系(コンパイラ、ライブラリ、その他?)の実装を担当しているメンバーを集めて行われた非公開の議論においての要点をまとめ、それら課題への対応についての提案をするものです。

主なトピックは次のものです

  1. コストとリソース、および経済的現実
    • ここの提案において、実装・保守・テスト・サポートにかかるコストが考慮されていない
    • コンパイラおよびライブラリ実装者のリソースは著しく制約されている
    • 新機能が追加されると、必然的にバグ修正・適合性の向上・パフォーマンスのチューニング・ライブラリの機能強化などの他の作業のリソースが削減される
  2. 実装者の声と実装複雑性の理解
    • 特に設計の初期段階において、実装者の役割と専門知識がプロセスに十分に反映されていない
    • 実装に関するフィードバックは後から遅れて導入され、しばしば敵対的なものあるいは設計の障害として扱われてしまう
    • 簡単に実装できるとする提案でも、実際のコードベースやツールチェーン全体の中での相互作用はそれほど簡単ではない場合がある
    • 実装経験は、実運用のためにコードベースへマージし、保守、テスト、展開をするために必要な労力を反映していない
  3. コア言語機能の設計(EWG)作業と標準文言策定(CWG)作業への参加における課題
    • 実装者はEWG/CWGの両方に継続的に関与し続けることが非常に困難
    • 提案の量や平行するグループの数、議論のペースは限られた実装者のリソースに大きな負担をかけている
    • 実装者は初期設計に関する議論に有意義に貢献できなくなっており、実装上の懸念事項が設計が進んだ後に表面化することがある
    • 逆に、設計議論は後で文言策定時に対処する必要がある意味論的制約に関する適切な理解や指摘がないまま進行する可能性があり、手戻りやグループ間の意識のずれのリスクが生じる
  4. 委員会の優先事項、ユーザーニーズ、および経営陣の期待との整合性
    • 委員会が標準化を目指す機能とユーザーや組織が採用したいと考えている機能との間に大きなギャップが生じている
    • 最終的に何が実装され、展開されるかは経営陣の決定に大きく左右される
    • 委員会が採用した機能であっても組織の優先事項やユーザーニーズに合致しなければ実装されない可能性がある
  5. 新機能とバグ修正、そして移植性
    • 新機能の開発は、既存の欠陥や未解決のコア言語の問題への対応に費やす時間を奪うだけでなく、標準規格に新たな欠陥を追加することもある
    • 未解決の問題は実装の差異に繋がり、移植性に影響を与える
    • 新機能がそれを実装し統合・調整される速度よりも早く積み重なると、不完全または矛盾した基盤の上に機能が積み重なり、移植性のリスクが高まる

これに対して今後の行動についての推奨事項を提示しています

  1. コストとトレードオフの可視化
    • 委員会は、コスト・リソース・技術的負債の影響をより明確にするための仕組みの検討、あるいはリリースごとの総コスト予算の策定に着手してほしい
    • 標準規格に機能を追加することが最終目標ではないことを理解し、利用可能なリソースについてより現実的に考える必要がある
  2. 実装者からのフィードバックを早期に取り入れる
    • 委員会は、実装者からのフィードバックをプロセスの早い段階で収集する方法を検討してほしい
    • その方法として、advisory implementer study group を設置することが考えられる
    • このようなグループは提出される提案に対して次の例のような情報を提供できる
      • 仕様通りにこの機能は実現可能か?
      • 特定の実装におけるこの機能のコストはいくらか?
      • 特定の顧客層や経営陣からこの機能に対する要望はあるか?
      • 参照実装に関するフィードバック
    • このようなSGを設置するもう一つの利点は、実装者間の連携が促進されること
  3. ペース調整とリリースの重点化
    • 委員会は、標準規格への機能追加ペースを緩める方法を検討してほしい
    • これにより、実装者が追いつく時間を確保し、実装品質の向上を図ることができる
    • 標準化サイクルを延期するか、機能追加に重点を置いたリリースと統合に重点を置いたリリースを交互に行うことを検討してほしい
    • また、新機能開発と並行して、不具合の解消・規格への適合性・移植性に関する作業を明確に優先すべき
  4. スケジュールと参加の重複を減らす
    • 委員会は、可能な限りEWGとCWGを並行して運営しないでほしい
    • これにより、詳細な知識を持つメンバーが設計の初期段階から関与でき、また通常は設計にしか関与しないメンバーが文言策定に関与する時間を費やすことができるようになる
    • このような体制は委員会内で特定の機能に対する焦点の一貫性を高めることにもつながる

これは明確な提案というよりは、委員会の運営体制への提言です。

P3963R0 Assignable lambdas with capture

キャプチャしているラムダ式のクロージャオブジェクトを、代入可能にする提案。

C++20より、キャプチャしていないラムダ式のクロージャ型にはデフォルトコンストラクタとコピー/ムーブコンストラクタが定義されます。一方、キャプチャをしているラムダ式は逆にデフォルトコンストラクタとコピー代入演算子は(それによってムーブ代入演算子も)delete定義されます。

このことはRangeライブラリのview型(特にラムダ式を頻繁に受け取ることになるviews::transform)において影響があり、view型のトリビアルコピー可能性を阻害する場合があります。

std::vector<int> v = {1, 2, 3, 4, 5};

// キャプチャしていないラムダ式のクロージャ型はトリビアルコピー可能
auto captureless = [](auto x) { return x + 1; };
static_assert(std::is_trivially_copyable_v<decltype(captureless)>); // ok

// キャプチャしているラムダ式のクロージャ型もトリビアルコピー可能
auto with_capture = [y = 1](auto x) { return x + y; };
static_assert(std::is_trivially_copyable_v<decltype(with_capture)>); // ok

// この transform_view 型はトリビアルコピー可能
auto view1 = v | std::views::transform(captureless);
static_assert(std::is_trivially_copyable_v<decltype(view1)>); // ok

// この transform_view型はトリビアルコピー可能ではない
auto view2 = v | std::views::transform(with_capture);
static_assert(std::is_trivially_copyable_v<decltype(view2)>); // ng 👈

トリビアルコピー可能性においては、コピーコンストラクタかコピー代入演算子(あるいはムーブのそれら)のうち1つでも有効(使用可能かつユーザー定義されていない)であればよいため、代入演算子が削除されていてもコピーコンストラクタが利用可能であれば(キャプチャしているラムダ式の場合はコピー/ムーブコンストラクタはdefault宣言されている)トリビアルコピー可能となります(そのほかの条件は満たしているものとして)。

そのためwith_captureの型はトリビアルコピー可能となります。

しかし、それを保持するtransform_viewがトリビアルコピー可能ではないのは、transform_viewが渡されたCallableをmovable-boxという型で保持しているためで、movable-boxは保持する型がcopyableのモデルではない場合に非トリビアルなコピー/ムーブ代入演算子を持つためです。

この場合、with_captureの型はトリビアルコピー可能でありながらコピー代入演算子はdeleteされているため、transform_viewmovable-boxはトリビアルコピー可能にならず、それを直接保持してるtransform_view型もトリビアルコピー可能ではなくなっています

通常のRangeアダプタの使用ではこのことは問題になりませんが、並行Rangeアルゴリズムの場合に影響があります。並行アルゴリズムの場合、実行方法によっては渡されたCallableオブジェクトを実行場所(各スレッドなど)へコピーする必要があり、その際にトリビアルコピー可能ではないとmemcpy相当の単純なコピーができず、コピーする分のコピーコンストラクタ呼び出しが伴ってしまいます。

この提案はこの問題の対策のために、キャプチャしているラムダ式のクロージャ型は、手描きの関数オブジェクトと同様の規則によって代入演算子を定義するようにすることを提案しています。

すなわち

  • コピー代入演算子: すべてのキャプチャ(コピーキャプチャに対応するメンバ変数)がコピー代入可能である場合、クロージャ型のコピー代入演算子はdefault定義されるべき
    • そうでない場合は削除されるべき
  • ムーブ代入演算子: すべてのキャプチャ(コピーキャプチャに対応するメンバ変数)がムーブ代入可能である場合、クロージャ型のムーブ代入演算子はdefault定義されるべき
    • そうでない場合は削除されるべき

とすることを提案しています。

このようにすると先ほどのmovable-boxの場合でも、コピー代入演算子がdefault定義されることによってクロージャ型はcopyableを満たし、movable-boxもコピー代入演算子をdefault定義することができるようになります。

// この transform_cview型もトリビアルコピー可能になる
auto view = v | std::views::transform([y = 1](auto x) { return x + y; });
static_assert(std::is_trivially_copyable_v<decltype(view)>); // ok(この提案による

提案文書より、コピーキャプチャの例

int y = 1;
auto f = [y](int x) { return x + y; };
auto g = f;  // 現在もok
g = f;       // 現在はng、この提案ではok

static_assert(std::is_copy_assignable_v<decltype(f)>); // 現在は失敗する、この提案ではパスする
static_assert(std::is_move_assignable_v<decltype(f)>); // 現在は失敗する、この提案ではパスする

参照キャプチャの例

int y = 1;
auto f = [&y](int x) { return x + y; };
auto g = f;  // 現在もok
g = f;       // 現在はng、この提案でもng

static_assert(std::is_copy_assignable_v<decltype(f)>); // 現在は失敗する、この提案でも失敗する
static_assert(std::is_move_assignable_v<decltype(f)>); // 現在は失敗する、この提案でも失敗する

参照キャプチャの場合(正確にはメンバが未規定になるものの)クロージャ型は参照メンバを持つことになり、参照メンバを持つクラス型は代入演算子が暗黙deleteされます。これは、参照そのものの再代入が不可能であるためです。

この提案はEWGのレビューをパスしてCWGでレビュー中です。

P3965R0 2026-01 Library Evolution Polls

2026年1月に行われるLEWGにおける投票の予定。

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

P3505R2はDRとしてC++29を目指しており、他のものはNBコメント解決としてC++26をターゲットにしています。

P3967R0 Dual compiles of functions with contracts

単一のコンパイルコマンドで、チェックされる契約アサーションとされない契約アサーションの両方をコンパイルするようにする提案。

この提案は、C++26 Contracts機能が契約アサーションのセマンティクスを十分に制御できないことによる問題を解決するために、一回のコンパイルでチェックされるコードとされないコードをコンパイルし、マングル名で区別して同居させるとともに、エントリポイントにおいてチェックされるされないをブロックによって明示的に指定するようにすることを提案するものです。

bool log(const char* msg)
{
  std::cout <<msg <<std::endl;
  return true;
}

void subfunc()
  pre(log("Running subfunc"))
{
}

void func()
  pre(log("Enter func"))
  post(log("Leave func"))
{
  subfunc();
  unchecked_contracts {
    subfunc();
    checked_contracts {
      subfunc();
    }
  }
}

int main()
{
  func();

  unchecked_contracts {
    func();
  }
}

出力

Enter func
Running subfunc
Running subfunc
Leave func
Running subfunc

この例は例示のために契約アサーションで副作用を伴うコードを実行していますが、契約アサーションでそれらの関数を呼べるようにする意図はこの提案にはありません。

main()においてはチェックあり無しどちらのコードを呼び出すかをunchecked_contracts/checked_contractsブロックを用いて選択できます。unchecked_contractsブロック内では、チェックされないコードが呼び出され、checked_contractsブロックではチェックされるコードが呼び出されます。

チェックされるされないの実行時の選択は、thread_localなチェックモード変数を用いて行われます。各関数呼び出しはコンパイル時にこのチェックモード変数を参照してディスパッチを行うサンク関数を経由して呼ばれるようになり、関数ポインタ経由の呼び出しや仮想関数の呼び出しにおいても適切にチェックされる/されないをディスパッチすることができます。

この提案による実装においては、コンパイル後のバイナリには契約チェックの有無で1つの関数あたり2つの実体が含まれるためバイナリサイズが単純に倍になり、コンパイル時間もその分増加します。また、サンクとチェックモード変数によるディスパッチはわずかながら素の関数呼び出しと比較してオーバーヘッドがあります。提案では、LTCGやチェックモード変数のレジスタ配置などの最適化によってこれらのデメリットを緩和できるとしています。

また、このような変更と相いれなくなるため、コンパイラはある翻訳単位内のすべての契約アサーションについてignore/observeセマンティクスでコンパイルする機能を提供すべきではないことも提案しています。

P3968R0 A Framework For Contracts

契約アサーションをユーザー定義型で実現する提案。

P2900で提案されているC++26のContracts機能には様々な批判が投げかけられていますが、その中にセマンティクスの選択にあいまいな点が多い(標準外のコンパイラオプションに委ねられている)ため、プログラマが意図した契約アサーションのセマンティクスが選択されない可能性がある、という問題があります。

この提案はこの問題の解決のために、契約アサーションのセマンティクスをクラス型で実装・表現して、そのconstexprオブジェクトを契約アサーションとして扱うライブラリベースソリューションを提案しています。

namespace myns {

  struct MyAssertionType {
    // The existence of the assertion_object_kind data member is used to
    // determine that objects of this type are usable as contract assertions. This can be
    // detected using a concept or innately by the compiler.
    int assertion_object_kind; // pre, post or assert.

    // These data members, if they exist, modify the core language behavior, reverting to
    // these P2900-compatible defaults if they don't.
    bool constify; // Constify condition if true.
    bool ignorable; // Contract assertions are ignored in unchecked builds.
    bool assumable; // Don't assume predicate was true in following code.

    void operator()(string_view comment, exception_ptr ep, int,
                    source_location loc = source_location::current()) const {
      if (ep == nullptr)
        std::println(f"Assertion {comment} at {loc} failed.");
      else
        std::println(f"Exception evaluating assertion {comment} at {loc}");
      abort();
    }
  };

  inline constexpr MyAssertionType myPre {
    .assertion_object_kind = 1, // Values are the same as in the assertion_kind enum.
    .constify = false, // I don't want constification in my contract assertions.
    .ignorable = false, // These contracts can't be ignored.
    .assumable = true // Assume the contract was not violated in succeeding code.
  }

  void myFun(int v) myPre(v > 0);
}

このmyPreはアサーションオブジェクトと呼ばれるオブジェクトで、その型はアサーションオブジェクト型と呼ばれるクラス型です。この例にあるようにアサーションオブジェクト型では特定のメンバを定義しておくことでその識別とセマンティクスの制御を行うことができ、特にoperator()では契約が破られた場合の振る舞いをユーザーコードで指定し制御することができます(すなわち違反ハンドラにあたります)。

とはいえ機能の全体がユーザー定義である訳では無く、関数宣言においてアサーションオブジェクトを識別することおよび、アサーションオブジェクトを契約アサーションとして扱ってその評価を(必要なら)行ってその結果に応じてアサーションオブジェクトのoperator()を呼び出すコードを生成するのもコンパイラの役割です。

アサーションオブジェクトとその型をそれと認識する方法は、assertion_object_kindメンバが存在しint型に変換可能であることによってコンセプト的に検出することを提案しています。

アサーションオブジェクトが識別されて以降、必要なメンバが見つからないとか、名前検索におけるエラー、多重定義時の挙動などはほぼ従来のC++の関数や変数におけるルールに従うことになります。

このような方法により、契約アサーションの評価セマンティクスの柔軟な制御とその選択の確実性を両立することができます。また、同時にContracts機能がライブラリ機能である<contracts>に依存しなくなるというメリットもあるとしています。

この提案によるフレームワークを用いることでC++26のContracts機能を再実装することができ、それによって後方互換性を保つことができるためC++29以降にこの提案を導入しても破壊的変更にはならないとしています。ただし、非互換な部分があるなどの理由により、この提案を将来の方向性として採択する場合はP2900をC++26から削除することを推奨しています。

namespace std::contracts {
  struct assertion_object_t {
    assertion_kind assertion_object_kind;
  
    bool constify;
    bool ignorable;
    bool assumable;
    
    void operator()(string_view comment, evaluation_semantic semantic, exception_ptr ep,
                    source_location loc = source_location::current()) {
      if (semantic != evaluation_semantic::quick_enforce) {
        contract_violation violation(comment,ep, assertion_object_kind, loc, semantic);
        handle_contract_violation(violation);
      }
      if (semantic != evaluation_semantic::observe)
        abort();
    }
  };
}

// In the global namespace
inline constexpr std::contracts::assertion_object_t pre {
  .assertion_object_kind = std::contracts::assertion_kind::pre,
  .constify = true,
  .ignorable = true,
  .assumable = false
};
inline constexpr std::contracts::assertion_object_t post {
  .assertion_object_kind = std::contracts::assertion_kind::post,
  .constify = true,
  .ignorable = true,
  .assumable = false
};
inline constexpr std::contracts::assertion_object_t contract_assert {
  .assertion_object_kind = std::contracts::assertion_kind::assert,
  .constify = true,
  .ignorable = true,
  .assumable = false
};

おわり

この記事のMarkdownソース

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

文書の一覧

全部で113本あります。

もくじ

N5011 Brno 2026

2026年06月に予定されている全体会議の案内。

2026/06/08~2026/06/13にチェコのBrnoで開催される予定です。

文書では会場や行き方などの案内がされています。

N5029 WG21 2025-10 Kona Admin telecon minutes

2025年10月26日に行われた、WG21管理者ミーティングの議事録。

前回からどのような活動があったかや、Kona会議で何をするかなどの報告がなされています。

N5031 WG21 2025-11 Kona Minutes of Meeting

2025年11月にKonaで行われた全体会議の議事録。

最終日に行われた全体会議での各グループの作業報告と、全体投票の様子が記録されています。

N5032 Working Draft, Standard for Programming Language C++

C++26のワーキングドラフト第9弾。

N5033 Editors' Report - Programming Languages - C++

↑の変更点をまとめた文書。

P1317R2 Remove return type deduction in std::apply

std::applyの戻り値型推論をやめる提案。

以前の記事を参照

このリビジョンでの変更はよくわかりませんが、文言の変更のみのようです。特に、R1で提案していた関連するコンセプトが削除されています。

この提案は2025年6月の全体会議でC++26に向けて採択済みです。公開を忘れていた様子です。

P1789R2 Library Support for Expansion Statements

P1789R3 Library Support for Expansion Statements

std::integer_sequenceを構造化束縛および展開ステートメントで使用できるようにする提案。

以前の記事を参照

R2での変更は

  • 関連する機能テストマクロを更新
  • NBコメント対応
    • NCIT-002
    • FR 007-011-142
    • CZ 2-143

これらのNBコメントはいずれも、この提案をC++26に入れることを推奨するものです。

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

  • LWGレビューを反映
  • 不要なコメントの削除
  • const integer_sequenceの特殊化を追加

などです。

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

P2243R0 Language linkage for templates

テンプレートに対するCリンケージ指定を許可する提案。

言語リンケージの指定とはextern "C"のような指定を宣言に付加したり、あるいはブロックに付加したりすることを言います。これはエイリアス宣言や関数宣言、変数宣言などに対して行うことができますが、テンプレートに対する言語リンケージの指定は明確に禁止されています。

ただし、現在の規定ではこの「言語リンケージ(language linkage)」という言葉の指すものが非常にあいまいであり、特にテンプレートに対する言語リンケージという言葉がほとんど定義されていないという問題があります。

関数に対する言語リンケージとは、ABIにおける呼び出し規約を反映したものになっています。

// C++言語リンケージの関数宣言
void f();

// `cf`は「voidを返すCの関数」型
extern "C" typedef void cf();

cf *p = f;  // ng: 型が合わない

(ただし一部の実装ではこれを受け入れるようです)

すなわち、関数に対する言語リンケージの指定は、その関数の呼び出し規約を指定することに対応しています。

一方、変数に対する言語リンケージは、ABIにおける名前マングルを反映したものになっています。

namespace N {
  typedef void* mk(int);      // C++言語リンケージで型`mk`(関数型)を用意
  extern "C" mk make_vector;  // 変数宣言、"C"言語リンケージは関数型には適用されない
  void* make_vector(int i)    // 再宣言は言語リンケージを保持する
  {
    return new std::vector<int>(i);
  }
}

int make_vector;              // ng、N::make_vector と衝突する

グローバル名前空間スコープの変数に対しては名前マングルが行われないため、この衝突が起きています。

すなわち、変数に対する言語リンケージの指定は、その変数名の名前マングル方法を指定(C++マングルの抑制)することに対応しています。

ちなみに、グローバル名前空間スコープの変数名に対するこの規則はなぜか演算子関数に対しても同様に適用されます。

struct A {};
struct B {};

extern "C" {
void operator+(A);
void operator+(B);  // ng、関数型が衝突している
}

一方、ユーザー定義リテラルではこのようなC言語リンケージの指定が禁止されています。

演算子関数はCリンケージで呼び出されることはないので、本来は不要なはずです(このことは言語リンケージというものの曖昧さを表しています)。

そして、テンプレート(関数・変数・クラス・コンセプトを含む)は一律に言語リンケージの指定が禁止されています。ここでの言語リンケージが何を指す(上記のいずれの意味なのか、また別の意味なのか)のかは特に規定されていません。

テンプレートは特殊化ごとに異なる名前を持つことになる(独自の名前マングル方法を持つ)ため名前マングルの指定は不要です。一方、呼び出し規約の指定の側面は有用である可能性があります。しかし現在はその区別なく禁止されているため、関数テンプレートや関数型を示すエイリアステンプレートに対して呼び出し規約を指定することができなくなっています。

例えば次のように、いくつかの種類のオブジェクトを受け取るC APIがあるとき

/* getters.h */
#ifndef GETTERS_H
#define GETTERS_H

#ifdef __cplusplus
extern "C" {
#endif

struct things {unsigned a,b;};

void get_int(int*);
void get_int_fast(int*);
void get_float(float*);
void get_things(struct things*);

#ifdef __cplusplus
}
#endif

#endif

これを次のようにラップして使いたくなることもあるでしょう

#include"getters.h"

// ラップ関数
namespace wrap {
  // C言語リンケージを持つ関数ポインタ型
  extern "C" typedef void (*int_getter)(int*);

  int get_int(int_getter f) {
    int ret;
    f(&ret);
    return ret;
  }
}

// 使用例
namespace client {
  void g() {
    auto val = wrap::get_int(get_int_fast);
    // ...
  }
}

これを一歩進めてテンプレート化したくなるかもしれません

namespace wrap {
  
  template<class T>
  T get(/* ... */ f) {
    T ret;
    f(&ret);
    return ret;
  }
}

このとき、このget<T>の引数型(fの型)を正しく記述することが非常に困難になります。なぜなら、void (*f)(T)と書くとこれはC++リンケージを持つ関数ポインタ型となってしまいC言語リンケージを持つ関数ポインタ型として記述できないためです。

テンプレートに言語リンケージを指定できないことから、エイリアステンプレートとして記述することもできません。

namespace wrap {
  extern "C"
  template<class T>
  using getter=void(*)(T);    // ng、テンプレートは言語リンケージを持てない

  template<class T>
  T get(getter<T> f) {
    T ret;
    f(&ret);
    return ret;
  }
}

このように、関数テンプレートおよびエイリアステンプレートに対して呼び出し規約を指定するためにC言語リンケージを指定することには価値がある可能性があります。

別の例として、次のようにオブジェクトの破棄の責任を受け取るC APIを考えます

/* cleanup.h */
#ifndef CLEANUP_H
#define CLEANUP_H

#ifdef __cplusplus
extern "C" {
#endif

void transfer_ownership(void*,void(void*));

#ifdef __cplusplus
}
#endif

#endif

これもまたラップしてデストラクタで使用できるようにしたくなることもあるでしょう

#include "cleanup.h"
#include <memory>

class A {/* ... */};

extern "C" void del_A(void *p) {
  delete static_cast<A*>(p);
}

void transfer_A(std::unique_ptr<A> a) {
  ::transfer_ownership(a.release(), del_A);
}

これを一歩進めてテンプレート化しようとすると、同様にC言語リンケージを指定できない問題にぶつかります

// テンプレートには言語リンケージを指定できない
extern "C" template<class T>
void del(void *p) {
  delete static_cast<T*>(p);
}

このように、関数テンプレート自体にもC言語リンケージを指定できると有用である可能性があります。

この提案は、現在のテンプレートに対する言語リンケージの禁止という制限を削除し、テンプレートに対するCリンケージの指定を許可しようとするものです。この提案の後では、上記の2例はそのまま許可されるようになります。

この提案は次のコードを許可するものではなく

template<class>
extern "C++" void f() {}  // ng、この提案後も

次のコードを許可するものです

extern "C" {
  template<class T>
  void f(T);

  template<class T>
  void g(void(T));
}

template<class T>
void f(T) {}

template<class T>
void g(void(T)) {}

このコードでは、関数テンプレートfを1つ宣言(定義)し、fの特殊化の型はCリンケージを持ちます。一方、関数テンプレートgは2つ宣言されており、そのうちの一つはC関数へのポインタを受け入れ、Cの呼び出し規約を使用します。

この提案はC++29をターゲットにしていますが、EWGではDRとすることで合意が取れているようです。

P2728R9 Unicode in the Library, Part 1: UTF Transcoding

P2728R10 Unicode in the Library, Part 1: UTF Transcoding

以前の記事を参照

R9での変更は

  • コピー不可能なinput_iteratorに関する文言におけるconstバグの修正
  • buf_std::arrayではなくstd::inplace_vectorを使用することで、説明専用のbuf_last_を削除
  • to-utf-view-implの演算子実装を簡略化
  • utf-iteratorto-utf-view-impl::iteratorにリネーム
  • to-utf-view-impl::sentinel型を追加
  • begin()をキャッシュする
  • reserve_hint()を追加
  • input_iteratoroperator==のバグ修正
  • 設計に関する議論を追加
  • UTF-32との間で変換を行う場合にsize()を追加

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

  • bool OrErrorCTPをranges::subrange_kindのようなto_utf_view_error_kind列挙型に置き換え
  • to_utfX_viewクラスをto_utf_viewに統合し、CTADを動作させるためにコンストラクタタグを追加
  • _or_error CPOを使用した場合にempty_viewの値型が正しく設定されないバグを修正
  • CPO templateについて、views::adjacent_transform<N>などの前例があったため新規性があるとする記述を削除
  • begin()をキャッシュしない
  • CPOでcharN_tの配列を拒否する
  • CPOで二重変換の最適化を実装する

などです。

P3064R3 How to Avoid OOTA Without Really Trying (Informational)

C++コンパイラによる実装においては、OOTA問題が発生しないことを解説する文書。

以前の記事を参照

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

  • RFUBとNOOTABOGAのリトマステストを追加
  • 情報提供文書であることを明記
  • 対応する提案文書としてP3692を指定

などです。

P3097R1 Contracts for C++: Virtual functions

契約プログラミング機能において、仮想関数に対する契約の指定をサポートする提案。

以前の記事を参照

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

  • R0にあった他言語の例や代替案などの設計空間の分析に関する記述がP3600R0へ分離された
  • 「Design goals and principles」セクションの追加
  • P2900とこの提案(リビジョン)に関する背景を追記
  • N5014へのリベース

などです。

この提案は一旦はP2900に取り込まれていたものの、EiffelやDで確立され実展開されているモデルから逸脱しており十分な導入と実装の経験が不足している、という懸念が提起され、C++26 Contracts仕様からは削除されC++29以降に向けて再検討することになりました。

再検討のためにContracts機能におけるユースケースや設計空間、およびトレードオフに関する詳細な分析を行い(P3600R0)、その分析に基づいてR0の設計がC++にとって最適なソリューションであるとして再提案しています。ただし、以前とは異なりGCCにおいてこの提案の実装が行われており、それについても言及されています。

したがって、このリビジョンではその提案内容や設計はR0から変化していません。設計根拠がより強化されており、契約アサーション機能を実装しているEiffel/D/Aidaの設計がC++にそのまま採用できるものではないことが詳しく説明されています。

P3099R1 Contracts for C++: User-defined diagnostic messages

契約アサーションにユーザーがエラーメッセージを指定できるようにする提案。

以前の記事を参照

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

  • "Empty string vs. no string"セクションを追加
    • メッセージが指定されていない場合に、contract_violation::message()から何を返すか?
      • nullptr or 空文字列
      • メッセージが指定されていない事と空文字が指定されていることを区別できるため、nullptrを選択

などです。

P3100R5 Implicit contract assertions

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

以前の記事を参照

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

  • 未定義動作リストに、見落とされていた未定義動作を1件追加

などです。

P3216R2 views::slice

元の範囲の連続した一部分を切り出すRangeアダプタ、views::sliceの提案。

以前の記事を参照

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

  • std::optionalの特殊化を追加

などです。

P3220R2 views::take_before

入力の範囲を指定した値が最初に出現する位置を終端として切り出すRangeアダプタ、views::take_beforeの提案。

以前の記事を参照

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

  • 要素型がtidy-objを満たす(かつview型がborrowed_rangeである)場合にborrowed_rangeとなるようにする
    • tidy-obj: 空のクラスかつトリビアルにデフォルト構築可能かつトリビアルに破棄可能

などです。

P3371R5 Fix C++26 by making the rank-1, rank-2, rank-k, and rank-2k updates consistent with the BLAS

<linalg>の一部の関数を対応するBLAS関数の仕様と整合させる提案。

以前の記事を参照

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

  • LWGレビューを反映

などです。

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

P3388R3 When Do You Know connect Doesn't Throw?

execution::connectによる操作が例外を送出するかどうかを早期に判定できるようにする提案。

以前の記事を参照

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

  • LWGレビューを反映

などです。

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

P3391R2 constexpr std::format

std::format()constexpr化する提案。

以前の記事を参照

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

  • std::to_stringstd::to_wstringconstexprを付加

などです。

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

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

以前の記事を参照

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

  • std::filesystem::pathと整合性を持つようにwchar_tの文言中の扱いを修正
  • 今後の変更によるABIへの影響について明確化
  • デバッグフォーマットがエラーコード全体に適用される理由を明確化
  • error_categoryのフォーマッタの提案へのリンクを追加
  • 謝辞を追加

などです。

P3400R2 Controlling Contract-Assertion Properties

契約アサーションに対してラベルを指定する機能の提案。

以前の記事を参照

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

  • introductionを再構成
  • 違反ハンドラからラベルオブジェクトにアクセスする際の仕様を修正
  • ベースラインの要素と今後の作業内容を分離
  • 代替設計の方向性について明確化
  • チェックされないセマンティクスを許容しない契約アサーションで発生する問題に対処

などです。

P3412R3 String Interpolation

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

以前の記事を参照

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

  • printfのサポートを削除
    • 将来の拡張としては残しておく
  • stringize argumentfリテラルが含まれる場合の文字列化の処理方法に関する説明を追加
  • _Pragmaは式フィールドで使用できることを明確化
  • 式フィールド内のラムダ・statement expression(GCC)・^block(Clang)は式フィールドの終了検出に問題を起こさないことを指摘
  • 生文字列リテラルはfリテラルと特別な方法で相互作用しないことを明確化
    • ただし、フェーズ2(行の連結)の反転処理はfリテラルのうち式フィールド以外の部分で実行されなければならない
  • ユーザーが記述した__format__呼び出しが連結と文字列化に与える影響について議論
  • モジュール関連の相互作用に関するセクションを追加
    • 基本的に影響はない
  • ::がフォーマット指定子を開始できることを指摘(rangeフォーマッタの場合)
    • このリビジョンではこの問題は解決されず、::は一つのpp-tokenとして認識され、フォーマット指定子を開始しない
  • std::formatconstexprになったため、__format__にもconstexprを付加
  • 実装可能性に関する各ベンダからのフィードバックを追加
  • 式フィールドの最上位レベルで条件演算子の使用を禁止する
    • これにより、prefix演算子として?の使用を許可する
      • 例えば値の存在を確認するなど
    • 式フィールドの終了判定も多少簡素化される
  • R2で議論された2つの記述戦略を組み合わせた記述の最初のバージョンを追加
  • cpp_string_format属性を削除し、代わりにオーバーロード解決が失敗するか、暗黙的な変換シーケンスでconsteval関数を呼び出す必要がある場合には、__format__引数リストを展開するようにする
  • デバッグ機能を削除
    • この機能があると、式フィールドの内容を字句解析する前に、フェーズ3で先行する文字列リテラルpp-tokenを出力できなくなる
    • clangでの実装は容易だったものの、フェーズ3でpp-tokenをバッファリングできないコンパイラでは実装不可能

などです。

std::printstd::format()を経由しないためにR3で導入された[[cpp_format_string(N)]]属性はオーバーロード解決フェーズに影響を及ぼすことでそれを実現するものでした。このリビジョンではそれを省いて、単にstd::print(f"Value {1}")のように書いたときにそのままfリテラルを展開してオーバーロードに失敗する場合に、fリテラル展開後の__format__(args...)の引数args...をそこに展開するようにしています。

ただし、std::format()がコンパイル時に呼び出せる場合はコンパイル時のstd::format()で完了させます。

std::string a;

a = f"Value {0}";                                   // #0
std::print(f"Value {a}");                           // #1
std::print(f"Wrong value {1}");                     // #2
std::print("String value: {}", f"Some value {3}");  // #3

これらの使用コードは次のように展開されます

a = __format__("Value {}", 0);                      // #0
std::print("Value {}", a);                          // #1
std::print("Wrong value {}", 1);                    // #2
std::print("String value: {}", __format__("Some value {}", 3));  // #3

P3424R1 Define Delete With Throwing Exception Specification

delete演算子オーバーロードが例外を送出しうる例外仕様を持つことを禁止する提案。

以前の記事を参照

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

  • 最新のWDにリベース
  • 非推奨に関する記述を削除
  • CWG 2024が解決されることを確認

などです。

P3505R2 Fix the default floating-point representation in std::format

P3505R3 Fix the default floating-point representation in std::format

std::format()における浮動小数点数出力時のデフォルトの表現(gオプション)を修正する提案。

以前の記事を参照

R2での変更は

  • std::to_charsを修正するオプションを追加
  • __cpp_lib_to_chars, __cpp_lib_constexpr_charconv機能テストマクロを更新

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

  • LEWG投票結果の追加
  • std::formatだけでなくstd::to_charsも修正されていることを明確化
  • オプション1を文言から削除し、LEWGが承認したオプション2のみを残す

などです。

LEWGのレビューにおいては、この提案の内容をDR(to_chars()関連はC++17、format関連はC++20)とすることで合意が取れているようです。

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

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

以前の記事を参照

このリビジョンでの変更は編集上の修正のみです。

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

P3642R3 Carry-less product: std::clmul

整数のキャリーレス乗算を行う関数の提案。

以前の記事を参照

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

  • operator<=>に不足していた<T>を追加
  • §4. Possible implementation にSIMDに適した実装を追記
  • 文言のリベースと改善

などです。

P3657R1 A Grammar for Whitespace Characters

P3657R2 A Grammar for Whitespace Characters

C++規格文書における空白(whitespace)の扱いを明確化する提案。

以前の記事を参照

R1での変更は

  • N5014にリベース
  • NBコメントUS 5-108への対応を明確化
  • 文言を大幅に再構成
  • 動作を変更する可能性のある提案をすべて削除
  • トークンを区切る空白文字に関する記述を削除
  • P2348R3との比較を簡略化
  • C++26 CDに採択されている他の提案との相互作用を記録
  • 縦方向の空白文字の処理に関する今後の方向性を追加

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

  • SG16のフィードバックを反映
  • CWG2002解決を明記
  • 最後のプリプロセッシングトークンが影響を受けない理由を明確化

などです。

P3666R2 Bit-precise integers

C23の_BitIntをC++に導入する提案。

以前の記事を参照

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

  • §4. Core design にSG22/SG6の投票結果を追加
  • §4.8. Raising the BITINT_MAXWIDTH で提案されていた代替値BITINT_MAXWIDTHを65535から32767へ変更
  • SG22のフィードバックにより、§5.1. Naming of the alias template を拡張
  • §5.4. Preserving integer-class types を追加し、それに伴い文言を変更
  • §5.6. New abs overload および本文全体における戻り値型の欠落を修正
  • §5.15. Passing bit_int into standard library function templates を拡張し、戻り値型に関する考察を追加
  • §5.16. The problem of representing widths as int を追加
  • §5.17. Library policy for function templates accepting bit_int を追加し、提案されたポリシーを §5.6. New abs overload に適用
  • 参照をN3699 から N3747に更新
  • <cmath>だけでなく<cstdlib>にもstd::absのオーバーロードを追加
  • § [utility.intcmp] を追加

などです。

P3684R1 Fix erroneous behaviour termination semantics for C++26

erroneous behaviourにおける終了動作についての調整を行う提案。

以前の記事を参照

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

  • NBコメントGB 02-036の解決を明記
  • 文言の改善

などです。

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

P3688R5 ASCII character utilities

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

以前の記事を参照

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

  • is_ascii_*関数をascii_is_*に変更
  • §3.8. Naming を追加
  • §3. Design において、複数のオーバーロードをすべての関数に対して単一の関数テンプレートに変更する
  • §3.5. Case-insensitive comparison functions において、異なる文字型を混在させることがサポートされていない理由を明記
  • <ascii>のsynopsisにおけるascii_is_digitの不要なデフォルト引数を削除
  • Cの用語に合わせるため、is_ascii_printableascii_is_printingにリネーム
  • Cの用語に合わせるため、is_ascii_graphicalascii_is_graphicにリネーム
  • ascii_is_alphanumericとの不整合を回避するために、is_ascii_alphaascii_is_alphabeticに変更する
  • §5. Wording においてASCII-compatibleの定義を正しくする

などです。

P3692R3 How to Avoid OOTA Without Really Trying

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

以前の記事を参照

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

  • 規格に追加予定の注釈の更新
  • 著者リストの更新

などです。

P3695R3 Deprecate implicit conversions between char8_t and char16_t or char32_t

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

以前の記事を参照

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

  • char8_twchar_tの間の変換の非推奨化を撤回
  • 文言の改善

などです。

P3724R2 Integer division

商と剰余を様々な丸めモードで計算するライブラリ関数の提案。

以前の記事を参照

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

  • div_to_even, div_to_odd, div_rem_to_even, div_rem_to_oddを削除
  • §4.4.4. ISO/IEC 60559 Rounding modes を追加
  • div_to_infdiv_to_pos_infにリネーム

などです。

P3726R1 Adjustments to Union Lifetime Rules

トリビアルな共用体を定数式で使用可能にするために制限を緩和する提案。

以前の記事を参照

R0では次の3点を提案していました

  1. 共用体の最初のメンバの生存期間を暗黙的に開始するP3074ルールの撤回
  2. 集成体メンバ変数に対するplacement newは集成体そのものの生存期間を暗黙的に開始する
  3. 構成要素の値(constituent value)のルールを変更し、共用体の配列要素が(親の共用体or配列の)生存期間外にある場合でも扱えるようにする

このリビジョンでは、1を維持して、2をP3074R0で提案された解決策に変更しています。すなわち、std::start_lifetime()を再導入し、暗黙の生存期間をこの関数によって明示的に開始するようにします。

P3735R1 partial_sort_at_most, nth_element_at_most

中間イテレータを指定する必要があるアルゴリズムについて、その必要のないバージョンを追加する提案。

以前の記事を参照

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

  • SG9のフィードバックを反映
  • サフィックス_n_at_mostに変更
  • §4.2. Additional variants of partial_sort を追加
  • 文言から不要なSizeテンプレート引数を削除

などです。

P3737R2 std::array is a wrapper for an array!

std::arrayの実装自由度を制限する提案。

以前の記事を参照

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

  • LWG4276の採択を踏まえて、議論と文言を更新

などです。

P3739R4 Standard Library Hardening - using std::optional<T&>

std::inplace_vectorにおいて、std::optional<T&>を利用して堅牢化モードを導入する提案。

以前の記事を参照

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

  • 提案する文言の追加

などです。

P3744R0 Explicit Provenance APIs

ポインタのProvenanceを扱うためのライブラリAPIの提案。

CHERI(Capability Hardware Enhanced RISC Instructions)という技術が組み込まれたCPU(ISA)においては、ポインタにいくつかのメタデータを埋め込むことで、ポインタを介したメモリアクセスにおいてメモリ安全性の問題が起こることをハードウェアレベルで防止することができます。

CHERIのメタデータにはいくつかのフィールドがありますが、その内のタグビットはポインタ(CHERIのメタデータ)の改竄を防止するためのフィールドであり、ポインタをコピーしたりする際に自動でセットされ、CHERIメタデータの一部を上書きすると削除されるなどの性質を持ちます。これは結果的に、ポインタのProvenanceの性質のハードウェアによる表現になっています。

CHERIはすでにいくつかのアーキテクチャに組み込まれて実装されており、LLVM/Clangの対応を通してCとC++の実装がすでに確立されています。CHERI環境のC/C++実装では、CHERIメタデータはポインタを用いて表現可能である一方で、ポインタからアドレスを取得してそれを整数として扱う処理においては現在のC++のモデルのままだと問題があることが分かっています。

それは、ポインタ(アドレス)を整数値に変換する際に、後でポインタに戻される操作とポインタに戻されない操作の2つを区別できない点で、現在のC++実装及びモデルでは、どちらの場合も後でポインタに戻される操作とみなして過度な最適化を制限します。

CHERI C++におけるポインタ型はメタデータが追加された分大きくなっており、ポインタのうちアドレスが占める部分はおよそ半分になります(例えばvoid*128bitに対して、size_t64bit)。それにより、Provenanceが不要である場合の整数型はポインタ型のサイズに対してその差の分小さくすることができます。逆に、Provenanceが必要である場合はメタデータを保持する必要性からポインタ型と同じサイズを維持する必要があります。このため、最適化の観点から、ポインタから変換された整数値のうち後でポインタに戻さないもの、を区別できる必要が生まれます。

CHERI C++では、後からポインタに戻されない操作(変換)を区別するためにptraddr_tという専用の整数型を用意しています。ポインタ値がptraddr_tの値に変換された後でポインタに戻されても、デリファレンスできない(CHERIの仕組みによる拒否される)ポインタが生成されることが保証されます。すなわち、ポインタをptraddr_tに変換するということは後からポインタに戻さない事を表明します。

逆に、あとからポインタに戻すような整数値への変換は、現在のC++コードで記述しているのと同じように行えます。すなわち、CHERI C++においてのデフォルト動作はポインタの整数値への変換はあとからポインタに戻すことを仮定するものです。これはポインタの整数型への変換でも同じです。

したがって、CHERI C++のようなハードウェアによるProvenance実装を活用するためには、通常のポインタと整数型の経路とは別の経路でもって、後からポインタに戻されない変換とその操作を区別する必要があります。この提案はこのサポートのためのライブラリAPIを追加しようとするものです。

提案ではまず、ptraddr_t型を導入します。これは、provenance-freeの整数型であり、次の特性を持つものです

  • 異なるオブジェクト(のポインタ)からのptraddr_t値は異なる値となる
  • 同一オブジェクト内の異なるオフセットを指す2つのポインタ(メンバポインタ相当)は、異なるptraddr_t値を生成する
  • 2つのポインタ型をchar*に変換し一方から他方を減算した結果は、両方のポインタをまずptraddr_tに変換してから減算した結果と等値になる
  • ptraddr_tからポインタ型へのキャストは未定義動作となる
    • 実装ではこれらの操作に対して診断を行うことが推奨される

ポインタ型とのサイズ比の違いや後方互換のために、ptraddr_tsize_tとは異なる型として定義される必要があります。

前述のように、ポインタからptraddr_tの値を得るには通常の変換ではなく専用の関数による変換を必要とします。

// <cstdint>
using ptraddr_t = /*ポインタアドレスを保持するのに適した整数型*/;

// ポインタのアドレスを取得する
ptraddr_t get_address(void* p) noexcept;

ポインタpからget_address(p)で整数を取得することは、この整数値を後からポインタに戻すことは無いことを表明します。

ポインタを数値として扱う際に最適化を最大限に活用しつつも、なお後からポインタに戻したいユースケースがあるようです。そのために、ptraddr_tの値をポインタに復帰する関数も用意されます。

// `p`のメタデータで`address`のアドレス値を持つポインタを作成する
template <typename T>
T* with_address(T* p, ptraddr_t address);

// (utility): Maps the address of a pointer
// 
template <typename T, typename UF>
constexpr T* transform_address(T* p, UF f) 
  requires (invokable<UF, ptraddr_t> &&
            same_as<invokable_result_t<UF, ptraddr_t>, ptraddr_t>)
{
  return with_address(p, f(get_address(p)));
}

CHERI C++におけるポインタはCHERIのメタデータを内包しています。メタデータにはタグビットとアドレス以外にも権限や型の互換性を表すフィールドがあります。そのため、アドレス部分のみが取り出されているptraddr_t値をポインタに戻すためにはそのほかの部分のメタデータが必要となり、with_address(p, addr)addrpのメタデータを合成してポインタを復帰する関数です。

ただし、with_address()を用いて間違ったメタデータによって復帰されたポインタのデリファレンスは未定義動作となります。

transform_address()はポインタからptraddr_t値を取得して何か処理をしてまたポインタに戻す、という操作を複合的に行うためのユーティリティです。

この2つの関数は、アトミック操作におけるCASやXORリンクリストなどのように途中で整数値として扱いつつも最後はポインタに戻す場合でパフォーマンスが重要となる用途で使用できるものです。

CHERIのような環境におけるアトミック操作の効率化のために、atomic_refatomicT*特殊化に対してcompare_exchange_(weak|strong)を追加することも提案しています。

// <atomic>

// See below:
template <typename T>
class atomic_ref<T*> {
  constexpr bool compare_exchange_weak(
    T*&, T*,
    T*& provenance_target,
    memory_order, memory_order
  ) const noexcept;

  constexpr bool compare_exchange_strong(
    T*&, T*,
    T*& provenance_target,
    memory_order, memory_order
  ) const noexcept;

  constexpr bool compare_exchange_weak(
    T*&, T*,
    T*& provenance_target,
    memory_order = memory_order::seq_cst
  ) const noexcept;

  constexpr bool compare_exchange_strong(
    T*&, T*,
    T*& provenance_target,
    memory_order = memory_order::seq_cst
  ) const noexcept;
};

// analogous APIs for atomic<T*> and free functions

これらのアトミック操作はABA問題による未定義動作を回避しつつ、ptraddr_tを使用した最適化を適用するものです(提案には実装例があります)。

また、現在その操作がほぼ実装定義とされているintptr_t/uintptr_tの値に対する操作について、ポインタ演算との等価性を保証することを提案しています。ただしこれは、ポインタから変換されたintptr_t/uintptr_tの値に対する操作についてのみ保証し、無関係なintptr_t/uintptr_tの値の演算はptraddr_tの演算と同じ意味論を持つべきとしています。

P3751R0 A gentle introduction to pointer authentication

P3751R1 A gentle introduction to pointer authentication

ARMv8.3で導入されたPointer Authentication Codesと呼ばれる機能についての解説文書。

Pointer Authentication Codesとは、ポインタに対してコンテキストに応じた認証を行い、その認証コードをポインタのビット内に埋め込んで利用する仕組みの事です。これは現在ARMv8.3以降のISAで実装され、利用されています。

Pointer Authentication Codesの存在は、トリビアルリロケーションやポインタの利用されないビットを利用できるようにする提案に影響を与えているものの、まだ登場したばかりの技術であるため知名度が高くありません。

この提案はPointer Authentication Codesの概要と、Clangにおけるサポートとモデル化について解説するものです。特に、C++言語機能や型システムに対してどのような影響を与え、また与えないのか、について重視しています。

P3763R1 Remove redundant reserve_hint members from view classes

標準のview型がsize()を提供している場合にreserve_hintを削除する提案。

以前の記事を参照

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

  • take_viewについて、そのベースの範囲がapproximately_sized_rangeでない場合にのみreserve_hint()を有効化する

などです。

この提案については、関連するNBコメントがリジェクトされたようで、この提案の追及も停止されている可能性があります。

P3772R1 std::simd overloads for bit permutations

<bit>のビット置換系操作APIのstd::simdオーバーロードを追加する提案。

以前の記事を参照

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

  • 参照する提案の番号を修正
  • simd::bit_repeat()の不要なnoexcept削除
  • N5014にリベース

などです。

P3793R1 Better shifting

整数のビットシフトを行うライブラリ関数の提案。

以前の記事を参照

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

  • シフト量の引数を、任意の整数型を取れるようにした
  • 負のシフト量に対する操作を、逆方向のシフトにした
  • §4.3.1. Emulating possible behaviors using std::shl を追加
  • §4.3.2. Benchmarks of std::shl variants を追加
  • §5. Possible implementation を追加
  • N5014にリベース

などです。

P3804R1 Iterating on parallel_scheduler

  • parallel_schedulerの設計を洗練させるための提案。

以前の記事を参照

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

  • SG1+LEWGからのフィードバックを反映
  • P3826R2に基づいて、文言を修正

などです。

P3815R1 Add scope_association concept to P3149

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

以前の記事を参照

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

  • フィードバックを受けて、背景と同期に関するセクションを強化

などです。

P3816R1 Hashing meta::info

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

以前の記事を参照

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

  • ハッシュの様々なセマンティクスと、現在の動作を選択した理由について説明するセクションを追加
  • ABIに関する潜在的な懸念事項について説明するセクションを追加
  • 初期設計とサポートに関する潜在的な拡張についてのセクションを追加
  • モチベーションをさらに追加

などです。

P3824R2 Static storage for braced initializers NBC examples

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

以前の記事を参照

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

  • 文言の改善

などです。

理由は分かりませんが、この提案はリジェクトされているようです。

P3826R1 Fix or Remove Sender Algorithm Customization

P3826R2 Fix or Remove Sender Algorithm Customization

senderアルゴリズムのカスタマイズをC++29まで延期する提案。

以前の記事を参照

R1での変更は

  • C++26のアルゴリズムのカスタマイズ方法を改善する代替案を提示

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

  • indeterminate_domain<>を追加

などです。

R1で追加された代替案のカスタマイズ方法の修正は、senderに対して完了場所を問い合わせる際は開始場所を明示するようにすることです。元々、senderは自身がどこで開始されるかを知らない限りどこで完了するかが分からない、という点が問題だったため、完了場所を問い合わせる際に開始場所を指定するようにすることで解決します。

これはsenderに対して完了ドメインを問い合わせる際にreceiverの環境を渡すことで実現されます。get_domain(get_env(sndr))の代わりにget_completion_domain<set_value_t>(get_env(sndr), get_env(rcvr))のようにクエリするようにします。

この変更ではクエリの際にreceiverの環境が必要となるためEarly customizationは削除されます。

この修正はC++29を目指す場合でもおそらくほぼ同様となります。この変更は大きな設計変更でありC++26のこの時期に入れるにはリスクがあるとして、C++26ではカスタマイズについてを削除し、C++29で修正しつつ復帰という当初の案がメインの提案です。

P3833R0 std::multi_lock

複数のmutexに対してより柔軟なロックをかけることのできるstd::multi_lockの提案。

C++26時点では、標準ライブラリには次のミューテックスラッパクラスが用意されています

  • std::lock_guard
    • 単一ミューテックス用のシンプルなRAIIラッパ
  • std::unique_lock
    • 単一ミューテックス用の柔軟なRAIIラッパ
    • 遅延ロック、ロック取得の試行、所有権移転などの機能を備えている
  • std::scoped_lock
    • 複数のミューテックス用のRAIIラッパ
    • デッドロック回避機能を備えている

ただ、ここにはstd::scoped_lockのように複数のミューテックスを取りながらstd::unique_lockのように柔軟で多彩なロックをサポートしているようなものがありません。

表に起こすと、次の表の右下の部分をカバーするものがありません。

性質\ミューテックス数 単一ミューテックス 複数のミューテックス
常に所有する(ムーブ不可) std::lock_guard std::scoped_lock
柔軟なロック制御方法 std::unique_lock

このため、この提案ではそのような特徴(std::scoped_lockstd::unique_lockの両方の利点)を持つ(上記表の右下の部分をカバーする)新しいミューテックス用のRAIIラッパ型であるstd::multi_lockを追加しようとしています。

namespace std {

  // 宣言例
  template<class... MutexTypes>
  class multi_lock;
}

この提案のstd::multi_lock<Ms...>は次のような特徴を持ちます

  1. 0個以上のミューテックスを受け入れる
  2. RAIIによる自動ロック解除機能を提供
  3. 遅延ロック、 try-lock, adopt-lock戦略をサポート
  4. 時間制限付きロック操作をサポート
    • すべてのミューテックスがCpp17TimedLockableの場合
  5. ムーブセマンティクスによる所有権移転をサポート

遅延ロックの例

std::mutex m1, m2;
std::multi_lock lock(std::defer_lock, m1, m2);
// ... prepare work ...
if (condition) {
  lock.lock(); // 複数のミューテックスを扱ってもデッドロックを起こさない
  // critical section
}

時間制限付きロックの例

std::multi_lock lock(100ms, m1, m2);
if (lock) {
  // 指定された時間以内にすべてのロックを獲得できた場合
  // critical section
}

手動制御による条件付きロックの例

std::multi_lock lock(std::try_to_lock, m1, m2);
if (!lock) {
  // すべてのロックを取得できなかったため、ハンドルする
  return;
}

P3834R2 Defaulting the Compound Assignment Operators

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

以前の記事を参照

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

  • B&で渡される右辺オペランドの許容範囲を拡大
  • § 4.1 Minor Edge Casesで、列挙型のステータスを明確化
  • noexceptに関する説明を追加
  • 投票結果を追加

などです。

この提案はEWGのレビューにおいてリジェクトされているようです。

P3836R2 Make optional<T&> trivially copyable (NB comment US 134-215)

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

以前の記事を参照

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

  • タイトルにNBコメント番号を追加
  • LEWGの投票に従い、オプション1のみを提案する
    • オプション2はトリビアルコピー可能であることに加えてポインタとレイアウトが同じになることを指定するもの

などです。

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

P3843R1 Reconsider R0 of P3774 (Rename std::nontype) for C++26

P3843R2 std::function_wrapper

std::nontypestd::constant_argにリネームしないようにすることを推奨する提案

以前の記事を参照

R1での変更は

  • 現在の方向性を反映して、タイトルを変更
  • std::nontype_tの完全な代替案としては提案しなくなった

などです。

P3844R1 Restore simd::vec broadcast from int

P3844R2 Restore simd::vec broadcast from int

std::simdの暗黙変換が可能なブロードキャストコンストラクタを復帰させる提案。

以前の記事を参照

R1での変更は

  • constevalコンストラクタをbasic_vecの値型に自然に変換可能な算術型に限定する
  • common_typeへの影響について議論を追加
  • [simd.math]への影響について議論を追加
  • constevalコンストラクタをimmediate-escalating expressionとして扱うことについて議論を追加
  • [simd.math]への潜在的な影響について議論を追加

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

  • 提案していた投票を削除
  • 不要なsimd-broadcast-argを削除するように文言を更新
  • [simd.math]の文言を元の設計意図(明示的なオーバーロードセットであるかのように扱う)に合わせるように変更
  • [simd.math]の文言に対する修正/改善
    • math-common-simd-t<V0, V1>がスカラ型を含むデフォルト以外のABIタグの組み合わせで動作しなかった
      • 整数絶対値に関する事前条件を追加

などです。

P3847R0 Lambdas capture left to right

ラムダ式のクロージャ型において、明示的なキャプチャの初期化順序を規定する提案。

ラムダ式のクロージャ型は、そのメンバの宣言順は未規定でありメンバ変数の初期化順も未規定とされています。これは初期化キャプチャが導入されるよりも前からの仕様で、おそらく暗黙的なキャプチャの使用順序を追跡しない実装を許可するための自由度だったと思われます。

しかし、このような自由度は現在の実装よって使用されておらず、仮に使用したとしても有用なメリットが無さそうです。例えば、最も可能性がありそうなのはクロージャ型のパディングを最小化することですが、クロージャ型のメモリフットプリントが問題になることはめったにありません。

一方で、次のようなコードを合法ではなくしています

// 初期化の順序が重要になる例
void construction() {
  auto x = std::make_unique<int>();
  [value = *x,  // valueが初期化されるときには`x`がmove後である可能性がある
   lifetime = std::move(x)
  ] {};
}
// 破棄の順序が重要になる例
struct last_call {
  std::function<void()> f;
  ~last_call() {f();}
};

void destruction() {
  auto x = std::make_unique<int>();
  const auto user = [&i = *x] {std::cout << i << '\n';};

  [lifetime = std::move(x),
   trigger = last_call{user}  // userが呼ばれるころにはlifetimeが破棄されている可能性がある
  ] {};
}

特にこのように、初期化キャプチャにおいてはデフォルトメンバ初期化子などと類似していることもあり、書いた順序と異なる順序で初期化される場合はプログラマの意図とは明らかに異なるでしょう。

ただ現在のところ、主要な実装はこの自由度を利用していません。すべての主要な実装では、すべての非参照明示的キャプチャ(簡易キャプチャと初期化キャプチャ)の順に対応するメンバが定義され、その後に暗黙キャプチャがその使用順(キャプチャの出現順)に定義され、初期化はその順序で行われるようです。

また、Itanium ABIではクロージャ型のレイアウトにこの順序を使用して規定を行う予定があるようです。

そのため、この提案ではこれらの既存実装における慣例に従って、明示的なキャプチャ(簡易キャプチャと初期化キャプチャ)はすべてその出現順に宣言され初期化されるように規定することを提案しています。そして、これを欠陥報告(DR)とすることも提案しています(おそらくC++14)。

ただしここでは、暗黙キャプチャについての順序を明記することは提案していません(EWGが望むのであればそれも可能とはしていますが)。なぜなら、デフォルトキャプチャがある場合にソースコード順と異なる結果をもたらすためです。

またついでに、参照初期化キャプチャ用のメンバが存在しない場合にその初期化方法について全く指定がないという別の問題も修正しています。

EWGのレビューでは、この提案をDRとしてC++29ターゲットで導入することに合意が取れているようです。その際、暗黙キャプチャの順序を含むようにはしていないようです。

P3849R1 SIS/TK611 considerations on Contract Assertions

C++26 Contractsをホワイトペーパー等を経由するようにする提案。

以前の記事を参照

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

  • 参考文献と補足説明を追加
  • dependency セクションを拡張
  • marketing に関するセクションを追加

などです。

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

あるポインタが別のポインタ範囲内にあるかどうかをチェックする関数の提案。

何かしらオブジェクトや配列要素を指すポインタがあり、それが別のポインタによって構成される範囲内に含まれているかを調べたい、あるいは出自の分からないポインタが2つあるときにその等価性を調べたい、という場合がたまにあります。しかし、標準の範囲内でこのような比較を行う方法は用意されていません(直接の比較結果は未規定)。

一応、std::less<T*>などでは実装定義の全順序でポインタの比較を行うことができるため、実装のサポートがあればそのようなチェックを行うことはできます。しかしこの方法は定数式で使用できません。

この提案では、実装定義の全順序に依存しない形で直接的にポインタ同士の関連性をチェックすることのできる関数を標準ライブラリで用意しようとするものです。

提案しているのは次のような関数です

namespace std {
  constexpr bool is_pointer_between(const void * ptr, const void * first, const void * last); // freestanding
}

std::is_pointer_between()はシグネチャ通り、第一引数に渡したポインタ値が、第二・第三引数のポインタ範囲内に含まれる場合(first <= p and p < lastの場合)にtrueを返す関数です。

この関数は実装に全順序を要求することなく使用でき、定数式でも使用可能なものです。ただし、その実装はおそらくコンパイラマジックが必要になります。

これを用いると例えば、契約アサーションによってポインタの包含をアサートできるようになります。

constexpr bool intersect(std::span<const T> lhs, std::span<const T> rhs) noexcept {
  const auto * a = lhs.data();
  const auto * b = lhs.data() + lhs.size();
  const auto * x = rhs.data();
  const auto * y = rhs.data() + rhs.size();
  return std::is_pointer_between(x, a, b) 
      || std::is_pointer_between(y, a, b)
      || std::is_pointer_between(a, x, y)
      || std::is_pointer_between(b, x, y);
}

void transfer(std::span<const T> source, std::span<T> target) 
  pre(not intersect(source, target))  // 範囲がオーバーラップしていないこと
  pre(source.size() == target.size()) 
{
  std::copy(source.begin(), source.end(), target.begin());
}

また、std::hive.get_iterator(T*)constexpr化のためにも使用できるとのことです。

P3856R1 New reflection metafunctions - is_structural_type (US NB comment 49) and is_destructurable_type

P3856R2 New reflection metafunctions - is_structural_type (US NB comment 49) and is_destructurable_type

P3856R3 New reflection metafunctions - is_structural_type (US NB comment 49) and is_destructurable_type

ある型が構造化束縛によって分解可能であるかを取得する関数の提案。

以前の記事を参照

R1での変更は

  • 提案番号を修正
  • proof of concept (section 3.3)からの考察を追加

R2での変更は

  • is_structural_type()を追加

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

  • バージョン番号を修正

などです。

is_structural_type()は構造的な型(NTTPとして使用可能な型)を判定するリフレクションメタ関数です(名前は似ていますが役割はかなり異なります)。

P3858R1 A Lifetime-Management Primitive for Trivially Relocatable Types

指定された場所でオブジェクトの生存期間を再開するstd::restart_lifetimeの提案。

以前の記事を参照

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

  • 型消去のセクションにApplicationを追加
  • std::restart_lifetimeの冗長な呼び出しがサポートされていることを明確化
  • 関連するNBコメントへの参照を追加

などです。

C++26からトリビアルリロケーション関連が削除されたことを受けて、この提案の議論は一旦停止されています。

P3860R1 Proposed Resolution for NB Comment GB13-309 atomic_ref is not convertible to atomic_ref

std::atomic_ref<T>においてstd::atomic_ref<const T>への変換コンストラクタを追加する提案。

以前の記事を参照

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

  • noexceptを追加
  • 制約の記述を“T and U are similar types”に変更
  • 整数型/浮動小数点数型/ポインタ型の特殊化に変換コンストラクタを追加

などです。

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

P3868R1 Allow #line before module declarations

モジュール宣言の前に#lineディレクティブを置けるようにする提案。

以前の記事を参照

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

  • groupを使用しないように文言を変更
  • 別の編集上の変更との互換性を持たせる

などです。

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

P3869R0 Slides for P3666R1 Bit-precise integers

P3869R1 Slides for P3666R1 Bit-precise integers

P3666R1の紹介スライド。

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

P3666R1ではC23で追加された__BitIntをC++でもサポートしようとしています。このスライドではどのようにそれを輸入してくるか(ほぼCそのまま+C++標準ライブラリでの対応)について簡単に説明されています。

P3876R0 Extending <charconv> support to more character types

std::from_chars/std::to_charschar以外の文字型による文字列をサポートするようにする提案。

std::from_chars/std::to_charsは文字列と数値間の変換を行うもので、特にパフォーマンスに重きを置いています。これらの関数は入力/出力の文字列としてcharの文字範囲のみをサポートしています。char8_tを始めとしてC++にはchar以外の文字型がありますが、これらの文字型による文字列のサポートは行われていません。

この提案は、std::from_chars/std::to_chars両関数でchar以外の文字型による文字列を入出力としてサポートすることを提案するものです。

特に、std::format()における数値から文字列への変換はstd::to_charsを用いて規定されているため、std::formatchar8_t等のユニコード文字型をサポートするために重要なステップとなります。

この提案の対象の文字型はchar以外のすべてのもの

  • char8_t
  • char16_t
  • char32_t
  • wchar_t

です。

wchar_tを除けばこれらの文字型はすべてユニコードのエンコーディングの一つと対応しており、ユニコード(UTF)ではASCII範囲の文字が数値的にASCII互換になっているため、ほとんど特別な処理は不要です。

この提案では、ユニコード文字型のstd::from_chars/std::to_charsオーバーロードは共にcharオーバーロードの出力形式と互換となるようにしています。例えば、std::from_charsにおいてASCII範囲外の数字とみなせる文字(など)を数値として変換できるようにすることは提案していません。

また、ユニコードのエラーハンドリングもほとんど行わないことを提案しており、この提案のオーバーロードはあくまでユニコード文字列内のASCII互換数字の範囲内(基本ラテン文字のブロック内)でのみ動作します。すなわち、std::from_charsでは基本ラテン文字のブロック外の文字コードはすべて解析パターンではないものとして扱われます。

string_view cstr = "123z"; // OK, 正常
int i1;
const auto [p1, e1] = from_chars(cstr.data(), cstr.data() + cstr.size(), i1); // OK

u8string_view u8str = u8"123\xFF"; // 不正なUTF-8
int i2;
const auto [p2, e2] = from_chars(u8str.data(), u8str.data() + u8str.size(), i2); // OK

assert(i1 == i2);                              // i1とi2は共に123になる
assert(p1 - cstr.data() == p2 - u8str.data()); // どちらも3コード単位分をパターンとして認識

これらのことは、std::from_chars/std::to_charsは共にパフォーマンスを最重視する低レベルなユーティリティであることを反映しています。

この提案ではむしろ、既存のcharオーバーロードの互換性を崩すことなく新しいオーバーロードを追加する方が困難だとしています。なぜなら、std::from_chars/std::to_charschar版だけでも11個のオーバーロードがあり、単純に文字型毎にオーバーロードを定義すると55のオーバーロードを追加することになってしまうため、テンプレート化が必須となるからです。

テンプレート化すると特に問題があるのは戻り値型です。std::to_chars_result/std::from_chars_resultはどちらも、入出力文字列の位置に関する情報を保持するために文字へのポインタ(char*/const char*)をメンバとして持つ集成体型でありテンプレート化されていないためです。名前も最も適切なものを占有しています。

既存のこれらをテンプレート化することはできないため、元の型に変更をくわえず新しいテンプレート化された結果型を使用するために、この提案ではエイリアステンプレートを使用するようにしています。

提案より、新しいstd::to_charsセットの概要

template<class T>
  concept character-type = any character type;

// 現在の戻り値型
struct to_chars_result {
  char* ptr;
  errc ec;
  friend bool operator==(const to_chars_result&, const to_chars_result&) = default;
  constexpr explicit operator bool() const noexcept { return ec == errc{}; }
};

// 新しい文字型毎の戻り値型
struct wto_chars_result { /* ... */ };
struct u8to_chars_result { /* ... */ };
struct u16to_chars_result { /* ... */ };
struct u32to_chars_result { /* ... */ };

// 文字型に応じて戻り値型を導出するエイリアステンプレート
template<character-type T>
  using to_chars_result_t = one of the result types above;

// 既存の整数型オーバーロード
constexpr to_chars_result
  to_chars(char* first, char* last, integer-type value, int base = 10);
// 対応する新しい関数テンプレートオーバーロード
template<character-type T, integer-type-concept U>
  constexpr to_chars_result_t<T> to_chars(T* first, T* last, U value, int base = 10);

// 浮動小数点数型でも同じアプローチを採用
to_chars_result to_chars(char* first, char* last,
                         floating-point-type value);
template<character-type T, floating-point-type-concept U>
  to_chars_result_t<T> to_chars(T* first, T* last, U value);

to_chars_result to_chars(char* first, char* last,
                         floating-point-type value, chars_format fmt);
template<character-type T, floating-point-type-concept U>
  to_chars_result_t<T> to_chars(T* first, T* last, U value, chars_format fmt);

to_chars_result to_chars(char* first, char* last,
                         floating-point-type value, chars_format fmt, int precision);
template<character-type T, floating-point-type-concept U>
  to_chars_result_t<T> to_chars(T* first, T* last, U value, chars_format fmt, int precision);

新しいstd::from_charsセットの概要

struct from_chars_result {
  const char* ptr;
  errc ec;
  friend bool operator==(const from_chars_result&, const from_chars_result&) = default;
  constexpr explicit operator bool() const noexcept { return ec == errc{}; }
};
// analogous classes:
struct wfrom_chars_result { /* ... */ };
struct u8from_chars_result { /* ... */ };
struct u16from_chars_result { /* ... */ };
struct u32from_chars_result { /* ... */ };

template<character-type T>
  using from_chars_result_t = one of the result types above;

// pre-existing overload:
constexpr from_chars_result
  from_chars(const char* first, const char* last, integer-type& value, int base = 10);
// new function template:
template<character-type T, integer-type-concept U>
  constexpr from_chars_result_t<T> from_chars(T* first, T* last, U& value, int base = 10);

// same approach for floating-point types:
from_chars_result from_chars(const char* first, const char* last,
                             floating-point-type& value,
                             chars_format fmt = chars_format::general);
template<character-type T, floating-point-type-concept U>
  from_chars_result_t<T> from_chars(T* first, T* last, U& value,
                                    chars_format fmt = chars_format::general);

また、この提案では追加されるwchar_tオーバーロードのstd::to_charsを用いるようにstd::formatの規定を修正することも提案しています。std::formatはすでにwchar_tをサポートしていますが、その規定でもstd::to_charsが使用されています。これを額面通りに実装するとstd::to_charsの出力をwchar_t文字範囲に書き込むことになりますが、このような実装は存在しない事を暗に仮定してもいます。この提案により、std::to_charsがほぼそのまま使用できるようになります。

SG16による初期レビューではこの提案の方向性(std::from_chars/std::to_charsの両方で4つの文字型のサポートを追加)におおむねコンセンサスが取れています(それでも反対意見はあったようです)。

P3878R0 C++26 Contracts are not a good fit for standard library hardening

P3878R1 Standard library hardening should not use the 'observe' semantic

標準ライブラリの堅牢化モードは終了セマンティクスで評価されることを必須とする提案。

C++26から導入される標準ライブラリの堅牢化モードは、Contractsの枠組みの中で標準ライブラリの一部の関数呼び出しにおいて事前条件をチェックするモードです。

Contractsの仕様では、アサーションがチェックされてからどうするか(特に終了するかしないか)については評価セマンティクスというもので主に指定されます。評価セマンティクスには終了セマンティクス(enforce, quick enforce)と非終了セマンティクス(ignore, observe)の大きく2種類があります。

現在の堅牢化モードでは少なくとも契約アサーションの条件がチェックされるセマンティクス(enforce, quick enforce, observe)が選択されることは規定しているものの、どのセマンティクスを選択するかは実装に委ねています。

この提案では、堅牢化モードにおける評価セマンティクスとしてobserveを禁止し、確実に終了セマンティクスで評価を行うようにすることを提案しています。

主な理由は、observeは契約違反を検出しても終了せずに継続するため、標準ライブラリの事前条件チェックにおいては確実に未定義動作に陥るためです。このような振る舞いは堅牢化(hardened)モードという名前にふさわしくないため、堅牢化モードでは禁止すべきという主張です。

この提案はobserveのユースケースを軽視しているのではなく、堅牢化モードという名前を名乗るべきではないとするもので、実装が「チェックされるが堅牢ではない(checked but not hardened)」モードを提供することには反対していません。

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

P3880R0 Make subspan aware of compile-time constants

spanのメンバ関数を、コンパイル時定数を引数として取ることができるようにする提案。

C++26では、std::cwによってコンパイル時引数を比較的簡単に実現できるようになりました。この提案は、これを用いてstd::span.subspan()などのサイズやインデックスの指定をコンパイル時定数によって指定できる様にしようとするものです。

int arr[42] = {};
std::span sp(arr);                                     // span<int, 42>
auto first5 = sp.first  (std::cw<5>);                  // span<int, 5>
auto last5  = sp.last   (std::cw<6>);                  // span<int, 6>
auto sub    = sp.subspan(std::cw<2>, std::cw<9>);      // span<int, 9>

対象は.first(), .last(), .subspan()の3つです。

これらの関数は現在実行時引数を取るため、静的エクステントを持つspanであっても動的エクステントのspanしか取り出せません。また、境界チェックを行おうとしても実行時にしか行えません。

コンパイル時定数を取れるようになれば、可能な限り静的エクステントの情報を保持できるようになります。

また、これによりstd::mdspanとの一貫性も高まります。std::mdspanに対するstd::submdspan()では、そのスライスの指定などに整数定数だけではなくstd::cwのようなコンパイル時定数を使用できるようになっていますが、std::spanではそうなっていないためです。この目的のために、この提案において使用可能なコンパイル時定数とはstd::cwに限りません(std::mdspanと同様にintegral-constant-likeという説明専用コンセプトで指定されています)。

P3881R0 Forward-progress for all infinite loops

全ての副作用の無い無限ループを未定義動作ではなくする提案。

C++では副作用のない(=終了の見込みがない)無限ループは未定義動作とされています。これはマルチスレッドプログラムにおいてプログラムが終了に向かって進行することを保証するための規則(forward progress guarantees)であり、この未定義動作を利用してコンパイラはそのようなループが終了することを仮定した最適化を行うことができます。

この自由度はコンパイラに最適化機会を提供する一方で、プログラマには不便を強いています

  • 不可解な最適化の可能性
    • 副作用の無い無限ループを削除するためにnullチェックなどの重要なコードが削除される可能性がある
  • UBを回避するための冗長コードの必要性
    • volatile変数の操作やyieldの挿入など

また、コンパイラの実装に対してもC++特有のこのようなループセマンティクスを実現するための実装を維持する負担を強いています(例えばLLVM のmustprogress)。C/Rustではこのような規定は存在しないため、無限ループの扱いおよび最適化のためのインフラがC++だけ別に必要になる部分があります。

このようなセマンティクスの違いはRustとC++およびCの間で一貫しない最適化結果をもたらし、相互運用性を妨げます。

肝心のコンパイラの最適化についても、ほとんど利用されていないどころか、利用しない方がプログラムのパフォーマンスが向上する可能性があることが示されています。

これらのことからこの提案では、副作用の無い無限ループを未定義動作とするのをやめて、単にwell-definedにすることを提案しています。

現在次のようなプログラムは未定義動作となっていますが、この提案後はそうではなくなります。

int main(void) {
  bool True = true;
  while(True);
  return 0;
}

次のプログラムを実行しても終了したりUBが発生したりしません。

int main() {
  while(true);
  *nullptr;
  return 0;
}

次のようなマルチスレッドプログラムでは、abort()が呼ばれるとは限らなくなります。

#include <atomic>
#include <thread>

std::atomic<bool> flag = false;

void thread0() {
  flag.store(true);
  abort();
}

void thread1() {
  flag.wait(false);
  bool True = true;
  while(True);
}

int main() {
  std::jthread t0(thread0);
  std::jthread t1(thread1);
  return 0;
}

このサンプルのように、いずれかのスレッドが実行ステップを実行しない場合でも、実装は弱い並行進行保証(weakly parallel forward progress)を提供することができます。これにより、現在未定義動作となっているコード(副作用の無い無限ループ)は未定義動作ではなくなります。また、この提案の後も現在未定義動作ではないコードの動作は変更されません。

P3883R0 A Proposal for a Boolean Flip Operator in C++

bool値を反転させる演算子の提案。

bool値を反転させるためにはflag = !flag;の様な一文を書かないとなりませんが、変数名が2回現れるなど冗長になります。

一方、このようなbool値の反転はboolフラグによって状態を管理するような場所で頻出するため、使用したいシーンは多岐にわたります。

この提案では、これをインクリメント演算子などのようにin-placeかつ簡潔に行うための新しい演算子として、後置~を提案しています。

bool flag = true;
flag~; // flag = !flag; と同等

この演算子は次のような特徴を持ちます

  • 後置演算子
  • オペランド対象はboolの左辺値のみ
    • 右辺値に対しては使用不可
  • 式の型はvoid
    • if (flag~)の様な紛らわしい使用を回避するため

前置~は整数型に対するビット反転演算子としてすでに競合していますが、後置~は現在無効な構文となっているため後方互換性を保ったまま導入することができます。また、後置であることによって後置インクリメント/デクリメント演算子との動作が一貫し、in-placeで値を変化させていることが明確になります。

P3884R0 Slides for P3505R2 Fix the default floating-point representation in std::format

P3505R2の紹介スライド。

P3505R2で提案されているstd::formatにおける正しい浮動小数点文字列化における問題やその背景、パフォーマンスへの影響などが詳しく説明されています。

P3885R0 Add a formatter for std::error_category

std::error_categoryをフォーマット対応させる提案。

現在のところ、std::error_categoryを直接フォーマットや出力する方法がありません。

// どちらもng
std::cout << std::generic_category();
std::print("{}", std::generic_category());

P3395で解決されたものの、std::error_category::name()にはエンコーディングが指定されていないという問題もありました。

この提案は、std::format()/std::printにてstd::error_categoryを直接扱えるようにしようとするものです。

std::print("{}", std::generic_category());
std::print("[{:>10}]\n", std::generic_category());
generic
[   generic]

std::error_categorystd::string_viewのフォーマッタを利用してフォーマットされるため、文字列と同じオプションが使用可能です。

iostreamに対する対応は行われていませんが、std::format()を通して出力することは可能になります。

P3886R0 Wording for AT1-057

契約違反ハンドラの置換がサポートされているかどうかをチェックする方法を提供する提案。

C++26 Contracts仕様では契約違反ハンドラの置換がサポートされているものの、そのサポートは実装定義とされています。とはいえ、実装がサポートしているかは文書を当たるしかなく、コードからそれを調べる方法はありません。サポートされていない場合にコンパイルエラーになる保証はなく、ほぼ未定義動作に近い扱い(ill-formed, no diagnostic required)になります。

#if __cpp_contracts 
  #include <contracts>

  void handle_contract_violation( 
    const std::contracts::contract_violation & 
  ); // ‼ IFNDR if not replaceable  

#endif

この提案では、これを回避するために、違反ハンドラの置換可能性を判定するための機能テストマクロを提供する様にするものです。提案しているのはライブラリマクロの__cpp_lib_replaceable_contract_violation_handlerです。

#if __cpp_contracts 
  #include <contracts> 

  #if __cpp_lib_replaceable_contract_violation_handler 
 
    void handle_contract_violation( 
      const std::contracts::contract_violation & 
    ); // ✅ guaranteed to be well-formed 

  #endif 
#endif

__cpp_lib_replaceable_contract_violation_handlerの値が0ではない場合は違反ハンドラの置換がサポートされていることを表しています。

この提案はNBコメントを受けてそのための文言を提供するためのものです。LEWGでは合意が得られたようですが、CWGでの合意を得ることができなかったようです(リジェクトされてはいないようですが)。

P3887R0 Make when_all a Ronseal Algorithm

P3887R1 Make when_all a Ronseal Algorithm

execution::when_allの停止完了に関する仕様を調整する提案。

execution::when_allは指定されたすべての子操作が完了(値・停止・エラー)してから自身も完了を報告する(子操作の完了を待機)するsenderアダプタです。when_allが完了するのはその名の通りwhen_allに指定されたすべての子操作(sender)が完了してからだと思うのが自然ですが、実際にはwhen_allが子操作を開始する前に停止要求を検出した場合にも停止完了をします。

しかもこの場合の停止が行われる際には子操作は一切開始されません。

この早期の停止要求検出機能は、子senderが全て停止完了を行わないことが静的にわかっている場合でも、子senderが全て停止トークンを認識しないことが分かっている場合でも、無条件で実行され、when_allsenderは停止完了チャネルを持ちます。これによって、下流のreceiverに対して停止完了シグナルをサポートすることを暗黙的に要求しています。

これらのことはexecution::when_allという名前から想像できるその責務・動作とは異なり、その名前以上の事を行っており、単一責任の原則や驚き最小化の原則に従っていないと言えます。

この提案は、execution::when_allのこれらの問題を解消して、その名前の通りの予測可能な動作をするようにしようとするものです。

次の変更を提案しています

  • 早期停止検出機能の削除
    • 子操作の開始前に停止要求を確認するのをやめて、停止要求に関わらず常に子操作を開始するように変更する
  • 停止完了チャネルを持つ条件の変更
    • 現在は無条件で値無しの停止完了チャネルを持つが、少なくとも一つの子操作が停止完了チャネルを持つ場合にのみwhen_allも停止完了チャネルを持つように変更する

これによって、execution::when_allはその名前の通りに子操作を開始しその完了を待機するものになり、それ以上の副作用を持たなくなります。

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

P3889R0 A minimal solution for contracts, or, what is an MVP?

よりシンプルなcontracts機能の提案。

この提案ではP2900のContractsは複雑かつ実装定義が多すぎることで、MVPとはいえないとして、真にミニマルなContracts機能を示すものです。

ここでは次の2つの特徴を持つ契約機能を例として示しています

  • 常にチェックされるアサーションが一種類だけある
  • 契約違反ハンドラを設定できる

この提案は厳密にはこのような機能を提案しているわけではないですが、このようなシンプルなものでも想像するよりも多くの人に効果的にサービスを届けることができ、P2900はこのような点を見落としている、と主張しています。

P3890R0 Add description for parallel memory algorithms

未初期化メモリに対するアルゴリズムの並行アルゴリズム版記述を明確化する提案。

<memory>ヘッダに存在する未初期化メモリに対するアルゴリズムには、C++17以降並行アルゴリズムが追加されています。しかし、規格書においてこのことは一部の文章によって存在が示唆されるのみで、具体的な定義がされていませんでした。

この提案は、未初期化メモリに対する並行アルゴリズムの定義を明確化するものです。

  • std::uninitialized_default_construct
  • std::uninitialized_value_construct
  • std::uninitialized_copy
  • std::uninitialized_move
  • std::uninitialized_fill
  • std::destroy

同時に、並行Rangeアルゴリズムについても追加されています。

P3891R0 Improve readability of the C++ grammar by adding a syntax for groups and repetitions

C++の文法規則を指定する構文において、繰り返しとリストを簡潔に表現するための記法を追加する提案。

C++の文法規則を標準内で指定するために使用されている構文記法はあまり機能が多くなく、次のような機能しかありません

  • 連結
    • pp-numberidentifier-continueなど
  • 共用体
  • オプション展開

特に、頻繁に使用されているにもかかわらず繰り返しとリストのための構文が欠けており、これによって半ばお決まりのパターンによる指定がなされています。

declaration-seq:
  declaration declaration-seq(opt)

template-parameter-list:
  template-parameter
  template-parameter-list , template-parameter

この様な指定は読みやすさを損ね、構文規則を膨張させています。

この提案では、繰り返しとリストのための新しい記法を追加することでこのような冗長な表現を置き換えようとするものです。

ここで提案されているのは次の3種類です

  • Xseq
    • seqは下付き文字
    • Xの一回以上の繰り返し
  • Xseq opt
    • seq optは下付き文字
    • Xの0回以上の繰り返し
  • ⟪X yyy⟫
    • Xyyyをグループ化することでopt, seq, seq optをグループに含まれるもの全てに対して適用できる

提案文書より、例(下付きのoptseq optはmarkdownだと再現できないので、()でくくって表現しています)

現在 この提案
compound-statement:
  { statement-seq(opt) label-seq(opt) }
statement-seq:
  statement statement-seq(opt)
label-seq:
  label label-seq(opt)
compound-statement:
  { statement(seq opt) label(seq opt) }
P3149R11 この提案
initializer-list:
  initializer-clause ...(opt)
  initializer-list , initializer-clause ...(opt)
initializer-list:
  initializer-clause ...(opt) ⟪ , initializer-clause ...(opt) ⟫(seq opt)
P3149R11 この提案
identifier:
  identifier-start
  identifier identifier-continue
identifier:
  identifier-start identifier-continue(seq opt)

この提案は純粋にeditorialなものです。

P3892R0 unless_stop_requested

停止要求が発行されていたら処理の開始を無視するsenderアルゴリズム、unless_stop_requestedの提案。

P3887R0(少し上の方)では、when_allが早期の停止要求検出機能を備えていることを問題視し、それを削除しようとしています。その動機付けの中で、早期の停止要求検出機能は単一のアルゴリズムとして簡単に実現できる、として自身の講演をリンクしています。しかし、使用例や実装例などは示されていません。

この提案は、この早期の停止要求検出とその時点での停止完了発行を行うアルゴリズムとして、unless_stop_requestedを追加するものです。

std::executionの停止要求(およびストップトークンの仕組み)は、C++20でstd::jthreadとともに追加された仕組みをベースとして構築されています。このstd::stop_tokenのフレームワークにおいては、停止シグナルのことを停止要求(stop request)と呼んでいます。ここで要求という言葉が採用されている意味は、停止要求が強いものではなく無視される可能性があるものだからです。

停止要求の処理は明確に協調的(cooperative)なものとして設計されており、それを受けて処理を停止するかどうかは要求を受けた側が決めることです。その根拠の一つとして、停止要求の受信とほぼ同時に非同期処理が完了(成功orエラー)した場合、停止要求は無視して本来の成功を返した方が何が起きたかの情報を伝達できることがあります。

これは単一のコンポーネントや小さな処理レベルでは良い選択となりえますが、より大きなシステムの構成要素となった時に停止要求を無視するコンポーネントは無限ループに陥るリスクがあり、システム全体を停止不可能な状態にしてしまいます。

これは標準ライブラリのような汎用的なコンポーネント(フレームワーク)では回避すべきでありそのための仕組みを最初から入れておくべき、という意見もありますが、停止要求のチェックと応答にはコストがかかるため、むしろ汎用的なフレームワークに入れてしまうと、フレームワーク内部の停止要求応答のためのコストはそこから構成されるコンポーネントの数に比例して増大していきます。

std::stop_tokenのフレームワークが任意になっているのはこうした事情からであり、この観点からもP3887R0の主張は理にかなったものと言えます。しかし一方で、ユーザーが簡単に停止要求への対応を行う方法を用意しておくことには特に問題はないはずです。そのような部品を提供しておけば、ユーザーがどの時点で処理の停止要求を考慮するかを完全に制御することができます。

execution::unless_stop_requestedsenderアルゴリズムとしてまさにそのような働きをするもので、senderによるチェーンに挿入しておくことでその処理ステップで停止要求のチェックを行うことを明確化できます。

using namespace ex = std::execution;

ex::scheduler auto sc = ...;

ex::sender auto async_work =
  ex::starts_on(sc, ex::just(10)) | 
  ex::then([](int n) {
    return ...;  // 何か処理
  }) |
  ex::unless_stop_requested | // 停止要求のチェック、停止要求が発行されていたら停止完了
  ex::then([](int n) {
    ...;  // 次の処理、unless_stop_requestedの時点で停止完了していた場合は実行されない
    return n;
  });

P3893R0 The CppCon 2025 Talk on Contracts and CodeQL in Context

CodeQLにおけるアサーションに対する静的解析の実装経験をP2900の肯定のために使用しない事を推奨する文書。

CodeQLはGithubが開発している静的解析ツールです。CodeQLでは最近C/C++の一部のアサートに対する静的解析機能を実装し、それについてCppConでの講演があったようです(この提案はその講演者の方の一人によって書かれたものです)。そして、そのことがC++26 Contractsをめぐる議論の中で言及されることがあったようで、この文書はCodeQLでの経験は概念実証段階にあるためP2900の静的解析サポートの実現可能性を評価する根拠として使用しないように求めるものです。

文書では、統一的なアサーションがあることによってCodeQLがそれを把握するのに役立つ可能性があるとしつつも、P2900の他の側面によってそれ以外の問題がもたらされるため、静的解析ツールの全体的な動作のために有用であるかはまだ分からないとしています。

P3895R0 Slides for P3724R1 - Integer division

P3724R1の紹介スライド。

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

P3724R1で提案されている様々な丸めモードによる剰余計算の必要性や、標準におけるサポート状況、提案の方向性などについて簡単に紹介されています。

P3896R0 Design goals for a contract support facility

Contracts機能の設計目標について説明した文書。

この文書では、C++26の機能凍結前後で急増したP2900のContracts機能への拒否・修正・再設計などの案への反論として、そもそもP2900が何を解決しようとしていたのかについての設計目標を説明するものです。

この文書によれば、C++における契約機能の導入に関する当初の(そして現在も変わっていない)構想は、関数に対する単一のアノテーションによって2種類の異なる正当性検証ツールを可能にするというものでした

  • assertマクロの様なオプショナルな実行時検査
  • 正当性チェック、静的解析

この実現のためには、これらの機能の既存実装において一般的な使用方法から一部妥協する必要がありました

  • アサーションは関数宣言内に記述する必要がある
    • アサーションマクロでは一般的ではない
  • アサーションはbool型に変換可能なC++の式によって記述する必要がある
    • 静的解析向けアノテーションでは一般的ではない

この構想は、C++の進化におけるもっとも重要なガイドラインの一つである、「新しい機能を追加して言語を複雑化する場合は、それが複数の問題を解決できるようにする必要がある」という原則に基づいたものです。

この意味において、Contracts機能に関する提案は既存の慣行(アサーションと静的解析用アノテーションに関する)を単に標準化するだけではなくそれを次のレベルへと引き上げようとしています。すなわち、オプショナルな実行時検査と静的解析の間に相乗効果をもたらすこと目指しています。これには、実行時検査のみのアプローチでは対処する必要のない新たな課題が伴います。

これによるプログラマのメリットは、アサーションを一度記述するだけでそれに基づいて複数の方法で正当性を検査できるようになることです。

また、P2900までの提案ではその設計において考慮されていなかった3つ目のツールが最近(2024年ごろ)報告されました。それは、ライブラリが明示した事前条件が破られた場合にプログラムを確実に終了させる非無効化保証を提供することです。P2900の設計はこれをサポートするように拡張可能であることは確認されているものの、C++26時点ではサポートされていません。

それ以前の設計目標のモデルは、プログラマが既に確認済みである事項を再確認するものとしてアサーションとその検証を利用していますが、この3つ目のツールはライブラリ作成者がアサーションによって事前条件を指定するものの、違反時の動作まで指定してしまっていることで、アサーションがその関数の動作の一部になってしまう懸念があります。このため、以前の2つの設計目標に適合するかどうか非常にきわどいラインにあります。

この3つ目のツールを重視するために、設計の妥協案として挙げられている「保証された終了を伴うアサーションを提供するだけ」というものは最初の2つの設計目標に向けた一歩ではありません。なぜならそれは単一の目的を達成するためだけのものでしかなく、専用の言語機能を導入しても既存のアサーションマクロ以上のものは得られないからです。

そのためならば、せいぜいチェックと違反時の確実な終了、そしてグローバルな違反ハンドラを提供するためのライブラリ関数があれば十分です。pre post contract_assertは不要です。これはContracts機能ではありません。

3つ目のツールを重視するにしても、最初の2つの設計目標を軽視すべきではありません。

提案されている再設計案にはこれとは別に、P2900 Contractsの持っている機能を細分化して言語機能とし、それを組み合わせてContracts機能を構成しようとするものがあります。この方法ではアサーションの評価についてを細かくユーザーが制御することができますが、一方で契約アサーションを記述する普遍的な表記法が導入されないため、最初の設計目標の1つである静的解析のためのアノテーションの提供ができなくなります。

なお、この提案における静的解析とは、プログラムの入力によってアサーション違反が発生する可能性があるかどうかを判定する解析を指しており、その用途においてアサーションはアノテーションでしかなく、評価セマンティクスは関係ありません。

P3897R0 Slides for P3776R1 - More trailing commas

P3776R1の紹介スライド。

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

P3776R1の提案内容の紹介と、懸念点の説明がされています。

P3898R0 Slides for P3793R0 - Better shifting

P3793R0の紹介スライド。

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

P3793R0で提案されいている整数のビットシフトを行うライブラリ関数のモチベーションや機能性などについて簡単に説明されています。

P3899R0 Clarify the behavior of floating-point overflow

浮動小数点数演算におけるオーバーフロー時の未定義動作を修正する提案。

CWGの議論の中で、浮動小数点数型の演算中の無限大に関する次のようなことがWell-formedであるかが合意が取れていないことが分かりました

  • FLT_MAX * 2のような、無限大(inf)を発生させる演算
  • infinity() + 1のような、無限大を伝播する演算
  • numeric_limits<T>::infinity()のように、無限大を取得すること

まず、浮動小数点数演算のうち未定義動作となるものについては [expr.pre]/4 で規定されています

If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.

[Note: Treatment of division by zero, forming a remainder using a zero divisor, and all floating-point exceptions varies among machines, and is sometimes adjustable by a library function. — end note]

式の評価中に結果が数学的に定義されていない場合、あるいはその型で表現可能な値の範囲外である場合、動作は未定義となる。

[注記:ゼロ除算の扱い、ゼロ除数を用いた剰余演算、およびすべての浮動小数点例外処理はマシンによって異なる場合があり、場合によってはライブラリ関数によって調整可能である。――注記終了]

2つの有限数間の演算はゼロ除算を除いて数学的に定義されていますが、その結果が表現可能な値の範囲に含まれない場合はその動作は未定義となりえます。この表現可能な値の範囲は[basic.fundamental]/13にあります(CWG2723によって最近追加されたようです)。

The minimum range of representable values for a floating-point type is the most negative finite floating-point number representable in that type through the most positive finite floating-point number representable in that type. In addition, if negative infinity is representable in a type, the range of that type is extended to all negative real numbers; likewise, if positive infinity is representable in a type, the range of that type is extended to all positive real numbers.

[Note: Since negative and positive infinity are representable in ISO/IEC/IEEE 60559 formats, all real numbers lie within the range of representable values of a floating-point type adhering to ISO/IEC/IEEE 60559. — end note]

浮動小数点型で表現可能な値の最小範囲とは、その型で表現可能な最も小さい負の有限浮動小数点数から、最も大きい正の有限浮動小数点数までの範囲を指す。さらに、負の無限大がその型で表現可能な場合、その型の表現可能な範囲はすべての負の実数に拡張される。同様に、正の無限大が表現可能な場合、その型の表現可能な範囲はすべての正の実数に拡張される。

[注記:ISO/IEC/IEEE 60559規格に準拠する浮動小数点形式では、負の無限大と正の無限大が表現可能であるため、ISO/IEC/IEEE 60559規格に準拠するすべての浮動小数点型において、実数全体がその表現可能な値の範囲内に含まれる。――注記終了]

翻訳はすべてPLamo翻訳を使用。

この定義の解釈について、表現可能な値の範囲に無限大とNaNが含まれるかどうかによって2つの解釈があるようです。これにより次の3つの相反する見解が存在しています

  1. 無限大は表現可能な値の範囲に含まれるため、無限大を生成する浮動小数点数演算のオーバーフローはWell-formed
  2. [expr.pre]/4 で結果が表現可能な値の範囲内にあると述べているのは、丸め処理が行われる前の数学的な結果を指しており、丸め処理によって結果が無限大になる前の状態を指している。実数はすべて表現可能な値の範囲内にあるため、浮動小数点数演算のオーバーフローはWell-formed
  3. [expr.pre]/4 は、式の評価中に発生するあらゆる種類の結果(無限大も含む)について言及している。実数が表現可能な値の範囲内にあるかもしれないが無限大はそうではないため、無限大が存在する場合でも浮動小数点数演算のオーバーフローは未定義動作。
    • [expr.pre]/4 の注記は浮動小数点例外の処理はマシンによって異なると述べており、これは浮動小数点オーバーフローを未定義動作として扱うことの設計根拠を示している

また、CWG2168ではSG6の見解として、無限大を生成する算術演算は定数式では許可されないが、std::numeric_limits<T>::infinity()の使用は問題ない(ただし部分式として使用できない)、というガイダンスを示しています。

CWG2723によって追加された表現可能な値の範囲の定義とこのガイダンス、および[expr.pre]/4の記述は一貫していないようです。この提案は、SG6のガイダンスに準拠しつつCおよび現在constexpr指定済みの<cmath>関数との整合性を保ちながら、合意が得られる最小の変更を行うことによって、この問題を解決しようとするものです。

ここで提案されていることはおおむね、FE_INEXACT例外を除いた浮動小数点例外を発生しない演算のみをWell-definedとして、定数式でも使用できるようにすることで、次のような動作を提案するものです

  • 有限値のオペランドから無限大/NaNを生成することはWell-formedだが、定数式では実行できない
  • 無限大またはquiet NaNを伝播させる演算はWell-formedであり、定数式で実行可能
  • 結果を表すために無限大/NaNが利用できない場合、オーバーフローは未定義動作

GCC15は提案しているこのような振る舞いを実装している唯一の実装であるようです。

冒頭で挙げた例では次のようになります

  • FLT_MAX * 2はWell-formedだが、定数式では実行できない
  • infinity() + 1はWell-formedであり、定数式で実行可能
  • numeric_limits<T>::infinity()はWell-formedであり、定数式で実行可能
    • 部分式としても使用できる

この提案における設計変更点は、浮動小数点数演算でオーバーフローが発生した場合、無限大が表現可能な場合は無限大が返されるようになることが規定される(ただし、定数式で実行可能ではない)点のみです。FLT_MAX * 2のようなコードが定数式であると仮定していたコードはコンパイルエラーになります。

P3902R0 Against implicit conversions for indirect

P3902R1 Against implicit conversions for indirect

P3902R2 Against implicit conversions for indirect

std::indirect<T>へ参照型T&への暗黙変換を追加すべきではないとする提案。

std::indirect<T>はC++26で追加されたライブラリ機能で、ヒープ上に確保されたオブジェクトに値セマンティクスを付与するものです。すなわち、std::indirect<T>はポインタを介して対象のオブジェクトを所有し、std::indirect<T>をコピーするとそのオブジェクトもコピーされ、constアクセスはconst性を対象オブジェクトまで伝播させます。

また、std::indirect<T>をムーブするとムーブ元のstd::indirect<T>は空状態になります。C++26仕様では、std::indirect<T>が空ではない事を事前条件としているのは->*のみであり、このことはstd::unique_ptrstd::optionalなどと整合しています。

アメリカから提出されたNBコメント(US 77-140)では、std::indirect<T>T&に変換できるようにすることで使用例を簡素化でき、reference_wrapperに近くなる、ということが提案されています。

この提案はこれに反対するものです。主な理由は次のものです

  • reference_wrapperに近くすることがなぜ望ましいのかは説明されていない
  • std::indirect<T>reference_wrapperよりもstd::unique_ptrstd::optionalに近いクラス型
    • インターフェースはそれらに寄せたものにすべき
  • 提案されている暗黙変換演算子は空状態でないという事前条件を持つものの、標準化の最終版で未定義動作の発生しうる状況を増やすことは回避したい

ただし、使用経験と実装経験を十分に積んだ後で再検討することはでき、将来望まれた場合の反対意見ではないとしています。

この提案は2025年11月の全体会議で承認されたようです(ただし何か変更を伴うものではありません)。

P3904R0 When paths go WTF: making formatting lossless

std::filesystem::pathのフォーマットにおいて情報の損失が発生しないようにする提案。

C++26 P2845R8でstd::filesystem::pathのフォーマットサポートが追加されています。この提案では、Windowsにおいて実行時コードページではなくリテラルエンコーディングに基づいてフォーマット先のエンコーディングを決定することで、Windowsにおけるエンコーディングの問題に対処しています。

しかし、それも完全ではなく、Windowsにおけるパス文字列としてペアになっていないサロゲート(ペアの片割れ)が現われる場合に正しくエンコードできない問題があります。

auto p1 = std::filesystem::path(L"\xD800"); // a lone surrogate
auto p2 = std::filesystem::path(L"\xD801"); // another lone surrogate
auto s1 = std::format("{}\n", p1); // s1 == "�"
auto s2 = std::format("{}\n", p2); // s2 == "�"

p1p2は異なる文字列ですが同じ文字列(U+FFFD REPLACEMENT CHARACTER)にエンコーディングされてしまっています。この問題はPOSIX環境では起こらないためプラットフォーム間での一貫性がありません。また、これによりpathと文字列の往復変換が不可能になります。

この提案では、このような場合におけるデータ損失を防ぐために、filesystem::pathのフォーマットにおいては不正なUTF-16文字列をWTF-8を使用してフォーマットすることを提案しています。

WTF-8(Wobbly Transformation Format − 8-bit)はUTF-8のスーパーセットであり、16bitコードの任意のシーケンスをUTF-16として不正な場合でも損失無く表すことができます。

auto p1 = std::filesystem::path(L"\xD800"); // a lone surrogate
auto p2 = std::filesystem::path(L"\xD801"); // another lone surrogate

// この提案後の出力
auto s1 = std::format("{}\n", p1); // s1 == "\xED\xA0\x80"
auto s2 = std::format("{}\n", p2); // s2 == "\xED\xA0\x81"

このようにフォーマットされた文字列からは、このことを仮定する文字列変換によって再び同じ表現のpathに戻すことができます(この変換は後程提案されるとのことです)。

ただし、WTF-8はユニコード標準の規定するエンコーディングではないため。std::printlnによって出力しても上記の変換後文字列は正しく出力されません。

std::print("{}\n", std::filesystem::path(L"\xD800"));

P3905R0 C++ Standard Library Ready Issues to be moved in Kona, Nov. 2025

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

P3906R0 C++ Standard Library Immediate Issues to be moved in Kona, Nov. 2025

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

↑(P3905)との違いは、事前にイシューとして公開されていなかったもののその修正の妥当性が(会議の間に)確認されて準備ができたものをまとめた文書の様です。

P3907R0 Waving more ::result_type goodbye

std::copyable_functionから::result_typeを削除する提案。

C++17では呼び出し可能型のインターフェースの一部となっていたresult_typeが非推奨化(P0090R0)され、C++20で削除されました。その際、std::functionのみで後方互換のために残していました。

C++23~26でstd::functionのファミリが増えた際、それらのものにもresult_typeが追加されていました。std::function_refにはなかったようですが、ライブラリIssueとして追加することが促されています。

この提案はそれに対して反対し、result_typeのような情報を取得するための専用の型特性を別に追加することを提案しています。

ただし、C++26の作業フェーズは終わっていることもあり、ここで提案しているのはC++26で追加されたstd::copyable_functionからresult_typeを取り除いておくことのみです。これにより、ひとまずresult_typeの使用を抑制しつつ、そのほかの措置を検討することができます。

std::move_only_functionはC++23で追加されたものであるため、ここでは手を付けていません。

この提案がresult_typeの復活に反対している理由は、P0090の主張と同じく、ラムダ式を始めとするCallable型の多くがこれを備えていないことにより、一貫性が無いためです。

ただし、呼び出し可能オブジェクトの戻り値型を取得したい場合というのは確かにあるため、代わりにより一般的に呼び出し可能型の情報にアクセスするための型特性std::signature_traitsstd::callable_traitsを追加することを検討しています。

std::signature_traitsは関数型を受けて、その関数型のもつ情報にアクセスするためのメンバを備える型特性です。

// signature_traitsの説明用定義例
template<...>
struct signature_traits<R(Args...) cv_qual ref_qual noex> {
  using unqualified = R(Args...);
  using return_t = R;
  
  template<size_t i>
  using param_t = Args...[i];
  
  static constexpr size_t arity = sizeof...(Args);
  static constexpr bool is_noexcept = noex;
  
  template<class U>
  using cv = U cv_qual;
  
  template<class U>
  using ref = U ref_qual;
};

これはあくまで説明のための定義の例であり、細部が正確ではありません。

std::callable_traitsは、呼び出し可能型から関数型を取得してstd::signature_traitsにアクセスする型特性です。

// ラムダのクロージャ型など
template<class T>
  requires is_class_v<T>
struct callable_traits<T> : _callable_traits_impl<^^T::operator()> {};

// 関数型
template<class F>
  requires is_function_v<F>
struct callable_traits<F*> : signature_traits<F> {};

// copyable_function
template<class S>
struct callable_traits<copyable_function<S>> : signature_traits<S> {};

// reference_wrapper
template<class T>
struct callable_traits<reference_wrapper<T>> : signature_traits<T> {};

このような型特性の方がより広く使用することができ、一貫しています。

P3908R0 constexpr from_chars<float> / to_chars<float>

<charconv>の浮動小数点数変換をconstexpr対応する提案。

<charconv>にあるto_chars/from_charsのうち、整数型の変換についてはすでにconstexprになっていますが、浮動小数点数の変換はどちらもconstexprではありません。

この提案では、浮動小数点数型のto_chars/from_charsオーバーロードをどちらもconstexprにする提案です。

浮動小数点数型のto_chars/from_charsオーバーロードがconstexprではなかったのは、定数式でそのような変換を実行する実装経験が無かったためです。しかし近年、定数式で実行可能かつ高速な浮動小数点数と文字列の変換アルゴリズムがいくつか実装されています。

浮動小数点数型のto_chars/from_charsオーバーロードをconstexprで実装できることは実証されているため、この提案はそれを提案しています。

P3909R0 Contracts should go into a White Paper - even at this late point

C++26 Contractsをホワイトペーパーとして出荷すべきとする提案。

P2900のContracts機能は言語にハードコートされた部分が多すぎることで柔軟性や拡張性に欠けており、C++29スコープのラベル機能を考慮しても言語仕様及び委員会による設計への依存が多すぎることで、ユーザーの多様なニーズに応えられておらず、今後も応えられないと主張しています。

例えば、ユーザーのニーズを満たすために何か変更をくわえようとすると、通常のWG21のプロセスを経る必要があり、これはハードルが上がるとともに時間がかかります。

この提案では、AdaのAssertプラグマを参考にした、シンプルでユーザーが任意に拡張可能な実装方法を概説し、それを検討するためにContractsをホワイトペーパーに移すことを提案するものです。

この提案が異議を唱えているのは主にアサーションのセマンティクス(評価セマンティクスと違反ハンドラの動作)がハードコートされている点です。

この提案では、任意のセマンティクスは次の2つのビルディングブロックを提供することで実装可能だとしています

  • ユーザーが設定した評価セマンティクスを問い合わせる機能
  • プログラムから契約違反を発生させる方法

これを用いてquick enforceは次のように実装できます

  • 述語はユーザーが評価セマンティクスをどう設定しているかを問い合わせることができ、ユーザーがquick enforceを設定していた場合は述語の評価結果がfalseだった場合に直ちにabort()/__builtin_trap()/__fastfail()を呼び出す
    • ユーザーが契約違反時に強制終了する事を想定している場合、ここでのP2900との違いは違反ハンドラの呼び出されない点のみ
    • ラベル機能によって同等の事が実現される予定であるため、これを実装できない理由はない

ここでの述語とは、契約条件とは異なるものの様です。この述語をユーザーが関数や関数オブジェクトの形で記述して、それを契約アサーションの評価時に使用できるようにすることでカスタマイゼーションポイントを提供し、それを通じてアサーションのセマンティクスを柔軟に制御しようとするものの様です。

この提案によるこのような設計であれば、すべてのそしてそのほかに必要となるアサーションのセマンティクスはライブラリ、あるいはユーザーサイドで実装し提供可能となります。実際に、現在の4つの評価セマンティクスとそこから派生するいくつかの評価セマンティクスについて、すべて標準でハードコートする必要がないことを示しています。

ラベルでもほぼ同様のことが達成できる見込みですが、次のような点で劣っているとしています

  • 委員会による設計の枠内に収まっている
    • コア言語に組み込まれた機能であり、委員会の想定外の方向に拡張しようとするとコストと時間がかかってしまう
  • ラベルによるカスタマイズはアルゴリズムのフックでその動作をカスタマイズするようなもので、アルゴリズムそのものを変更できない
    • 例えば、契約条件式からの例外送出を契約違反に変換する/しない
  • ラベルを使用するためには複雑で新しい専用のコードを書く必要がある
    • この提案の方法は、関数/関数オブジェクトを書くだけでカスタマイズできる
  • ラベルは低レベルなビルディングブロックではなく、ラベルの下にあるものを変更できない

P2900を急いでISに移行することで、このような代替案の可能性が閉ざされてしまい、少なくとも著者の方はWG21がその準備ができているとは言えないとしています。P2900は紙の上で設計され実証も展開もほぼされておらず、貴重な代替案も試さずに議論だけで却下しており、実際のユーザーにとって不十分なものになっている、としています。

P3910R0 Improving safety of C++26 contracts

Contractsの評価セマンティクスを名前マングリングに含めるようにする提案。

C++26 Contracts(P2900)の仕様に対して色々と意義が提出されており、その中の一つに複数の翻訳単位がリンクされる際にすべての翻訳単位で評価セマンティクスを一貫させる方法がない、というものがあります。例えば、mainの翻訳単位をenforceセマンティクスでコンパイルしていても、ignoreセマンティクスでビルド済みのライブラリをリンクする場合にそのライブラリの関数においては契約チェックが行われず、これを検出したり制御する方法がありません。

この提案はこれに対する解決の方法として、契約アサーションが指定されている関数の名前マングリング処理において、選択された評価セマンティクスを含めるようにするという方法を提案しています。

名前マングリングに評価セマンティクスを含めることによって、ある関数に対して評価セマンティクス毎にコンパイルされた関数のコードが同じオブジェクトファイル内で共存可能になります。これによって、インライン展開されない関数であっても、使用場所(呼び出し側)の評価セマンティクス指定に対応した関数が呼び出すことができるようになります。

オブジェクトファイルにおいて4つの評価セマンティクスに対応した関数の実体が含まれていれば、それをリンクする翻訳単位において指定されている評価セマンティクスによってコンパイルされた関数のシンボル名はその4つのいずれかに対応することになります。これによってビルド済みライブラリであっても、主たる翻訳単位の設定に応じた評価セマンティクスの指定が可能になります。

もしリンク先のオブジェクトファイルにおいて指定された評価セマンティクスによるシンボル名が見つからない場合はコンパイルエラーとなりますが、これは評価セマンティクスの指定が混合されている(必ずしも4つ全ての評価セマンティクスによるシンボルが含まれない)場合のビルドにおいてリンカエラーを引き起こすことになり問題となります。

そこで、リンカもContractsの評価セマンティクスに関する知識を持っておくようにすることでこれを解消します。主体となっているオブジェクトにおいて指定されている評価セマンティクスに該当するシンボルを見つけられない場合、リンカは別の評価セマンティクスを選択してリンクを続行できるようにします(このリンク時の評価セマンティクス選択は現在のContracts仕様の範疇です)。その際、リンカはコマンドライン引数として優先順位付きの評価セマンティクスリストを受け取り、このリストの順番で評価セマンティクスを変更して同じ関数のシンボルを探索するようにします。リストをすべて探索してもシンボルが見つからない場合にリンカエラーを発します。

このリンカに渡す評価セマンティクスリストは必ずしも既知の評価セマンティクスのすべてを網羅している必要はなく、例えばignoreセマンティクスだけを除外することで暗黙的にignoreセマンティクスが選択されることを防止することができます。

ここまでの方法だと、インライン関数のアドレス等価性について問題があります。すなわち、インライン関数のアドレスは一つのプログラム内では翻訳単位を跨いで同一である必要があるものの、異なる評価セマンティクスでコンパイルされた翻訳単位ではそのアドレスが異なる場合があります。

/// ヘッダファイル
inline int f(int n)
  pre(n != 0)
  post(r: 0 < r)
{
  ...

  contract_assert(n < 100);

  ...
}

/// 翻訳単位A、enforceセマンティクスでコンパイル

void call() {
  auto* pf = f; // f_enforceのようなシンボル名のアドレスになる
}

/// 翻訳単位B、observeセマンティクスでコンパイル

void call() {
  auto* pf = f; // f_observeのようなシンボル名のアドレスになる
}

このf_enforcef_observeのシンボル名は契約アサーションの動作が異なるため別の実体となり、翻訳単位AとBの間でpfのアドレスは一致しません。このような状況は現在のC++では未定義動作となります。

この提案ではこの解決のために、コンパイラがこのようなアドレスの取得に対してundefinedシンボル名を発行するようにすることを提案しています。undefinedシンボル名はコンパイラによってこのような場合(契約がなされたインライン関数のアドレス取得時)に発行されるものの対応する実体コードは生成されません。この場合、先ほどのようにリンカは渡された評価セマンティクスリストの一番上にある最も優先度の高いセマンティクスを選択し、そのシンボルのアドレスをundefinedシンボル名のものの代わりに入れるようにします。

/// 翻訳単位A、enforceセマンティクスでコンパイル、リンカの評価セマンティクスリストは"enforce < observe < ignore"とする

void call() {
  auto* pf = f; // f_undefinedのようなシンボル名のアドレスを指定するコードをコンパイラが生成
                // f_undefinedシンボルは未定義かつ契約あり関数のシンボルなので、
                // リンカは評価セマンティクスリストの一番上にあるセマンティクスに対応するシンボルを選択
                // 結果、f_enforceのようなシンボル名のアドレスになる
}

/// 翻訳単位B、observeセマンティクスでコンパイル、リンカの評価セマンティクスリストは"enforce < observe < ignore"とする

void call() {
  auto* pf = f; // f_undefinedのようなシンボル名のアドレスを指定するコードをコンパイラが生成
                // f_undefinedシンボルは未定義かつ契約あり関数のシンボルなので、
                // リンカは評価セマンティクスリストの一番上にあるセマンティクスに対応するシンボルを選択
                // 結果、f_enforceのようなシンボル名のアドレスになる
}

リンカによるこのような処理によって、インライン関数のアドレスは評価セマンティクスの指定に関わらずすべての翻訳単位で同一に保たれます。

ただしこの処理が行われる場合、評価セマンティクスの選択が再び不透明になります。とはいえ、ユーザーが指定している評価セマンティクスリストの一番優先度の高いものが選択されることになるので、結果として選択されたセマンティクスはその翻訳単位ビルド時に指定されていた評価セマンティクスと同等かより厳密なものになるはずです。

このような方法によって、インライン関数がインライン展開されているかに関わらず同じ評価セマンティクスで評価されることや特定の評価セマンティクスをプログラム中で厳密に指定することなどが達成でき、P2900仕様に対して提出されている意義のうち1つが解消されます。

ビルド済みライブラリを配布する場合、特定の一つの評価セマンティクスに対応するシンボルだけを生成しておくのではなく、既知のすべての評価セマンティクスに対応するシンボルをすべて含めるようにして配布しておくと、利用者の指定に応じた評価セマンティクスの選択が可能になります。これは、CUDAのfat binaryとしてすでに実用化されているアプローチと同等です。

このアプローチをとる場合でもまだ問題はあり、extern "C"関数やmain()はこのような評価セマンティクスに応じたシンボル生成を行うと必ず多重定義エラーが発生する事です(似た問題がグローバル変数の初期化においてもあります)。特にmain()の場合は実体が一つしか存在できないことで、そのmain()をコンパイルした評価セマンティクス指定によって実行ファイル全体の評価セマンティクス指定が決まってしまいます。これにより、プログラムで局所的に評価セマンティクスの指定を変更するようなことが妨げられます。

この提案ではこの解決としていくつかの方法を提示しています

  • これらの特殊なエンティティに対して複数のシンボル生成を抑制するオプションを追加する
  • そのソースファイルをコンパイルする評価セマンティクスのリストを指定するオプションをコンパイラに追加する
    • コンパイラはコンパイルの際、そのリストにある評価セマンティクスに応じたシンボルを生成する
    • が、extern "C"関数やmain()などはリストの先頭のセマンティクスに対してのみコードを生成する

ただしどちらの方法でもmain()関数をコンパイルする際の評価セマンティクス指定がプログラム全体に影響してしまう問題を回避できません。この対策として関数呼び出し時に評価セマンティクスを指定する方法を提案していますが、構文等の設計に議論が必要なためそれは将来の標準化における課題としています。ここでは暫定措置として、evaluation_semantic_cast<Semantics>()による明示的な構文を提案しています。

// 別の翻訳単位で1つ以上の評価セマンティクスでコンパイルされたextern関数
void myFunction();

void callMF() {
  // callMF()をコンパイルする評価セマンティクスに対応する関数を呼び出し
  myFunction();

  // enforceセマンティクスでコンパイルされたcallMF()を呼び出し
  evaluation_semantic_cast<enforce>(myFunction)();
  
  // 評価セマンティクスの選択をリンカに委ねる
  evaluation_semantic_cast<undefined>(myFunction)();
}

提案ではさらに、仮想関数の場合のアプローチやモジュールの場合、可能な最適化などについて検討されており、筆者の方のコードベースにおけるP2900の現状の問題の程度の報告などがされています。

この方法を実現するためには、コンパイラだけではなくリンカにも変更が必要となります。C++標準規格としてはリンカの存在を特に取り扱ってはいないものの、リンカに対する影響については慎重なところがあります。この提案では、リンカへの影響を気にするあまり言語機能の進化を妨げた例が過去にあり、この問題についても同様(このままだと同様になる)としています。

P3911R0 RO 2-056 6.11.2 [basic.contract.eval] Make Contracts Reliably Non-Ignorable

契約アサーションの評価セマンティクスから、ignoreの選択を制御できるようにする提案。

契約アサーションの評価セマンティクスが集中管理されていることによって、ビルド構成やプロジェクトポリシーによって容易にignoreセマンティクスに設定されてしまう可能性があるため、Contracts機能を信頼性・モジュール性の低いものにしてしまうとして、この提案では無視されないように制御できるようにすることを提案しています。

ここでは次の4つの案を提示しています

  1. pre!の様な構文によって、必ず終了セマンティクスで評価されるアサーションを指定できるようにする
  2. ignoreセマンティクスを削除する
  3. P3400で提案されている完全なプロパティシステム(ラベル指定)を採用する
  4. P2900 Contracts MVPを別の方法で出荷する
    • 1~3のいずれも合意に至らない場合
    • C++29, white paper, TS

この提案では1のpre!を推奨しています。

この提案はルーマニアのNBによるNBコメントと連動した提案の様です。

P3912R0 Design considerations for always-enforced contract assertions

常に評価され破られたら終了する契約アサーションの設計上の疑問点をまとめた文書。

ルーマニアNBのコメントおよびそれを受けてのP3911R0(上の方に解説があります)では、通常のセマンティクス可変なアサーションに対して、常に評価され条件式がfalseになったらプログラムを終了するような契約アサーションをC++26に向けて導入しようとしています。提案ではpre!(expr)のように通常のアサーションに対して!を後ろに付けた構文を提案しています。

EWGはこのNBコメントと提案を受けて、C++26に向けてこのようなアサーション(always enforced assertion)の追加を検討することにしたようです。

この文書では、NBコメント解決のためにこの段階で新しい機能を急いで導入することはWG21の標準化モデルに反しており、C++26WDにおけるContracts機能はこの様なアサーションが無いことを理由に遅延させるべきではないとしています。

しかし、そのうえでEWGがこのような機能を検討するならば、設計空間を慎重に検討し設計選択におけるトレードオフを十分に理解することが重要であるとして、この文書はalways enforced assertionの設計に関しての提言をするものです。

always enforced assertionはC++29で予定されているContractsの機能拡張であるラベル機能(P3400R2)とかなり重複があることが分かっています。これはC++26に導入される予定はなくC++29に対してもまだ検討中ではありますが、SG21において複数回のレビューを受けており、always enforced assertionの様な機能を包含する契約アサーションの拡張機能として現時点で最も検討が進んでいる設計基盤となっています。

この文書ではラベル機能の設計の経験をベースとして、always enforced assertionおよび類似の機能提案が回答する必要のある設計上の疑問点を提示します。

  1. 構文
    1. 既存キーワードに記号を付加する構文にすべきか?その場合どの記号を使用すべきか?
      • !は慣習的に否定を意味するため、認識上の混乱の可能性がある
      • pre!の様な構文は、rustの宣言マクロのような機能のために確保しておいた方が良い可能性がある
      • 今のところ、他の記号について代替案はない
      • 現在の契約構文はCとの互換性もある程度考慮されている
        • 関数マクロで再現可能な構造になっていることで、C側で無効化できる
    2. デフォルトの構文(pre(), post(), contract_assert())はどちらのアサーションを表すべきか?
      • always enforced assertionにデフォルトの構文を割り当てて、無視可能(セマンティクス可変)なアサーションに別の構文を当てるべきという提案があった
      • また、デフォルトを決定できないのならば現在の構文を廃止して、セマンティクス可変/固定アサーションそれぞれに別の構文を与えるべきという案もあった
      • どちらのアプローチでもC++26 Contracts機能の大幅な再設計となり、P2900R14が強い合意を得てWDにマージされ、現在NBコメントフェーズであることを鑑みると、このような変更はWG21標準化における慣行に反する
    3. always enforced assertionと通常のアサーションの構文は、どれほどの関連性を持つべきか?
      • 構文の長さ、構文構造の類似性など
      • どちらのアサーションがデフォルトになるかに関係なく、always enforced assertionと他のアサーションの構文がどのように関連付けられるかを明確にする必要がある
        • 同じキーワードのわずかな違いで関連性を明示すべきか?
        • 別のキーワードを利用して異なる機能であることを明確化すべきか?
        • 片方の種類のアサーションは他方よりも短いことが望ましいか?
        • など
    4. always enforced assertionの構文はラベル機能との前方互換性を持つべきか?
      • always enforced assertionがC++26で導入され、ラベル機能がC++29で導入されると、契約アサーションにおいて全く同じことをするために2つの方法が存在することになる
      • この状況を回避する方法の一つはラベル機能に対する前方互換性を持たせることだが、その場合はその構文の仕様が<contracts>ヘッダに依存するかなどの問題に対処する必要がある
      • どのような選択を取ったとしてもラベル機能の設計空間を侵食するため、これらの選択の適切な決定のためにはP3400R2をC++26のスコープ内でEWGにおいてレビューする必要があるが、NBコメントフェーズでそれは実現可能性が低い
  2. セマンティクス
    1. always enforced assertionはenforce/quick_enforceのどちらのセマンティクスを採用すべきか?
      • enforce/quick_enforceセマンティクスはどちらも、pre!()の様なアサーションの要件に合致している
      • 単一の構文で両方のセマンティクスを許容する場合、その選択は結局コンパイラオプションによって行われる必要がある
        • このようなオプションは通常のアサーションのオプションとは区別されている必要がある
        • このことはalways enforced assertionの動機と矛盾する可能性がある
    2. 新しい構文はどのアサーションに対して利用可能になるべきか?
      • pre, post, contract_assertのいずれか一部、あるいは全部?
      • pre, postcontract_assertでは考慮すべき設計上の問題が異なるため、この選択が重要になる
    3. 2種類のアサーションの混在は許可されるか?
      • pre!(x)pre(y)を1つの関数宣言で共存させることができる場合、その評価順序は保証すべきか?
      • always enforced assertionの動機の一つである、事前条件違反ごにすぐ終了することによる利点を活かすためには、その評価順序を保証する必要があるが、この選択はalways enforced assertionの設計を大幅に制約することになる
    4. 関数が間接的に(他の言語から)呼ばれる場合でも、pre!/post!は評価されるべきか?
      • always enforced assertionの目的から、関数ポインタを介した関数呼び出し時にもpre!/post!を評価することには異論の余地がない
        • ほかの言語から呼ばれる場合も同様になる
      • このような評価を行うには呼び出し先側でpre!/post!を評価する必要がある
        • このような場合呼び出し元は契約アサーションを認識できないが、契約アサーションは関数型の一部ではなく関数ポインタは契約アサーションを保持できない
        • このため、コンパイラは呼び出し元側でチェックを行うことができなくなる
      • この要件はalways enforced assertionとその類似提案が仕様化可能かつ実装可能であるために順守しなければならない追加の設計制約を課す
    5. pre!()の条件式評価規則はpre()と同じになるべきか異なるべきか?異なるとしたらどう異なるべきか?
      • const化、副作用の省略、重複評価、例外の扱いなど
      • pre!()がC++26WDの最小限の拡張として設計される場合、同じ規則に従うべき
      • 一方、always enforced assertionの動機と要件から、これは通常の契約アサーションとは根本的に異なる機能であり、P2900/P3400と独立して設計された場合異なる規則に従うことになることが予想される
      • また、契約とプログラムの正当性の関係についての見方によっては、always enforced assertionは決定論的なプログラムの動作の一部としてみなされる可能性がある
        • P2900の契約チェックがゴーストコードと位置付けられているのは、P2900の中核原則の一つである「プログラムの正しさを検証するためのチェックを追加しても、その正しさを変更してはならない」に従う物
        • P2900の条件式評価に関する批判されがちなルールはこれに従っている
      • 2種類のアサーションが異なる評価規則に従う場合、両者が同じセマンティクスで評価されたとしてもその条件式の意味は異なることになる
    6. always enforced assertionは実装済みか?そのABIへの影響はどのようなものがあるか?
      • P2900は関数の宣言に契約アサーションを追加してもABIの変更の必要がなく展開可能であることを意図して設計されており、2つの主要なコンパイラで実装されている
        • そうでなければ、ツールチェーンのアップデートが必要となり、展開が大きく遅れることになる
      • 同じアサーションの枠組みに載っている以上、always enforced assertionにも同じ基準を適用するのが妥当
        • 2.5の問に関して、異なる評価規則を採用する場合はなおさら
      • 実装可能性を検証し、ABIへの影響について議論する必要がある
      • always enforced assertionが正確に一回だけの評価を保証する場合、ABIの変更を回避するためには呼び出し先側でアサーションのチェックを行わなくてはならない
        • これは間接呼び出しをサポートするため
        • その場合、通常の契約アサーションとの混在を許可すると、通常の契約アサーションにも同じ制限が適用される
        • 通常の契約アサーションは呼び出し元と呼び出し先の両方での評価を許可しているため、これは大きな設計変更となる
    7. 契約アサーションに仮想関数サポートを追加する場合、always enforced assertionはそこにどのように組み込まれるべきか?
      • 契約アサーションの仮想関数サポートはC++26には間に合わなかったものの、CWGによる文言レビューを通過した経験とGCCによる実装経験があり、C++29の早い段階で導入されると予想される
      • always enforced assertionはそこにどのように組み込まれるかを説明する必要がある
        • ラベル機能も含めたContractsのC++29の拡張は、相互互換性を確保するために同時に設計されている
        • always enforced assertionはそうではないため、これらの機能に対する先方互換性を考慮する必要がある
        • 評価規則が異なる場合、仮想関数サポートについて困難が生じる
          • 基底クラスの契約に対して派生クラスの契約が異なっている場合、正確に一回だけの評価を保証するとこの場合の契約チェックの必要性と矛盾が生じる
  3. ライブラリ
    1. <contracts>のライブラリAPIを拡張して通常の契約違反とalways enforced assertionの契約違反を区別すべきか?どのように区別すべきか?
      • contract_violationオブジェクトから契約違反に関する情報を取得できるが、always enforced assertionにおいてこのAPIの振る舞いをどのように変更するか、あるいはしないかを決定する必要がある
      • ラベル機能においてcontract_violationオブジェクトからラベルを取得する方法はかなり複雑で、実質ラベルが一種類しかない場合にこのAPIを選択するのは疑問がある一方、別の方法を取れば恒久的な設計上の矛盾が生じる
    2. 標準ライブラリにおける堅牢化された事前条件は、pre!()で表現できるべきか?
      • C++26の契約アサーションは標準ライブラリの堅牢化モードにおけるフレームワークとして利用されているにもかかわらず、C++26の機能セットではそれを実装できない点で批判されている
        • 現在は契約アサーションと同等の振る舞いをするベンダー固有のマクロが使用されている
      • always enforced assertionがあれば堅牢化された事前条件を実装できるのか?、そうすべきか?
        • しかし、堅牢化された事前条件はalways enforced assertionとは根本的に異なるもの
        • 堅牢化された事前条件はコンパイラオプションによって評価セマンティクスが変更できる(ignore or enforce)必要がある一方で、always enforced assertionは評価されないセマンティクスが選択されないものであるため

2026/03に行われた全体会議において、Contracts機能にこれ以上の変更を加えずにC++26に導入することが決定されたようです。これによっておそらく、always enforced assertionはラベル機能によって実現されることになると思われます。

P3913R0 Optimize for std::optional in range adaptors

P3913R1 Optimize for std::optional in range adaptors

Rangeアダプタにおいてstd::optionalを認識し動作を最適化する提案。

C++26ではstd::optionalrangeになり、Rangeアダプタで使用できるようになります。

auto f() -> std::optional<int>;

int main() {

  for (const auto& v : f() | std::views::as_const | std::views::reverse) {
    std::println("{}", v);
  }
}

しかし、Rangeアダプタはstd::optionalを認識しないため(ほかのrangeと同様に扱うため)その特性を考慮した動作の最適化などを行いません。

std::optionalは範囲としては要素0か1の範囲であるため、例えば上記例のようにviews::reverseする場合は何もしないで返すことができます。しかし現在はそうなっておらず、reverse_viewを構築してreverse_iteratorを返すことになります。

この提案は、Rangeアダプタでstd::optionalを認識して特別扱いすることで、その特性に合わせたアダプタ動作の最適化を組み込もうとするものです。

ここではstd::optional<T>型のoptの入力に対して、次のような変更を提案しています

  • opt | views::as_const
    • std::optional<T>、または
    • std::optional<const U&>TU&の場合)
  • views::take(opt, n)
    • n == 0: 空のstd::optional
    • それ以外: opt
  • views::drop(opt, n)
    • 0 < n: 空のstd::optional
    • n == 0: opt
  • views::reverse: opt

これ以外のアダプタについては変更を提案していません。

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

P3914R0 Assorted NB comment resolutions for Kona 2025

C++26 CDに対して寄せられたNBコメントの一部の解決のための文言を提供する文書。

ここでは以下のNBコメントの解決のための文言が提供されています

  • US 160-260
    • ranges::uniqueranges::unique_copyについて、入力範囲が空である場合が許可されていない
      • 空の入力に対して動作するように仕様を調整する
  • US 209-332
    • senderの指定で使用されているunspecified-exceptionはその仕様が不十分
      • std::exceptionから派生(曖昧さの無い公開継承)したものであり、dependent_sender_errorとは関係ないことを明確化する
  • US 228-348
    • execution​::​spawn_futureの規定に登場する説明専用エンティティssource-tはデフォルト構築可能である必要があり、デフォルト構築されたオブジェクトは停止状態と関連付けられるべき
  • US 263-396
    • execution::query_parallel_scheduler_backend()の戻り値のshared_ptrはオブジェクトの所有権を持つべき
    • “implements the interface”という表現はC++的ではないため、 “whose type is derived from”に変更する
  • US 265-398
    • parallel_scheduler_backendの3つのメンバ関数の事前条件について、ストレージ(記憶域)についてlifetimeという用語を使用しているが、ストレージには持続期間(duration)がありlifetimeはない
      • ストレージについては、lifetimeではなくdurationを使用する
  • US 266-399
    • parallel_scheduler_backendの3つのメンバ関数の事前条件について、“one of the expressions below”は曖昧であるため、“the call to set_value, set_error, or set_done on r”に変更する
  • US 112-172
    • std::meta::extract()で使用されるextract-value()の要件について、派生型から基底型への変換を禁止すべき
      • Uが配列型である場合にremove_extent_t<U>*のsimilarな型であることを追加する
  • US 130-193
    • 型のプロパティをクエリするリフレクションメタ関数の規定について、対応する型特性メタ関数が存在していない場合に例外を送出するようにする
    • 対応する型特性の使用要件の違反の仕方に応じてどのようなエラーとなるかを規定する

これらの問題は何事もなければC++26でそのまま解消されます。

US 160-260はおそらく並行Rangeアルゴリズムに関連するNBコメントだと思われますが、以前のアルゴリズムの規定(C++20 Rangeアルゴリズムおよびそれ以前のもの)に対しても変更しています。

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

P3915R0 Responses to Trivial Relocation NB Comments

C++26 トリビアルリロケーションに対するNBコメントに対応する文書。

この文書は、C++26 CDに対して寄せられたNBコメントのうちトリビアルリロケーション(P2786R13)に関連するものに対して、P2786の著者の方が応答するものです。

寄せられたNBコメントは次の7つに大別されます

  1. P2786のほぼ全てを削除する
  2. トリビアルリロケーションとmemcpyを同等にする
    • memcpyでもリロケーションできるようにする
  3. std::relocatestd::uninitialized_relocate_*に置き換える
  4. 暗黙的なトリビアルリロケーションの修正
    • 暗黙的にムーブ操作がdeleteされる場合でも、トリビアルリロケーション可能にする
  5. 条件付きトリビアルリロケーション
    • _if_eligibleをキーワードにする
    • _if_eligiblebool値を取るようにする
  6. リロケーション関数においてconst型を許可する
  7. restart_lifetime(P3858)

これらのカテゴリごとに、NBコメントに対して返答(賛成・反対・中立、その根拠など)をしています。

概ね次のような姿勢です

  1. 反対
  2. 反対
  3. 反対
  4. 賛成
  5. 中立、ただしC++26では現在で十分
  6. 中立
  7. C++26に対しては反対

 

P3917R0 A Lifetime-Management Primitive for Trivially Relocatable Types (Presentation)

P3858R1の紹介スライド。

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

このスライドではP3858で提案されているrestart_lifetime()のモチベーションやユースケース等の説明が簡潔になされています。また、restart_lifetime()のARM64eプラットフォームにおける悪用の懸念やC++26に入れる際の利点欠点なども説明されています。

P3919R0 Guaranteed-(quick-)enforced contracts

終了セマンティクスが保証される契約アサーションのニーズについて報告する文書。

C++26のContracts仕様(P2900)では、契約アサーションの評価セマンティクスは固定ではありません。これに対して、「常にチェックされ、契約違反は終了することが保証される」セマンティクス(というよりもそういう動作をするアサーション)のニーズがあり、この文書はそれについて説明するものです。

文書によるとニーズとは次のようなものです

  • メモリ安全性を保証するためのアサーションでは、条件は常にチェックされ、条件がfalseなら後続のコードを実行しないために終了しなければならない
  • そのようなアサーションでは、条件式の評価は正確に一回である必要がある
    • 実行時のオーバーヘッドを最小化し、パフォーマンスを理由に利用を控えることが無いようにする
    • 正確に一回だけの評価によって、副作用も制御可能になる
  • 関数ポインタなど、間接的な呼び出しにおいても必ずチェックされなければならない
  • 条件式の評価に伴う例外送出を契約違反に変換しない
    • 変換にはコストがかかり、メモリ安全性アサーションは可能な限り低コストである必要があるため
  • const化は有害
    • メモリ安全機能においては条件式の理解しやすさが重要であり、目に見えない変換の理解を必要とすべきではない
    • 必ず一度だけ評価されることで、副作用の懸念は無くなる(副作用は必ず一度だけ発生する)

これらはP2900の提供する契約アサーションでは完全に満たすことができないニーズであるため、実質的に現在のContracts仕様に反対を表明する文書になっています。

P3920R0 Wording for NB comment resolution on trivial relocation

トリビアルリロケーションをC++26から削除するための文言を提供する文書。

2025年11月に行われたkona会議において、トリビアルリロケーション(P2786R13)をC++26から削除してC++29をターゲットとして検討しなおすことがEWG/LEWGで合意されたようです。

この文書は、P2786R13の変更をC++26 CDから取り除くための文言の変更をまとめたものです。単純にrevertするのではなく、一部で行われた文言の改善を維持するようにしています。ただし、トリビアルリロケーションに関する機能で残るものはないようです。

P3921R0 Core Language Working Group "ready" Issues for the November, 2025 meeting

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

量が多いのは、C++26のNBコメント由来のイシュー解決が含まれているためです。

P3922R0 Missing deduction guide from simd::mask to simd::vec

P3922R1 Missing deduction guide from simd::mask to simd::vec

simd::vecの推論補助を復元する提案。

スカラ型の演算コードではブール値を整数型に変換することがたまに行われます。

template<typename T>
void f(Tx, Ty) {
  int b = x < y;
  // ...
}

これはsimd::vecでも行うことができます

template<typename T>
void f(Tx, Ty) {
  vec<int> b = x < y; // ng、型が異なる
  // ...
}

ただし、ここでのbの型は正しくはありません。これはbasic_mask型が得られています。

この場合に単項+を使用することで整数型のsimd::vecに変換することができます。

template<typename T>
void f(Tx, Ty) {
  auto b = +(x < y);  // bはsimd::vec<T>(Tは整数型
  // ...
}

ただし次のように書けた方がより直感的と思われます

template<typename T>
void f(Tx, Ty) {
  basic_vec b = x < y;
  // ...
}

この提案はこのように書くことができるようにするために、simd::vecsimd::basic_maskからのテンプレートパラメータ推論を行う推論補助を追加することを提案するものです。

simd::basic_mask<Bytes, ABI>simd::vec<T, ABI>のように両者は1つ目のテンプレートパラメータが異なり、ABIタグ型(2つ目のテンプレートパラメータ)の選択には実装の自由度があります。それによって、simd::basic_maskオブジェクトに対する単項+の結果型をユーザーが予想すること(特に2つのテンプレートパラメータが何になるか)は困難になっています。

basic_vec b = x < y;のように書いたときにsimd::vecの2つのテンプレートパラメータを推論するには推論補助が必要となりますが(左辺のbasic_maskに対して+を適用した結果型を取得するようにするもの)、これは著者の方の参照実装には実装されていたものの仕様に対する提案はもれていたようです。この提案ではそのCTADを追加することでsimd::vecの比較演算結果を直接simd::vecで受けられるようにしています。

P3923R0 Additional NB comment resolutions for Kona 2025

C++26 CDに対して寄せられたNBコメントの一部の解決のための文言を提供する文書。

ここでは以下のNBコメントの解決のための文言が提供されています

  • AT 7-213
    • std::complexはtuple-likeとなったが、タプルサポートライブラリにおいてそれが明記されていない
  • US 140-233
    • std::hiveの要素列(rangeあるいはイテレータ範囲など)を指定できるコンストラクタ/代入演算子のうち、その操作の後の要素の順序を指定していないものが多い
      • 入力と同じ順序を保つことを一貫して指定する
  • US 141-235
    • std::hive::reserve()について、要素の順序の維持が明記されていない
      • 既存要素については何も起こらないこと明確にするために、変更されないことについての言及を削除(サイズも要素の順序も変化することは無い)
  • US 145-234
    • std::hive::trim_capacity(size_type n)について、呼び出し前にキャパシティがn未満である場合の動作が明記されていない
      • キャパシティがn未満である場合、何も起こらない事を明記する
  • US 147-240
    • std::hive::splice()について、例外送出条件がsplice操作の前にチェックされるのか最中にチェックされるのか不明なことによって、例外送出時のコンテナの状態が不明になっている
      • splice操作の前にチェックされることを明記し、強い例外保証を提供する(つまりコンテナの状態は変更されない)
  • US 164-203
    • 集合操作アルゴリズムの指定で出現するMの定義について、複数の同値要素の処理を考慮しているかが明確ではない
    • 効果の指定では、M > Nの場合にどの要素がコピーされるのかが明確ではない
  • US 126-189
    • type_ordertype_order_vは不完全型を許容すべき
      • 明示的な許可を追加する
  • US 227-346
    • execution​::​spawn_futureにおいて、アロケータはまだその型が不完全な時にspawn-state/spawn-future-stateに再バインドされデータメンバとして使用される
       -  不完全型をサポートする必要はなく、使用箇所でアロケータを再バインドする
      
  • US 229-347
    • spawn_future(sndr, token, env)の説明で使用されているsの型が不明確
  • US 221-339
    • execution​::​bulk等の指定に使用されている説明専用check-typesの位置や定義が間違っている
  • US 225-341
    • std::tupleのコンストラクタは条件付きnoexceptではなく、“potentially-throwing”というチェックが正しく機能しない可能性がある
      • 条件付きnoexceptを追加する

これらの問題は何事もなければC++26でそのまま解消されます。

US 160-260(おそらく並行Rangeアルゴリズムに関連するNBコメント)、US 225-341は以前からあるものに対する変更です。

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

P3924R0 Fix inappropriate font choices for "declaration"

規格文書内での宣言に対するスタイルを一貫させる提案。

C++規格書内では宣言という用語について、類似した2つの使用法が定義されています

  • [basic.pre]で定義されている通常フォントの宣言(declaration)
  • [dcl]で定義されている文法的な宣言(declaration
    • 斜体で表示される

前者は単に宣言という言葉として文章中で使用され、後者は特定の宣言の文法要素を指定するために使用されています。これら2つは規格文書中で適用されているスタイルが異なっています(通常フォントと斜体)。

しかし規格文書中ではこの2つのスタイルが誤って適用されている箇所があるようで、この提案はそれを指摘するNBコメントを受けて再度見直しと調整を行ったものです。

このことからわかるように、この提案はeditorialなもので動作の変更等を伴うものではありません。

P3925R0 RO 3-292 29.10.8.3 [simd.comparison] Make basic_simd a Regular Type (with Boolean operator==)

std::simd==の振る舞いについて修正の提案。

この提案は以前のP2892のフォローアップとなる提案です。基本的なところは以前の記事を参照してください

std::simdstd::simd::vec)のoperator==は要素ごとの比較の結果によるマスク(std::simd::basic_mask)を返し、要素列が一致しているかいないかをbool値で返すもの(std::equalやコンテナの比較)ではありません。

この提案はそれを既存のコンテナ等と同じ振る舞い(std::equalによる比較によるような)になるように修正しようとするものです。

この提案ではSIMDの専門家(現状の動作が自然)と一般的なC++開発者(bool値を返すほうが自然)を区別して、後者のユーザーに向けた設計とすることを求めています(P2892では技術的な議論のみが行われていた)。具体的な数値は記載されていないものの、一定の規模のアンケートを行った結果として現在の動作が非直感的だという調査結果も報告しています。

この提案では次の3つの設計案を提示しています

  1. 既存APIのリネーム
    • operator==を削除し、現在の比較動作は明示的なフリー関数(equal_elements())で行われるようにする
  2. operator==boolを返すようにする
  3. std::simdを次のバージョンまで延期する

提案としては1か2の採用を推奨しています。

この提案はルーマニアのNBとしてのNBコメントに基づく提案でもあります。

P3926R0 Slides: operator T& on indirect<T> (in defense of US 77-140)

std::indirect<T>を暗黙的にT&に変換できるようにすることについての解説を行うスライド。

これは、P3902R2で反対意見が出されている問題について、賛成意見を解説するものです。

P3928R0 static_sized_range

そのサイズがコンパイル時に判明している場合のsized_rangeを表すコンセプトの提案。

sized_rangeコンセプトではそのサイズはranges::size(r)によって取得されますが、これが定数式で実行可能であるかどうかを調べる方法が無かったため、コンパイル時にサイズが確定しているかどうかを調べてコンセプト定義に反映することができませんでした。

P2280R4の採択により、コンセプトの定義時やSFINAEの文脈においてranges::size(r)の呼び出しが定数式であるかどうかを自然に調べることができるようになりました。そして、C++26のstd::simdではこのテクニックを用いてranges::size(r)がコンパイル時に取得可能かどうかを積極的にチェックしています。

範囲のサイズ取得が定数式で行えるかどうかを判定することが有益なのはstd::simdだけではなく、この提案はrangeの性質としてそれを表現し判定できるようにするためのコンセプトranges::static_sized_rangeを提案しています。

static_sized_rangeはC++26のstd::cwstd::constant_wrapper)を用いて次のように定義できます

namespace std::ranges {
  template<class T>
  concept static_sized_range =
    sized_range<T> && requires(T& t) { cw<ranges::size(t)>; };
}

cw<ranges::size(t)>ranges::size(t)の呼び出しが定数式で行えない場合にエラーとなるため(std::cw<V>がNTTPVによってstd::constant_wrapper<V>を返すため)、Tのオブジェクトtに対してranges::size(t)が定数式で呼び出せない場合にこのコンセプトはfalseになります。

また、static_sized_rangeに対してそのサイズをコンパイル時に取得するための変数テンプレート、range_static_size_vも提案しています

namespace std::ranges {
  template<static_sized_range T>
  constexpr auto range_static_size_v =
    decltype([](T& t) { return cw<ranges::size(t)>; }(declval<T&>()))::value;
}

std::cw<V>std::constant_wrapper<V>型オブジェクトを返し、std::constant_wrapper<V>::valueからVを取得できます。この定義ではそれを利用しています。

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

static_assert(ranges::static_sized_range<array<int, 3>>);
static_assert(ranges::range_static_size_v<array<int, 3>> == 3);

static_assert(ranges::static_sized_range<span<int, 5>>);
static_assert(ranges::range_static_size_v<span<int, 5>> == 5);

static_assert(ranges::static_sized_range<ranges::single_view<int>>);
static_assert(ranges::range_static_size_v<ranges::single_view<int>> == 1);

static_assert(ranges::static_sized_range<ranges::empty_view<int>>);
static_assert(ranges::range_static_size_v<ranges::empty_view<int>> == 0);

static_assert(!ranges::static_sized_range<span<int>>);
static_assert(!ranges::static_sized_range<vector<int>>);
static_assert(!ranges::static_sized_range<optional<int>>);
static_assert(ranges::range_static_size_v<string> == 42); // ill-formed: constraints not satisfied

さらに、既存のRangeアダプタ等でもこれらのものを活用することを提案しています

  • integer-class
    • range_static_size_v<R>で取得した値の整数型が正しく定数式で使用可能になるように明確化
  • ref_view
    • static_sized_rangeをラップする際にその性質を継承するようにする
      • ref_view<array<int, 42>>のような場合にstatic_sized_rangeとなるようにする
  • array/span/view_interface.front()/.back()
    • 範囲が空の場合に呼び出しをコンパイルエラーにする
  • join_view
    • 現在常にsized_rangeではないが、外側の範囲がsized_rangeであり、内側の範囲が固定サイズである場合などの特定のケースではsized_rangeになることができる
    • 内側の範囲がstatic_sized_rangeである場合、join_viewsized_rangeにできる
    • 外側もstatic_sized_rangeならば、join_viewstatic_sized_rangeになる
  • lazy_split_view
    • tiny-rangestatic_sized_rangeで置き換えることで、input_rangeを分割する際のパターンとしてstatic_sized_rangeを使用できるようになる
  • span
    • 範囲コンストラクタについて
      • 静的にサイズが決まる範囲から構築する際、サイズチェックをコンパイル時にチェックできる(現在は実行時
      • CTADにおいて入力範囲の静的サイズを取得して固定サイズspanを推論できる
  • std::simd/std::inplace_vector
    • ranges::size(r)が定数式である場合、という条件をstatic_sized_rangeで置き換える
  • ranges::min/max/minmax
    • 入力範囲が空でないという事前条件をコンパイル時にチェックできるようになる

このような広い範囲での応用可能性が挙げられていることが、static_sized_rangeの必要性と有用性を示してもいます。

P3929R0 Fix safety hazard in std::function_ref

std::function_refの二重ラップを防止するために、変換コンストラクタをexplicitにする提案。

C++26で追加されたstd::function_refはcallableなものへの軽量な参照となるstd:functionのファミリの一つです。std::function_refstd::functionなどと同じく指定された関数型と互換性のあるcallableの参照となることができるため、型が完全に一致してコピーコンストラクタが呼ばれる場合を除いて、std::function_refstd::function_refというものを構築することができます。

しかし、std::function_refはcallableへの参照でありそれを所有していないため、これは参照の参照のようなものであり、ダングリング参照を容易に生成しえる危険な構築経路となります。標準ライブラリで参照セマンティクスを持つ既存の型(std::string_viewstd::span)はこのような動作をしません。

// function_refで関数を受け取る関数
void call_me_later(std::function_ref<void()> f);

void f();
int g(); // we don't care about g's result

// 関数ポインタを渡せる
call_me_later(f); // ok
call_me_later(g); // ok

// function_refも渡せてしまう
call_me_later(std::function_ref(f)); // ok, コピーコンストラクタが呼ばれ、fの関数ポインタを直接渡したのと同じ
call_me_later(std::function_ref(g)); // use-after-free, `std::function_ref(g)`の一時オブジェクトへの参照を渡している

call_me_later()が受け取った関数を保存していてこの呼び出しの後で使用する場合、寿命が尽きたstd::function_refを呼び出してしまいます。

実際にはこのように構築することはそうそうないはずですが、callableを別の場所から取得してくる場合などでその型が不明瞭な場合、気づかない間にこのような渡し方をしてしまう可能性があります。

// 関数を返す関数
auto return_g_ptr() { return g; }
auto return_g_ref() { return std::function_ref(g); }

call_me_later(return_g_ptr()); // ok, gの関数ポインタを渡している
call_me_later(return_g_ref()); // use-after-free, `std::function_ref(g)`の一時オブジェクトへの参照を渡している

あるいはメンバ関数をstd::function_refに渡したい場合

struct foo{
  void f();
  int g(); // we don't care about g's result
};
foo obj; // suppose obj lives long enough

call_me_later({std::constant_arg<&foo::f>, obj}); // ok
call_me_later({std::constant_arg<&foo::g>, obj}); // ok

内実を知らなければconstant_argを用いたこの初期化式は何をしているのか全く明確ではないため、std::function_refを明示的にしたくなるかもしれません

call_me_later(std::function_ref(std::constant_arg<&foo::f>, obj)); // ok, コピーコンストラクタが呼ばれる
call_me_later(std::function_ref(std::constant_arg<&foo::g>, obj)); // use-after-free, `std::function_ref(std::constant_arg<&foo::g>, obj)`への一時オブジェクトを渡している

こうすると同じ問題にぶつかります。これは、後者の場合はstd::function_ref{...}としていることによってまずここでstd::function_refの構築が行われ、CTADによってテンプレートパラメータが推論されます。それはfoo::gに基づいて決定されるため、std::function_ref<int()>になります。一方、call_me_later()の引数型はstd::function_ref<void()>になるので変換が走ります。

前者の方は、引数型std::function_ref<void()>の構築を行うものであるため、CTADは起こらず型変換も発生しません。

このような寿命の問題がない場合でも、二重std::function_refは参照の参照であるため、呼び出しに際して2回の間接呼び出しが必要になります。

この提案は、このような二重std::function_refが暗黙的に発生することを防止するために、テンプレートパラメータが一致しない場合の変換コンストラクタをexplicitにしておくものです。

ここで関連するstd::function_refのコンストラクタは2つあります。

namespace std {

  template<typename R, typename... Args>
  class function_ref<R(Args...) cv noex> {
    ...

    // コピーコンストラクタ
    constexpr function_ref(const function_ref&) noexcept = default; 

    // 変換コンストラクタ
    template<class F>
    constexpr function_ref(F&& f) noexcept;

    ...
  };
}

片方はコピーコンストラクタであり、これはテンプレートパラメータがcv修飾やnoexceptまで含めて完全一致している場合にのみ呼ばれるものであり、ダングリング参照を生成しないので問題ありません。問題なのはもう一つの汎用の任意のF&&から構築するコンストラクタで、現状これはテンプレートパラメータが異なるものの呼び出しについて互換性のあるstd::function_refを暗黙的に受け入れています。

この提案ではこのコンストラクタを条件付きでexplicitにすることを提案しています。これにより、先ほどのcall_me_later()で問題となる例は暗黙変換が禁止されることによってコンパイルエラーになるようになります。

提案されているexplicitの条件は簡易に記述することが難しいものの

  • Fstd::function_refの特殊化であり
  • Fの関数型のシグネチャに互換性がない
    • 互換性がある場合: 引数型と戻り値型が同じでnoexceptとCV修飾に互換性がある場合

場合にexplicit(true)になります。

また同時に、同じ判定方法で関数型のシグネチャに互換性がある場合に次の動作変更も提案しています

  • 該当のコンストラクタを用いて構築された場合、渡されたstd::function_refを参照するのではなく、それが参照しているcallableを直接参照するようにする
    • 二重std::function_refを回避する
    • この場合はexplicit(false)(暗黙変換可能)
  • 代入演算子を有効化する
    • 上記の変換コンストラクタを経由して代入されるため、ダングリングや二重std::function_refにならない

これらの変更により、二重std::function_refが暗黙的に構築されなくなり、可能な場合は二重std::function_refを回避する最適化が行われた上で暗黙変換が許可されるようになります。

この提案はリジェクトされているようです。

P3931R0 consteval all the non-allocating operator"" things

標準ライブラリ内のユーザー定義リテラルをすべてconsteval関数にする提案。

実行時の依存関係(実行時にしか取得できない引数など)を持たず、単にオブジェクトを生成するだけの関数は、暗黙的な外部依存関係(動的メモリ確保の必要性など)がない限りconsteval関数であるべきだとして、ちょうどそれらの条件に該当する関数群である標準ライブラリ内のユーザー定義リテラル(operator"")についてconsteval関数に変更しようとする提案です。

ただし、ユーザー定義リテラルのうちでも動的メモリ確保の必要性があるもの、すなわちstd::stringを生成する""sについてはその対象ではありません。

提案の対象は次のものに関連するユーザー定義リテラルです

  • std::string_view
  • std::complex
  • <chrono>

これらのユーザー定義リテラルを使用する場合で何か問題がある場合、それは実行時ではなくコンパイル時にコンパイルエラーとして報告されます。それにより、ユーザーはバグに早期に気づくことができるようになり、実行時のデバッグにおいてもそれらのものを無視することができます。

この変更によって既存のコードが壊れる可能性があるのは、ユーザー定義リテラル演算子オーバーロード関数のアドレスを取っている場合です。consteval関数のアドレスはコンパイル時にしか存在できないため、そのようなコードはエラーになる可能性があります。しかし、標準ライブラリのほとんどの関数は基本的にそのアドレスを取ることが禁止されているため、実質的に既存コードを壊さないとしています。

P3933R0 constexpr std::hive

std::hiveを定数式で使用可能にする提案。

C++26では既存の全てのコンテナとコンテナアダプタが基本的に定数式で使用可能になっています。その一方、同じくC++26で追加されたstd::hiveはほとんどの関数がconstexpr指定されていないことによって定数式で使用可能ではありません。

この提案は、C++26においてstd::hiveを定数式対応させようとするものです。

std::hiveはパフォーマンスを重視しているコンテナであり、特にキャッシュヒット率を向上させることを意識しています。そのため、コンテナの一部の操作などが定数化されてしまうことによる実行ファイルの肥大化と、(本来キャッシュ内で計算すればいいものを)定数のメモリからの読み出しに置き換えてしまうことによるパフォーマンスの低下を懸念してほとんどconstexpr対応は行われていませんでした。

この提案では、それはオプティマイザの仕事の結果でありconstexpr対応やその仕様とは関係ないこと(オプティマイザの定数畳み込みはconstexpr関数であるかどうかとは無関係に行われている)、constexpr変数の初期化などによって定数式をトリガーしないと定数化は行われないこと(ほとんど、自動的に起こることではない)などから、そのような懸念を否定しています。

MSVC STLのフォークによるconstexpr std::hiveの実装を行っており、そこで問題があったのは.get_iterator(const T *)のみだったとのことです。この関数はstd::hiveの要素のポインタを受け取って、そこからイテレータを取得するものです。その計算量は要素数に対して線形と指定されており、その実装はポインタのグローバルな順序付け(実装定義全順序)に依存しているようで、これが定数式では提供されないことが問題となります。

そのため、この提案では.get_iterator()constexpr指定することは提案していません。

この提案の著者の方は、.get_iterator()constexpr指定するために必要なユーティリティをP3852で提案しており、これが採択されてから改めて.get_iterator()constexpr指定することを推奨しています。

また、std::hiveの可能な実装戦略のうち少なくとも一つではあとからconstexpr対応させようとするとABI破損の可能性があるため、C++26でconstexpr対応しておくことを提案しています。

P3935R0 Rebasing <cmath> on C23

<cmath>ヘッダの参照をC23に更新する提案。

<cmath>ヘッダはCの<math.h>を参照しており、ほとんどの機能はC由来になっています。C++26時点では<cmath>ヘッダはC17の<math.h>を参照しています。

P3348R4ではC++26におけるCの参照をC23に更新していますが、10進浮動小数点数型に関するものやその他コア言語の変更が必要になるものがあったため<cmath>ヘッダの参照は更新されていません。

この提案は、コア言語に必要な対応を行ったうえで<cmath>ヘッダの参照をC23に更新するものです。これにより、C23までに追加されたいくつかの数学関数がC++でも利用可能になります。

ただしこの提案では、C23の10進浮動小数点数型(_Decimal128)をC++でも使用可能にすることは含んでいません。

C20以降で追加されたものは大きくISO/IEC 60559に準拠する数学関数と、マクロの2種類です。

そのうちNarrow rounding functionsと呼ばれる丸め関数群については、関数名の一部として型を指定する関数群をそのまま輸入するのではなく、1つの関数テンプレートとして使用可能にすることを提案しています。

コア言語について変更が必要なのは、浮動小数点数の正規化(canonicalization)に関してであり、このための文言も提案しています。

追加を提案しているものは多岐にわたるため、詳細は提案をご覧ください(関数については表にまとめられています)。

P3936R0 Safer atomic_ref::address (FR-030-310)

std::atomic_ref::address()の戻り値型をvoid*にする提案。

std::atomic_ref::address()atomic_refが参照しているオブジェクトのポインタを取得するものです。これは取得したポインタを介してアクセスするためのものではなく、そのポインタ値そのもの(アドレス)を使用したい場合のためのものです(主に並行ハードウェアにおいての同期コスト削減のために使用する)。

ただ、この関数の戻り値型はatomic_ref<T>に対してT*であり、容易に間接参照して元のオブジェクトにアクセスする事ができてしまいます。しかし、それはアトミックアクセスではなくなるため、ほとんどの場合誤用となります。

そのためatomic_ref::address()の戻り値型はそのままだと間接参照することができないvoid*uintptr_tにすべきだという意見もあり、その提案の途中のリビジョンではそうなっていた時期もありました。しかし、どちらの型も定数式で使用できない問題があったことから、最終的には関数名を変更(最初は.data()だった)したうえで戻り値型はatomic_ref<T>に対してT*を返すようにされました。

uintptr_tは必須の型ではないことによって定義されていない環境があり得ることが問題でしたが、これは解決のめどが立っていません。一方、C++26 P2738R1の採択によってvoid*T*のキャストは定数式でサポートされるようになっています。

この提案は、atomic_ref::address()の戻り値型についての懸念を表明するNBコメントを受けて、その解決のためにatomic_ref::address()の戻り値型をvoid*に変更しようとするものです。

void*はそのままだと間接参照できないため元のオブジェクトにアクセスする事が簡単ではない一方、アドレス値を取得して利用する用途では全く問題がありません。これにより、元の目的を達成しながらNBコメントの懸念に対処することができます。

唯一の欠点は、atomic_ref<T>においてTのCV修飾をコピーする必要性から戻り値型が複雑になることだとしています。

namespace std{

  template<typename T>
  class atomic_ref {
    ...

    // TのCV修飾をvoid*にコピーして適用した型を表す
    // atomic_ref<int>ならvoid*、atomic_ref<const int>ならconst void*
    using address_return_t = COPYCV(T, void*);
    
    ...

    // 元の宣言
    constexpr T* address() const noexcept;

    // この提案
    constexpr address_return_t address() const noexcept;

    ...
  };
}

P3937R0 Type Erasure Requirements For Future Trivial Relocation Design

C++29以降のTrivial Relocation機能の設計要件の提案。

P2786R13で提案されC++26 WDにマージされていたトリビアルリロケーション機能は、NBコメントでの反対意見などを受けてC++26から削除されることが決定されました。この提案はそれを受けて、ライブラリでの使用経験や要件から望ましいトリビアルリロケーション機能に対する設計要件を提示するものです。

この提案は、Proxyというライブラリ(標準ライブラリ入りを目指して提案されてもいる)において、型消去を扱う場合に移動(ムーブ/リロケーション)パフォーマンスを最大化する観点からトリビアルリロケーションに求める要件を検討しています。

型消去ラッパでは、型消去対象のオブジェクトを何らかのバイトバッファとして保持しているため、型消去ラッパ自体をリロケーションしようとする場合はそのバイトバッファに格納されているオブジェクトもリロケーションする必要があります。バイトバッファに格納されているオブジェクトの型がトリビアルリロケーション可能であることが分かっている場合、このリロケーションはバイトバッファのmemcpyで行うことができます。

しかし、P2786R13のトリビアルリロケーション可能な型の定義においては、何らかのfixup操作が必要な型が含まれてしまっています。よくあげられるところではそれは多態的な型(仮想関数テーブルを持つ型)であり、ARM64eなどの特定のプラットフォームにおいてPointer Authentication Codeへの対応が必要になります。

このような場合、型消去ラッパの保持するバイトバッファをリロケーションするにはmemcpyではなくそこに格納されている型に応じたリロケーションパスを呼び出する必要があります。このような型を想定しなければ一切ユーザーコード(型消去対象の型で定義されている操作)を実行しない単なるmemcpyで終わっていたのに対して、ユーザーコードを実行してバイトバッファの移動を行う必要があります。これは、単なるmemcpyあるいは多態的型を保持するポインタの単純なムーブと比較してパフォーマンスで劣ることになります。

提案では、Proxyライブラリにおいてこのようなリロケーション後のfixupを考慮する場合としない場合(あるいはする必要のある型消去ラッパなど)との間でリロケーションのパフォーマンスをいくつかのプラットフォームで計測したベンチマーク結果が掲載されており、fixupを考慮する場合に大幅なパフォーマンスの低下が起こることを報告しています。

そのため、この提案ではトリビアルリロケーション可能な型とはmemcpyによるビット単位のコピーでリロケーションが完了する型として、追加のfixupが必要な型を含まないようにすることを提案しています。この提案のこの定義では、多態的な型はトリビアルリロケーション可能ではなくなります。

これを柱として、この提案では次の設計要件を提示しています

  1. トリビアルリロケーション可能とは、ビット単位のコピーのことと定義
    • トリビアルリロケーション可能な型とは、オブジェクト表現をバイト列としてstd::memcpy/std::memmoveを用いてsizeof(T)分コピーし、適切にアラインされたストレージに再配置できる型のこと
      • このコピーの後、宛先オブジェクトは元のオブジェクトとの寸断なく生存期間を開始し、元のオブジェクトの生存期間は終了する。この際にデストラクタの呼び出しやその他のfixup処理は発生しない
    • この性質は完全にコンパイラによって決定可能であり、判定する型特性のユーザー定義特殊化は禁止される
  2. ムーブ可能ではない型でもトリビアルリロケーション可能にする
    • 暗黙的に削除された、または意図的に定義されていないムーブ操作は、トリビアルリロケーションを無効化しない
      • その表現が要件1を見たすクラス型ではその宣言されたムーブ操作の有無にかかわらず、トリビアルリロケーション可能となる
    • これにより、ムーブ後の無効状態の導入を防止しつつトリビアルリロケーション可能になり、型の無効状態を考慮するユーザー定義リロケーション操作の必要性を排除できる
  3. 条件付きのトリビアルリロケーション可能性
    • 型が、あるブール条件下でのみトリビアルリロケーション可能であることを宣言することを許可する
      • このブール条件が満たされ、すべてのサブオブジェクトが要件1を満たす場合、その型はトリビアルリロケーション可能となる
    • P2786R13のtrivially_relocatable_if_eligible(bool)を利用することを推奨
    • これにより、ライブラリ作成者は独自の特性クエリ方法や複雑な特殊化ルールなどを導入することなく、型の性質に合わせてトリビアルリロケーションを活用できる
  4. 生存期間のセマンティクスの明確化
    • トリビアルリロケーション可能なオブジェクトのwell-formedな生コピーを作成した後、新しいランタイムプリミティブを導入せずに、コピー先オブジェクトの生存期間が直ちに開始され、コピー元オブジェクトの生存期間が終了することを明確化する
    • これにより、新しい関数が不要になり既存の最適化に関する知識が活用できる
  5. 低レベルコードでの使用時のガイダンスの提供
    • 生存期間のルールが順守される場合でも、トリビアルリロケーションが一般的な低レベルパターンに対して安全であることを明確に文書化する
      • SBOのバッファ拡張
      • アリーナ・コンパクション
      • mmapによる永続化

C++26からトリビアルリロケーション仕様が削除されたことでその仕様の再設計が必要となり、この提案はその再設計に際してライブラリ実装者の観点からほぼ必須の要件をまとめて提示するものです。

この提案はリジェクトされているようです(P4155R0がレスポンスとして提出されています)。

P3938R0 Values of floating-point types

浮動小数点数型の表現可能な値のモデルについて、もう少し明確にする提案。

提案では、浮動小数点数型の細部についてのいくつか質問に答える形でC++標準の浮動小数点数モデルの行間を明確化していき、規定が必要な部分を浮き彫りにしています。

主に次のような点を明確にしようとしています

  • 浮動小数点数リテラル(0.0など)は常に正(非負)
    • doubleが符号付のゼロを表現できる場合、0.0は正のゼロであり、-0.0は負のゼロ
      • 正と負のゼロを表現できる浮動小数点数型の場合、ゼロに対する単項-は符号が逆のゼロを返す
  • ISO/IEC 60559に準拠していることの意味の定義
    • ある浮動小数点数型は、その値の表現がISO/IEC 60559に記載されいている浮動小数点数形式の一つであり
    • かつ、ISO/IEC 60559に記載されている浮動小数点数値の集合を表現可能な場合、にISO/IEC 60559に準拠している
    • ISO/IEC 60559に準拠していることは、その浮動小数点数型に対する演算がISO/IEC 60559の規定通りに振舞うことを意味しない
  • 浮動小数点数型が表現できる値の指定
    • 少なくとも有理数のサブセット
    • 型の実装定義値表現によっては、次の非有限値を表現できる
      • 無限大
      • quiet NaN
      • signaling NaN
      • その他の実装定義の値
      • これらの値についても、正と負の符号を持つ異なる値を表現できる
  • 負の定義
    • 値が負であるのはその値がゼロより小さい場合
      • 負のゼロとNaNは負の値ではない
    • 符号ビットが立っていることはその値が負の値であることを意味しない
  • 式の結果におけるゼロ
    • 式の結果が正負のゼロを表現可能な浮動小数点数型である場合、式の結果としてどのゼロが選択されるかは実装定義
    • <=>== !=< <= > >=において、正のゼロと負のゼロは同値
  • テンプレートにおける等価性の定義
    • テンプレート引数の文脈(特にNTTP)において、浮動小数点数の等価性とは、ビット単位で同一(bitwise identical)であること
    • ビット単位で同一(bitwise identical)であるとは
      • Tの値xyは、std::bit_cast<U>(x) == std::bit_cast<U>(y)trueの場合にビット単位で同一とみなされる
        • Uは、Tの同じサイズでオブジェクト表現におけるパディングビットの数と位置がTと同じ仮想的な符号なし整数型
      • 正負のゼロ、ペイロードが異なるNaNなどはビット単位で同一ではない

この提案は現在のC++標準の規定する浮動小数点数型とその演算についての振る舞いを変えるものではありません。明確になっていない行間の部分を明確に規定しようとするものです。

P3940R0 Rename concept tags for C++26: sender_t to sender_tag

std::executionのコンセプトの判定に使用されるタグ型の命名を修正する提案。

std::executionのコンセプトはそれによって使用可能になるいくつかのCPOを持っています。例えば、senderに対するsenderファクトリ/アダプタが該当します。これらのCPOは入力の型に対してそのメンバ関数を考慮してカスタマイゼーションポイントを探索しますが、その際に同名の関係ないディスパッチを防止するために、コンセプトにアダプトするためには特定のメンバ型をコンセプトによって指定されるタグ型によって定義しておく必要があります。

このstd::executionにおけるタグ型はCPO名に対して_tプリフィックスを付加する命名になっています。そのコンセプトにアダプトする型を定義する場合は、メンバ型としてCPO名+_conceptの名前でこのタグ型のエイリアスを定義しておく必要があります。

namespace std::execution {

  // scheduler コンセプトのタグ型
  struct scheduler_t {};

  template<class Sch>
  concept scheduler =
    derived_from<typename remove_cvref_t<Sch>::scheduler_concept, scheduler_t> &&
    ...;

  // receiver コンセプトのタグ型
  struct receiver_t {};

  template<class Rcvr>
  concept receiver =
    derived_from<typename remove_cvref_t<Rcvr>::receiver_concept, receiver_t> &&
    ...;
}

例えば、schedulerコンセプトの場合はscheduler型を定義する際に、その型をSとするとS::scheduler_conceptという名前の型としてstd::execution::scheduler_t型を取得できるようにしておく必要があります。

このようなタグ型はすでに標準ライブラリにもいくつか存在しているのですが、それらのタグ型はその役割によって大きく2つに分類されます。

  • 曖昧性解消用のタグ型
    • コンストラクタ呼び出しや関数呼び出しの曖昧性を解消するためのタグ型
  • コンセプトタグ型
    • ある特定のコンセプトのモデルとなる型を識別するためのタグ型

そして、既存のタグ型はこのカテゴリの違いによってどうやら命名規則が異なっています。

  • 曖昧性解消用のタグ型: name_t
  • コンセプトタグ型: name_tag

std::executionのタグ型は後者のコンセプトタグに該当するため、この命名規則に従っていません。この提案はC++26のうちにこれを修正しておこうとするものです。

曖昧性解消用のタグ型は、ライブラリ関数(コンストラクタ)の特定のオーバーロードを明示的に選択するために引数に指定して使用するタグの型です。

// タグ型とタグ値
struct foo_t {}; // maybe with an explicit ctor
inline constexpr foo_t foo = foo_t();

// タグを使用するクラス型
struct useful_class {
  explicit useful_class(foo_t, int, int);
  explicit useful_class(bar_t, int, int);
};

// タグを指定することでコンストラクタを呼び分ける
auto myVar = useful_class(foo, 1, 2);

標準ライブラリの中では次のものがこれに該当するタグ型として提供されています

  • std::defer_lock_t
  • std::from_range_t
  • std::in_place_t
  • std::piecewise_construct_t
  • std::unexpect_t

このタイプのタグ型は_tプリフィックスを持ちます。

コンセプトタグ型は、関数の呼び出し時に指定する形で使用されるのではなく、ユーザー定義型において特定の名前で定義されるものです。ジェネリックプログラミングの文脈でタグによって入力されたユーザー定義型の性質を特定し、その満たす性質(コンセプト)に応じたディスパッチを行うために使用されます。

// タグ型
struct foo_tag {};

// コンセプト
template<class T>
concept foo = ~~~~;

template<class T>
void internal_algorithm(T, foo_tag);
template<class T>
void internal_algorithm(T, bar_tag);

// 満たすコンセプトによってディスパッチを行う関数
template<class T>
void useful_template(T t) {
  internal_algorithm(t, typename T::your_category());
}

// コンセプトへアダプトする型
struct MyFoolikeClass {
  using your_category = foo_tag;
};

また、このようなタグ型の継承構造によって、対応する概念の階層構造を表現することができます。

標準ライブラリでこのタイプのタグに該当するのはイテレータのタグ型です。

struct forward_iterator_tag : input_iterator_tag {};

template<class I>
concept forward_iterator =
  input_iterator<I> &&
  derived_from<ITER_CONCEPT(I), forward_iterator_tag> &&
  ...;

コンセプトタグの場合、タグ名はコンセプト名+_tagになり、メンバ型のエイリアス名は_category/_conceptのプリフィックスを持ちます。

std::executionで追加されているタグ型は、その命名規則を除いてコンセプトタグに該当するタイプのタグ型です。そのため、C++26の完成前にその命名を変更する必要があります。

  • sender_t:
    • メンバ型名: sender_concept
    • コンセプト: sender
  • receiver_t:
    • メンバ型名: receiver_concept
    • コンセプト: receiver
  • operation_state_t:
    • メンバ型名: operation_state_concept
    • コンセプト: operation_state
  • scheduler_t:
    • メンバ型名: scheduler_concept
    • コンセプト: scheduler

これらのタグ型は現在のところ互いに派生関係にはなく、それぞれ派生するタグ型を持ちません。しかし、std::executionの上に構築される予定のネットワークライブラリではstd::executionから派生したコンセプトが定義されることが予想されており、その場合にはこれらのタグ型から派生したタグ型が定義される可能性があります。その場合はさらにコンセプトタグへ該当するようになります。

P3941R0 Scheduler Affinity

execution::affine_onアルゴリズムの改善提案。

execution::affine_onexecution::task(P3552R3)とともに導入されたsenderアルゴリズムで、senderschedulerを受け取って、そのsenderが与えられたschedulerで完了するようなsenderを返すものです。この時、受け取ったsenderが受け取ったschedulerで完了するように既になっている場合、返されるsenderでは追加のスケジューリングを省略することができます。

コルーチン内で他の非同期処理(コルーチン or sender)を待機(co_await)する場合、非同期処理のネストはco_awaitによって通常のシングルスレッド実行コードと同じような見た目で書くことができるようになります。この時、ある非同期処理のco_await前後で、その一連の処理を実行する場所(実行コンテキスト、すなわちscheduler)は変化しないものと仮定されるのが自然です。

例えば次のような極端な例において

task<> fun() {
  co_await ex::just();
  auto v = co_await ex::just(0);
  auto [i, b, c] = co_await ex::just(0, true, 'c');
  try { 
    co_await ex::just_error(0);
  } catch (int) {}
  co_await ex::just_stopped();
  int result = co_await []->ex::task<int> { co_return 42; }();
}

この例でco_awaitしているのはほぼすべてsenderですが(最後だけコルーチン)、このようなコードにおいてco_awaitの前後で実行場所が変化することは非直観的となることが分かると思います。例えば、fun()自体があるスレッドプール上で実行されているときに、その内部のいずれかのco_awaitの後でスレッドプールとは関係ない別のスレッドで実行されるようになっていることはどちらかと言えばバグと判断されると思われます。

このため、execution::taskではco_awaitする前後で実行場所(scheduler)が変化しないようにしています。すなわち、別の非同期処理をco_awaitする場合、その処理がどこのschedulerで実行され、また現在のschedulerを書きかえるような事を行ったとしても、非同期処理が完了してco_awaitによる待機が解除される際に元のschedulerに復帰して後続の処理(大本のコルーチンそのもの)を再開します。

この仕組みの事をScheduler Affinityと呼びます。Scheduler Affinityは他にも、コルーチン固有の落とし穴を回避するためにも役立ちます。

execution::affine_onexecution::taskにおけるScheduler Affinity動作に使用されるsenderアルゴリズムで、execution::taskのコルーチン内のco_awaitにおいて、待機対象の処理が完了した後に元のschedulerに復帰するために使用されます。

schedulerの復帰には再スケジュールと呼ばれる処理が必要になり(実行コンテキストへの再投入、スレッドプールの場合はスレッドプールのキューへの投入など)、これはコストがかかります。そのため、この再スケジュールをする必要がないことが分かる場合(co_awaitする操作がschedulerを変更しないことが分かる場合)に再スケジュールを回避してそのまま元のschedulerで再開することが望ましく、execution::affine_onはこのような最適化が許可されているものです。

execution::affine_onexecution::continues_onと同じシグネチャで同じ動作をするのですが、この点がexecution::continues_onと異なります。

しかしP3552R3のexecution::affine_onは非常に最小限の定義しかされておらず、仕様が不透明で設計も不完全でした。それに対してはP3796などで問題が指摘されており、この提案はその流れやNBコメントを受けてexecution::affine_onの設計を改善しようとするものです。

この提案では次の変更を提案しています

  1. execution::affine_onの引数の修正
    • affine_on(sndr, sch) -> affine_on(sndr)
      • senderのみを受け取るようにする
    • scheduleraffine_onの上から(co_await式内で用意して)渡すのではなく、affine_onの返すsenderに接続(connect)されたreceiverの環境から取得する
      • これは、co_await sender-exprに対して、as_awaitable(affine_on(sender-expr), *this)(ここでの*thisexecution::taskオブジェクト)で行われる接続操作を介して
      • execution::taskのプロミス型の環境からschedulerを取得する
        • これはco_awaitの前の実行コンテキストのschedulerと一致するため、scheduler引数無しで正しいschedulerを取得できる
  2. 失敗しないschedulerの要求
    • affine_on(sndr)の目的は、渡された処理sndrを実行し元の実行コンテキスト(scheduler)上で完了すること
    • sndrの処理が完了した後のスケジューリング操作(as_awaitableで接続後のoperation_stateの処理)が成功しなかった場合(set_error/set_stoppedで完了した場合)、元のschedulerに復帰することができなくなるためaffine_onの目的を果たすことができなくなる
    • このため、affine_onで使用されるschedulerreceiverは必ず成功するものであることを要求する必要がある
      • receiverに関連付けられた停止トークンはunstoppable_tokenであり
      • schedulerの完了シグネチャにset_error_t/set_stopped_tを持たないものであること
    • すべてのschedulerがこれを保証できるわけではない
      • 保証できないschedulerでは、affine_onの使用(ひいてはtaskコルーチンにおけるco_awaitの使用)がコンパイルエラーとなる
      • 標準のschedulerではparallel_scheduler以外のschedulerでこの保証が実現可能
  3. senderアルゴリズムにおけるexecution::affine_onカスタマイズ
    • schedulerを変更することがないsenderアルゴリズムにおいては、そのことをaffine_onに伝達するために返すsenderに対してaffine_onをカスタマイズできる必要がある
      • affine_onsender型のメンバ関数としてtransform_sender(sndr)を追加する
        • デフォルトでは恒等関数だが、子senderを返すようにして実行コンテキストを変更しない事を通知する
      • 例えば、just, just_error, just_stopped, read_env, write_env, then, upon_error,upon_stoppedなどが(一部は条件付きで)該当する
  4. change_coroutine_schedulerの削除
    • change_coroutine_schedulerは、taskコルーチンの実行コンテキスト(scheduler)を切り替えるためのもの
      - `co_await change_coroutine_scheduler(sch)`のように使うもの
      
    • この切り替えにはいくつか問題があり、ユースケースは非常に稀であるとして削除する
      • schedulerの切り替え前に作成されたローカル変数の構築と破棄が異なるscheduler上で起こる
        • 元のschedulerで構築され、切り替え後のschedulerで破棄される
      • task内にネストしたtaskとの間で対称転送をを正しく行うためには、各taskが適切なschedulerで完了する必要があり、このために元のschedulerに復帰するための情報を保存しておく必要がある
        • ネストtaskchange_coroutine_schedulerされたとしても、そのtaskが完了した時に元のschedulerに復帰する必要がある
        • このために、元のschedulerと異なるschedulerに切り替えるための状態を保存しておく必要がある
          • コルーチン内でchange_coroutine_schedulerが使用されているかを静的に知ることができないため、このために常に追加のストレージが必要になる
    • あるtask自体が使用しているschedulerを切り替えるためには、そのtask自体にon(sch, task)starts_on(sch, task)を適用して明示的に変更できる
  5. execution::affine_onのデフォルト実装の明確化
    • ここまでの変更も踏まえて、以前のexecution::continues_onから踏襲されていた設計とは全く異なるaffine_onの定義が得られる
    • 1~4の変更を組み込んだ動作をデフォルトの定義とする
  6. execution::affine_onの名称変更
    • ここまでの事を踏まえると、affine_onという名前は適切ではない可能性がある
    • 名称の代替案は特に挙げていない

この提案はまだ具体的な文言を伴っておらず、これらの変更点も確定したものではないようです。

P3945R0 Comments on D3933R0 (constexpr hive)

constexpr std::hive提案(P3933R0)に対する、std::hive提案の著者からのコメント。

P3933R0(上の方)では、std::hiveのC++26でのconstexpr対応が提案されており、それが行われていない理由や検討の歴史、先行実装と問題点について報告しています。

この文書は、std::hive提案の著者の方によるP3933R0へのコメントで、技術的な側面、提案のヒストリーの問題点、現在の実装においての問題点、標準化手続きの問題点などについてコメントされています。

著者の方としてはstd::hiveconstexpr対応には反対ではないものの、「完全な実装とテストケースが提供され、私(std::hiveの著者)以外の第三者によるテストが行われ、かつアーキテクチャ固有のパフォーマンス上の問題やその他の問題が確認されない場合に限り、constexpr std::hive の採用を検討する価値があると考える」としています。

ただし、C++26に急いで間に合わせる必要はなく、まず標準ライブラリの実装が出てくるのを待ち、そのうえで実行時パフォーマンスや実装負荷を考慮しながらconstexpr実装を行って、それから標準化を進めることを推奨しています。

転載はしませんが、技術的なコメントや実装におけるコメントにはstd::hiveの提案では語られていないような点も語られているので、興味のある人は読んでみるとよいかもしれません。

P3946R0 Designing enforced assertions

必ずチェックされ違反が起きたら終了するアサーションの設計検討を行う提案。

2025年11月のKona会議において、ルーマニアNBからのNBコメントとそれに付随する提案(P3911R0、上の方に解説があります)を受けて「無視できない契約アサーション(non-ignorable contract assertions)」と呼ばれる新しい言語機能をC++26に向けて追加することを決定したようです。この提案は、それを受けてその設計空間の探索やトレードオフの検討を行うものです。

この提案では、この無視できないアサーションをP2900の契約アサーションの枠組みの中で設計しようとしており、特に事前条件アサーションの動作モデルについて次のように定義しています

事前条件アサーションは、関数呼び出し時にfalseである場合にプログラムにバグが存在することを示す条件を表すものである。これは静的解析ツールの様なツールによって報告されることが推奨される事象である。 その後の動作は、強制アサーションを使用しているか設定可能アサーションを使用しているかなど、多くの条件に依存する

このような定義にしているのは、ベースとなる事前条件のモデルが「条件がtrueでない限り、プログラムの動作は未定義」というものであるためです。これは広く用いられているモデルであり、事前条件が破られた場合の動作を規定するとそれはもう事前条件ではなくなります(つまり、その動作は関数の動作の一部になります)。

上記の定義の無視できないアサーション(強制アサーション)のモデルは、一定の動作保証を与えつつも(モデルの中ではそれについて言及せず)、依然として事前条件違反はプログラムのバグであるとして扱うものです。

そして、次の3つの設計基準を定義しています

  1. 関数宣言は人間が容易に解析でき、簡潔である必要がある
  2. アサーションの主要な目的(どのような場合にバグがあるとみなすか、を伝達する)が、二次的な目的(バグのある呼び出し時に何が起こるか)よりも明確に示されている必要がある
  3. 強制アサーションを使用している人が、誤って設定可能アサーションを記述してしまうことが起こらないようにする

この上で、構文とセマンティクスの設計を検討しています。

まず構文については、P3911R0で提示されていたものを含む次の4つを候補としています

// 候補 A
void fun(int number, int index)
  pre (number > 0)
  pre! (index >= 0)
  pre! (index < size());

// 候補 B
void fun(int number, int index)
  pre? (number > 0)
  pre! (index >= 0)
  pre! (index < size());

// 候補 C
void fun(int number, int index)
  pre_maybe (number > 0)
  pre_always (index >= 0)
  pre_always (index < size());

// 候補 D
void fun(int number, int index)
  pre (number > 0)
  pre<enforce> (index >= 0)
  pre<enforce> (index < size());

AとBには次のような認識上の曖昧さがあります

void fun(vector& vec1, vector& vec2)
  pre!(vec1.empty())   // typo?
  pre(!vec2.empty());  // or intended? 

void container::fun()
  pre not(empty());  // container not empty?

また、AとBにはCにおける契約アサーション(検討段階)との互換性の観点からのリスクがあり、Cではpre(expr)のようにアサーションを関数呼び出し形式にしたい要望があります。これは、Cとの相互運用においてpre, post, contract_assertを何もしない関数マクロとして定義しておくことができるためです。とはいえ属性を考慮すると現在すでにこれは必ずしも満たされないため、この要件の重要性は不明だとしています。

Cでは、_maybe_alwaysのサフィックスがアノテーションとしての重要な部分である述語に対してノイズになっています。

最後のDはP3400R2で提案中のラベル指定との互換性があるものです。しかし、設定可能アサーションの方が強制アサーションよりも短くなります。また、P3400はまだ設計中であり、構文についてはもこのまま導入されるかはわかりません。例えば次のようになる可能性があります

void fun(int * p)
  pre<std::enforce> (p != nullptr);

// または
void fun(int * p)
  pre [[=enforce]] (p != nullptr);

このようになる(あるいはほかの形)場合、構文の一貫性が無くなり混乱を招くことになります。

Bには2つの利点があります

  • 特定の動作をデフォルトにしないため、常に選択する必要があり追加の入力も軽微
  • assertマクロのために使用できなかったassertという名前に関する問題を解決できる

ただし、これによりpreのようなサフィックスなしのものが永遠に存在しなくなります。

NSAの言う安全性の観点から、安全なアサーションは最低限次の2つの点が保証されている必要があります

  1. アサーション内の述語が少なくとも一回評価されること
  2. アサーションによって保護された命令の実行に制御が進まないこと

この要件を満たしただけだとまだ実装依存の動作がいくつか許容されます。最も顕著なのはenforceとquick_enforceのどちらのセマンティクスを選択するかです。enforceセマンティクスでは必ずしもプログラムの終了が保証されないため、強制アサーションの目的を明確化する必要があります。

  • アサーションによって保護されたコードを実行しないこと?
  • どのコードも実行しないこと(無条件終了)?

強制アサーションにおいては、設定可能アサーションで議論の的になっているいくつかの側面について別の選択をすることもできます。

  • アサーションの複数回の評価
  • const
  • 条件式からの例外送出の契約違反への変換
  • 条件式で副作用を許容する

これらの決定には議論の余地があり、強制アサーションと設定可能アサーションの間で異なる設計上の選択をすることはそれはそれで大きな議論を呼ぶことになります。また、P3400のラベル機能が採択された場合、ほぼ同じ事を行うために複数の方法が存在することになります。

ただ、このような強制アサーションにおいて使用される式がある程度単純なもの(標準ライブラリの堅牢化事前条件のように)に留まる場合、別の選択をすることも可能かもしれません。例えば、条件式が例外を送出しうる場合をコンパイルエラーにするなどです。

強制セマンティクスにおいては条件式の評価回数を正確に一回にとどめることができるようになります。しかし、これには欠点もあります

  • 呼び出し先でアサーションを評価する必要があるため、呼び出し元でエラーを報告する機能が失われる
    • 事前条件違反が発生した場合、呼び出し元を指摘する必要がある
  • プログラマが任意のコード(正当性とは全く関係のない、場合によっては副作用のあるコード)を関数呼び出しの前後に使用することを可能にし、促進する
    • const化が有効でも、副作用を及ぼすことは可能

この提案は設計空間の検討を行うのみで、特定の選択を提案してはいません。むしろ、このようなアサーションをここで入れることには反対の雰囲気があります。

P3947R0 identifier_of Should Return std::string

文字列を返すリフレクションのメタ関数の戻り値型をstd::stringにする提案。

std::meta::identifier_ofなどの文字列を返すメタ関数の戻り値型はstd::string_view(もしくはstd::u8string_view)であり、これらの関数では返される文字列はnull終端が保証されることが明記されています。

struct C { };

constexpr string_view sv = std::meta::identifier_of(^^C);
static_assert(sv == "C");
static_assert(sv.data()[0] == 'C');
static_assert(sv.data()[1] == '\0');  // ok

ただしこのことは、既存のstd::string_viewの使用習慣と矛盾しています。通常std::string_viewはnull終端されていないものとして扱うことが推奨され、教えられており、標準ライブラリにおけるこれらの関数はその習慣とは真逆の事をしています。

また、コンパイラを始めとしたツールがstd::string_viewを介したアクセスの際にそのサイズを超えた場所を参照するようなコードを検出しようとする場合、標準規格が保証しているこれらのコードがFPとして検出されてしまうため、ユーザーかツールのどちらかが対応しなければならなくなります。

さらに、str[str.size()]のようなnull文字へのアクセスはstd::string_viewの事前条件違反であり、定数式では使用できません。ここにアクセスしたい場合はstr.data() + str.size()と書く必要があります。

リフレクションのAPIの他の部分を見てみると、std::vector<std::meta::info>を返す関数(members_of()など)ではstd::span<std::meta::info>ではなくstd::vectorを返しています。このことはstd::string_viewを返す関数と一貫していません。

この提案は、この問題の解決として、これらの関数でstd::string_viewを返す代わりにstd::stringを返すようにすることを提案しています。

対象となるのは次の関数です

  • identifier_of()
  • symbol_of()
  • display_string_of()

また、代案としては次の方法も挙げています

  • null終端保証を削除する
  • C++29のstd::cstring_viewを待つ

これらの関数がstd::string_viewを返すようになっているのは、これらの関数を次のように記述するためだったようです

constexpr auto name = identifier_of(r);

これは、コンパイル時の動的メモリ確保の制約(非一時的なアロケーションが許可されていない)により、std::stringを返す場合にコンパイルエラーとなります。しかし、このためのソリューションとしてdefine_static_string()が用意されているため、将来std::stringの非一時的なアロケーションが許可されるまではdefine_static_string()を使用することを推奨しています。

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

P3948R0 constant_wrapper is the only tool needed for passing constant expressions

コンパイル時定数を関数の引数として渡す方法について、std::constant_wrapperを一貫して使用するようにする提案。

C++26で追加されたstd::constant_wrapper<V>はコンパイル時定数をNTTPとして保持して扱うための型で、特にconstexpr引数を実現するためのライブラリ機能です。

std::constant_wrapper<V>はほとんどすべての演算子をオーバーロードしていることによって、保持する値Vに対する透過的な操作を提供しつつ、それらの操作の結果が再びstd::constant_wrapper<V>を返すことで値が型の文脈に留まり続けるようにします。

一方で、C++26のstd::function_refではメンバ関数ポインタのサポートのためにstd::nontypeというNTTP値タグ型を導入しています。これはstd::nontype<+f>のようにして関数ポインタをNTTP値としてstd::function_refのコンストラクタに渡すためのもので、NTTP値として渡すことによって関数ポインタのストレージを省略し別の用途(メンバ関数呼び出しに必要な*thisオブジェクトの参照)に使用するためのものです。

std::nontypestd::constant_wrapperと同じ目的であるためそれを統一すべきという議論があったものの、std::constant_wrapperが関数呼び出し演算子もオーバーロードしていることによってstd::constant_wrapper<+f>のように渡した時にこの渡したものそのものが呼び出し可能になってしまうという問題があったことからstd::nontypestd::function_refのコンストラクタ専用のタグ型として残されることになりました(このとき同時にNTTPという用語が変化したことによる名称変更があり、最終的にstd::constant_argになった)。

結局現在のところ、constexpr引数を実現するための方法が2種類存在しています。基本的にはstd::constant_wrapperを使うべきですが、なぜ2種類あるのかやその使い分けなどは学習に当たってのノイズとなります。

この提案は、この現状に対してstd::constant_wrapperへの一本化を提案するものです。

std::function_refstd::nontypeの代わりにstd::constant_wrapperを使う際の問題点はP3792R0で説明されていますが、それは簡単に言えばstd::cw<f>fに同じ引数を与えて呼んだときに最終的な呼び出しの結果(戻り値)が異なる場合に、それを関数ラッパに入れるとstd::function_refとそれ以外の関数ラッパで呼び出し結果が異なってしまうことです。

この提案ではこれに対して、そのような曖昧さが生じる場合を検出してコンパイルエラーにすることを提案しています。

std::function_refstd::constant_wrapperを受け取るコンストラクタ(現在はstd::constant_argを受け取っている)において、渡されたstd::constant_wrapperf0に対してArgsが0より大きい場合、次のどちらかを満たすことを適格要件(Mandates)として要求します

  • f0(declval<ArgTypes>()...)がWell-formedではない
  • f0(declval<ArgTypes>()...)の型がstd::constant_wrapperの特殊化ではない

ここでのf0std::constant_wrapper<f>として渡されてきたものであり、まずArgsstd::function_ref<R(Args...)>)を直接渡して呼べないのであれば問題ありません(std::constant_wrapperの呼び出しは引数もstd::constant_wrapper互換である必要がある)。呼べてしまう場合でも、その戻り値型がstd::constant_wrapperでなければ問題ありません(std::constant_wrapperの呼び出し結果はstd::constant_wrapper)。

Mandates違反はSFINAEしないので、この曖昧さを検出したら即座にコンパイルエラーになります。

これにより、std::function_refに対してstd::cw<f>を渡した際にfの呼び出しをラップしたいのか、std::cw<f>の呼び出しをラップしたいのか曖昧になる、という問題が解消されます。そしてこれをもって、この提案ではstd::constant_wrapperを一貫して使用し、std::constant_argを削除することを提案しています。

おわり

この記事のMarkdownソース

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

文書の一覧

全部で74本あります。

もくじ

N5021 2026-11 Búzios Meeting Information (rev. 1)

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

以前のもの(N5020)の更新版のようです。

N5022 WG21 agenda: 3-8 November 2025, Kona, HI, USA

2025年11月にコナで行われる会議のアジェンダ。

N5023 2025-11 Kona meeting information (rev. 1)

2025年11月にハワイのコナで行われる全体会議のインフォメーション。

以前のもの(N4977)の更新版のようです。

N5024 2026-03 London meeting information

2026年03月にイギリスのロンドンで行われる全体会議のインフォメーション。

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

N5027 2025-10 WG21 admin telecon meeting (revised 2025-09-29)

11月の全体会議に先立って行われる、WG21管理者ミーティングの案内。

N5028 C++26 CD summary of voting and comments

C++26 CD(N5013)に対して提出されたNBコメントをまとめた文書。

P1708R10 Basic Statistics

標準ライブラリにいくつかの統計関数を追加する提案。

以前の記事を参照

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

  • 統計量のデータ型を変更するためのオーバーロードを削除
  • 平均と分散(および標準偏差)を返す関数は、それらを専用の構造体で返すようになった
  • 尖度関数はその引数(sampleとexcess)を個別の値ではなく構造体として取るようになった
  • アキュムレータオブジェクトはより検討を行うために将来の提案に延期する
  • P2681から共分散の計算を導入

などです。

P2034R5 Partially Mutable Lambda Captures

ラムダ式の全体をmutableとするのではなく、一部のキャプチャだけをmutable指定できるようにする提案。

以前の記事を参照

このリビジョンでの変更は、実装経験の報告について追記したことです。

報告によると、GCCにおいては実装のための変更は大きくなく、短時間で完了したとのことです。

P2728R8 Unicode in the Library, Part 1: UTF Transcoding

以前の記事を参照

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

  • トランプの例を追加
  • utf-iteratorからiterator_interfaceを削除
  • code unit viewをP3117のtransform_viewとexpression-equivalentなRangeアダプタ(クロージャオブジェクト)に置換
  • null_sentinelnull_termをP3705に移動
  • std::uc名前空間を削除し、std::ranges/std::ranges::viewsに置換
  • ライブラリのEBを削除
  • イテレータを親のビューへのポインタを持つように変更し、借用とスタックサイズの二次増加を排除
  • charwchar_tのサポートを削除
  • 提案文書のほとんどの部分を書き直し、図を追加

などです。

このリビジョンでは提案内容そのものは大きく変わっていませんが、ユニコードの構造の説明や現在利用可能なAPIについての説明や、提案している設計の説明などが図やサンプルコードとともに追加されています。

P3086R5 Proxy: A Pointer-Semantics-Based Polymorphism Library

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

以前の記事を参照

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

  • IntroductionとMotivationを改訂
    • 型理論の観点から提案を提示
    • ポインタ中心のモデルを明確にし、現在の仮想ディスパッチとの関係を明確にした
  • 実装経験に基づく用語定義と理論的根拠において、以下の機能を統合
    • substitution_dispatch, proxy_viewobserver_facade
    • weak_proxyweak_facade
  • access_proxyを削除
  • 仮想関数、スマートポインタベースのラッパ、カスタムvtableベースの設計との比較分析を行い、委員会からのフィードバックに対応
  • リファレンス実装には存在するものの標準化は提案されていないヘルパマクロへの依存を削除し、提案するインターフェースを明確化

などです。

Better Lookups for map, unordered_map, and flat_map

連想コンテナからキーによって要素を検索するより便利な関数の提案。

以前の記事を参照

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

  • P2988(std::optional<T&>)がドラフトに統合されたことを反映して提案を更新
  • 説明を連想コンテナ要件セクションに移動することを推奨する編集コメントを追加
  • unordered_map::getに複雑度について追記

などです。

P3099R0 User-defined error messages for contract assertions

契約アサーションにユーザーがエラーメッセージを指定できるようにする提案。

C++26 Contractsでは、3種類の契約アサーションはいずれも契約条件式のみを取り、契約違反が発生した場合はデフォルトの違反ハンドラによって何かしらの診断メッセージが発行されます(おそらく、違反を起こしたアサーションのソースコード位置と条件式の文字列化などの情報が出力される)。

これらのアサーションにユーザーが追加のエラーメッセージを指定できると、出力されたエラーメッセージからその契約違反が発生した原因や解決方法などを素早く理解するために役立ちます。Cのアサートもそのような機能を持っていませんでしたが、assert(expr && "Reason")の様なワークアラウンドによって実質的に同じことが可能でした(これは契約アサーションでも可能です)。また、多くの非標準のアサーションライブラリではそのような追加の診断メッセージ指定をサポートしている場合がほとんどです。

また、C++26 Contractsの場合は違反ハンドラのカスタマイズによってそのようなエラー情報の扱いをユーザーが変更することができるため、違反ハンドラからでもユーザー指定のメッセージにアクセスできる必要があります。

ClangによるC++26 Contractsの先行実装では、ベンダー属性を利用してこれをサポートしています。

T& operator[] (size_t i) 
  pre [[clang::contract_message("Out-of-bounds access")]] (i < size()); 

この提案は、このclangの実装経験を参考にしつつ、契約アサーションにおいてユーザー指定のエラーメッセージをサポートしようとするものです。

提案では、エラーメッセージの指定方法はstatic_assertと構文的にも意味的にも同等なものになるようにしています。

T& operator[] (size_t i)
  pre (i < size(), "Out-of-bounds access");

3種類のアサーションすべてで、contract_assert(expr, message)のように契約条件式の後にエラーメッセージを指定します。この構文は、static_assertと一貫することに加えて条件式の前にメッセージが来ない事や構文ノイズが少ないことなどから選択されています。

このエラーメッセージはコンパイル時の文字列である必要があり、C++26のstatic_assertのエラーメッセージとして有効な文字列と同じものを受け付けます(文字列リテラルだけでなく、コンパイル時std::stringなどを渡せる)。実行時文字列をサポートしようとすると、契約違反が起きてから違反ハンドラが呼び出されるまでの間にユーザーコードを実行する必要性が生じてしまい、これはセキュリティリスクの懸念があるためここではサポートを延期しています(P3819R0のcontract_violation::evaluation_exception()と同じ理由)。

違反ハンドラの引数であるstd::contracts::contract_violationオブジェクトからは.message()という新しいAPIによってこのメッセージを取得できるようにします。

namespace std::contracts {
  class contract_violation {

    ...

    const char* message() const noexcept; 
    
    ...
  };
}

デフォルトの違反ハンドラおよび定数式における契約違反時の診断メッセージについては、このユーザー指定エラーメッセージを含めるようにすることを推奨事項としています(現在でも診断メッセージの出力は推奨事項)。

この提案によって、static_assert(expr, message)は定数評価中にenforceセマンティクスで評価されたcontract_assert(expr, message)と同等になるようになります。また、contract_assert(false, message)とすることで定数評価中に任意のメッセージを出力できるようにもなり、P2758R5で提案されている機能の一部を包含してもいます。

P3255R2 Expose whether atomic notifying operations are lock-free

std::atomicの通知・待機系関数がロックフリーとは限らない場合がある問題について修正する提案。

以前の記事を参照

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

  • 新しく見つかったaddress-freedom問題への対応のためにSG1に戻した
  • ターゲットをC++29へ変更
  • 提案の一部がDRであることを明確化し、文言を分割
  • 既存標準ライブラリ実装の説明を追加
  • 標準に従って、操作がロックフリーであるという意味についての説明を追加
  • address-freedom問題に関する説明を追加
  • ロックフリー操作をアドレスフリーにするという推奨はatomicの通知・待機操作には適用されないことを明確化

などです。

このリビジョンでは結局次の事を提案しています

  • std::atomic_flagの通知・待機操作は、既存の慣例に従ってロックフリーである必要がないことを明確化する
    • これは欠陥報告(DR)
  • is_lock_free, is_always_lock_free, std::atomic_is_lock_freeは、既存の慣例に従ってatomicの通知・待機操作には関連しないことを明確化
    • これもDR
  • std::atomic_flag, std::atomic, std::atomic_refに対して次のメンバを追加する。これらの値はatomicの通知・待機操作がロックフリーであるかどうかを示す
    • .notify_is_lock_free()
    • std::atomic_notify_is_lock_free()
    • ::notify_is_always_lock_free
  • std::atomic_flag, std::atomic, std::atomic_refに対して次のメンバを追加する。これらの値はatomicの通知・待機操作がシグナルハンドラ内で(UBなしで)使用できるかを示す
    • .wait_is_signal_safe()
    • std::atomic_notify_is_signal_safe()
    • ::notify_is_always_signal_safe
  • ロックフリーな通知操作がアドレスフリーであることを期待しないものとするように、標準規格における推奨プラクティスを更新

このリビジョンで言及されるようになったアドレスフリー(address-freedom)とは、アトミック操作の実装がアトミックオブジェクトのアドレスに依存しないようになっていることを言っており、標準規格においては推奨プラクティスとしてロックフリー操作がアドレスフリーであることを推奨しています。

しかし実装を調査したところ、atomicの通知・待機操作はロックフリーであってもアドレスフリーにならない場合があり、特に通知操作はそうならないであろうことが判明しました。ただしアドレスフリーとシグナルセーフはまた直交した概念であるため、アドレスフリーであってもシグナルセーフは達成できる場合があり、このために専用のクエリを導入しています。

P3351R3 views::scan

状態を持つ関数を使用可能なviews::transformであるRangeアダプタ、views::scanの提案。

以前の記事を参照

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

  • C++29をターゲットに変更
  • partial_sumエイリアスを削除
  • Categoryのセクションに、イテレータのスタッシュ、ムーブのみの型、scan_viewにおける無条件input_rangeと条件付きforward_rangeの選択に関する議論などを追加
  • scan_viewを条件付きborrowed_rangeとすることの実現可能性に関する議論を追加
  • Beman Projectにおける実装経験を追加
  • P2846R6が導入されたWDにリベース
    • LWG4189が採択されたため、freestandingセクションを削除
  • その他編集上の修正

などです。

P3385R6 Attributes reflection

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

以前の記事を参照

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

  • P3678R0を統合
  • appertainを削除
  • has_attributeメタ関数を削除

などです。

P3401R1 Proxy Creation Facilities: Enriching Proxy Construction for Pointer-Semantics Polymorphism

Proxyを構築するユーティリティ関数の提案。

以前の記事を参照

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

  • オブザーバー型または所有型の監視を通して許容可能なターゲットとしてラップできる値型と、既にプロキシ可能なポインタlike型などを区別するために、proxiable_targetコンセプトを追加
  • テンプレートパラメータ制約を強化し、以前は単にclass Fだったところをfacade Fにした
  • 共有所有権の作成(allocate_proxy_shared, make_proxy_shared)と非所有ビューの作成(make_proxy_view)を追加
  • フリースタンディング対応の明確化
    • 割り当てが必要な関数以外はフリースタンディングで利用可能
  • ill-formed条件を、置換エラーではなくproxiable_target / inplace_proxiable_targetを使用して指定する

などです。

P3411R4 any_view

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

以前の記事を参照

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

  • デフォルト構築されムーブされたオブジェクトは空のビューに割り当てられるようにした

などです。

P3505R1 Fix the default floating-point representation in std::format

std::format()における浮動小数点数出力時のデフォルトの表現(gオプション)を修正する提案。

以前の記事を参照

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

  • 下限値と上限値は引数の型の値として表現可能な最も近い値であることを文言で明確化
  • 文言では絶対値を使用する
  • PythonとC++で同じ数値を使用することで、前者は簡潔性("shortness")の要件を満たし後者は満たさないことを明確化

などです。

LEWGのレビューにおいては、この提案の内容をDR(to_chars()関連はC++17、format関連はC++20)とすることで合意が取れているようです。

P3567R2 flat_meow Fixes

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

以前の記事を参照

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

  • 文言の修正

などです。

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

P3578R1 What is "Safety"?

安全性(Safety)という言葉の定義を明確にし、その使用方法に注意を払う事を推奨する提案。

以前の記事を参照

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

  • “Grandma Safety”というフレーズを削除し、表現を簡素化

などです。

P3584R1 Proxy Facade Builder: Enriching Facade Construction for Pointer-Semantics Polymorphism

P3086R3からfacade_builderというクラステンプレートだけを導入する提案。

以前の記事を参照

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

  • basic_facade_builderの単一のテンプレート引数proxiable_ptr_constraints(集成体型NTTP)を明示的な個別のNTTPに分割(MaxSize, MaxAlign, Copyability, Relocatability, Destructibility
  • relocatabilityのデフォルトをtrivialに変更
    • nothrowを暗黙的に指定していた文言はそのまま
  • add_facadeのセマンティクス改訂
    • add_facadesubstitution_dispatch(オプトインのbool値)を介して置換のための直接的な規約を挿入するようになった
  • basic_facade_builder::add_skillを追加
    • ボイラープレートの削減とファサードのモジュール化の促進における役割を説明
  • 冪等性ルールを明確化
  • 後方互換性と最小限のバイナリサイズのバランスを取るために、明示的に置換をオプトインする根拠を説明

などです。

P3603R1 Consteval-only Values and Consteval Variables

consteval変数の提案。

以前の記事を参照

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

  • 実装経験の追加
  • not_fnの例を追加
  • 文章の拡張
  • リフレクションの採用に伴う文言の更新

などです。

P3606R1 On Overload Resolution, Exact Matches and Clever Implementations

関数テンプレートを含めた関数オーバーロードの解決において、GCCの実装戦略を標準化する提案。

以前の記事を参照

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

  • 提案する文言の追加

などです。

P3643R2 std::to_signed and std::to_unsigned

整数型を対応する符号付/符号なしの整数型に簡易に変換する関数の提案。

以前の記事を参照

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

  • §2.2. Dual purpose を追加
  • §4. Design と §5. Possible implementation の順番を変更
  • §4. Design に制約についての議論を追加

などです。

P3655R3 cstring_view

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

以前の記事を参照

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

  • revision historyを追加
  • "discussion on type", "design rationale", "historical information", "wording"の順に並べ替え
  • 以前のアンケートセクションの削除
  • SG23/LEWGのフィードバックに従い、提案の範囲を絞り込み
  • Beman Projectにおける実装経験について追加
  • コンストラクタの説明を拡充
  • nullptrコンストラクタを削除
  • 空コンストラクタを追加
  • コンストラクタが変換演算子よりも優先されるため、string_viewcstring_viewの関係を書き換え
  • 文言を拡充

などです。

P3663R3 Future-proof submdspan-mapping

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

以前の記事を参照

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

  • 著者リストを更新
  • LWGレビューによる文言の改訂
    • 文言はより数学的な表現になり、実装の説明をあまり行わなくなった
    • 以前のリビジョンで提案されていた説明専用関数や既存の説明専用関数は、リネーム・置換または削除された
    • “Unit-stride slice”はレイアウトマッピングではなくIndexTypeにのみ依存し標準スライス型にのみ適用されるように再定義
  • 文言の変更を反映するために、それ以外のセクションを更新

などです。

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

P3666R1 Bit-precise integers

C23の_BitIntをC++に導入する提案。

以前の記事を参照

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

  • N3699の公開に伴い、§4.5. _BitInt(1) を更新
  • §5.11. make_signed and make_unsigned を追加し、対応する文言を変更
  • 列挙型の基底型として_BitIntを許可
    • §4.3. Underlying type of enumerations を参照
  • 基底型として_BitIntを持つ列挙型の昇格を考慮
  • § [diff.lex] Annex C に0wbの型の違いに関するエントリを追加

などです。

P3669R3 Non-Blocking Support for std::execution

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

以前の記事を参照

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

  • 別の例を追加
  • 実装リンクを追加
  • 文言の修正
  • 命名に関する議論を追加
  • P0260の変更を削除

などです。

P3684R0 Fix erroneous behaviour termination semantics for C++26

erroneous behaviourにおける終了動作についての調整を行う提案。

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

最終的なC++26仕様においてEBの動作としては、実行時にEBが起こる場合に実装は診断メッセージを発行したうえでその後(未規定の時間経過後)プログラムを終了する、という動作が許可されています。これにより、EBが発生するとその後プログラムはエラー状態になり、その後任意の時点で突然終了する可能性があります。

C++26のEB(未初期化変数読み取り)に関する現在の実装には2つの方向性があります

  1. 自動変数をゼロないし固定のパターンを持つ値に初期化することでその読み取り時(EB時)に安全な値を読み出すようにする
    • GCC/Clangが-ftrivial-auto-var-initを介して提供している
    • この実装では未定義動作は排除されるものの、推奨されるEBの診断動作を全く行わない
  2. EBとなる未初期化変数読み取りを実際に検出する
    • Memory Sanitizerによる実装
    • EB(未初期化変数読み取り)の発生後しばらく経つと診断メッセージを発行して終了する

C++コードが最適化後の機械語命令列にどのようにマッピングされるかは直観的に理解しにくい場合が多いため、未初期化変数読み取りの様なチェックは通常コンパイル後のコードへの検出機能の組み込みによって行われます(Sanitizerがまさにそうです)。

特に、未初期化変数読み取りが発生する場所はその後に続く計算処理に巻き込まれることで、ソースコード上で特定できる場所よりも後の計算処理内部であることがあり、診断とプログラム終了もその場所で行われることになります。このような実装戦略を反映しているのがC++26の規定にある遅延終了セマンティクスです。

ただし、Memory Sanitizerの実装はEB発生後その特定を無限に遅延するわけではありません。あくまで、実際のソースコード位置よりもかなり離れた場所で検出と診断・プログラム終了が行われる可能性がある、というだけです。Memory Sanitizerはその実装として、未初期化変数読み取りの結果未初期化である値が原因として異なる動作をした評価を捕捉することに重点を置いています。

そのため、Memory Sanitizerの動作を適合実装として維持するためにEB発生後のプログラムがいつでもそのEBを根拠として終了できる許可を与える必要はありません。現在の規定では、EB発生とその検出後にタイマーを設定し任意の時間経過後に突然プログラムを終了するような異常な実装は不必要に許可されています。このような動作はソフトウェアの予測可能性と信頼性の向上に対して逆行する行いであり、考慮する必要はありません。

また、P3100R4では未初期化変数読み取り以外のUBについて、その妥当なデフォルト動作をEBとして置換しようとしていますが、これらのUB(EB)の場合は遅延終了が意味を持たないものがあり、遅延終了を許可することでEBの結果として生じる誤った値(Erroneous Value)とP3100R4で提案されている暗黙の契約違反との間の意味の一貫性の確立が妨げられます。

この提案では、このような問題の解決のためにEB発生後の遅延終了を削除することを提案しています。その代わりに、EBによって生成されるErroneous Valueがそれを使用する演算等を介して伝播するようにしています。

結果がErroneous Valueに依存するような操作はすべてErroneous Valueを生成するため、そのような操作はすべてEBとなります。実装は最初にEBが起きた場所ではなく、このようにErroneous Valueの伝播とともに発生するEBのどこかでEBの検知とプログラム終了という動作を取ることができます。これはMemory Sanitizerの実装動作ですが、GCC/Clangの実装動作のように最初に読みだすErroneous Valueを特定の値に初期化し、あとはそのまま続行するという動作も引き続き許可されます。

現在の実装ではErroneous Valueを読み取るとその値はクリーンアップされて有害ではなくなるというアプローチをとっているものの、その代償にプログラム全体が有害な状態になってしまっています。Erroneous Valueがデータとともに伝播するようにすることで、問題のあるデータの範囲をプログラム全体からErroneous Valueを扱うプログラムの一部に限定し現実的な実装をカバーするのに十分な程度にErroneousな範囲を広げつつ、EB発生後任意のタイミングでの突然の終了の様な動作を拒否することができます。

すなわちこの提案によって、EBの発生したプログラムはエラー状態に陥り予期しない終了の脅威に晒され続ける、ということは無くなります。ただし、Memory Sanitizerの実装のように、最も都合の良いタイミングでEBを検知し終了するための最大限の柔軟性を提供しています。

この提案の後では、EBについて次のような性質が実現されます

  • Memory Sanitizerなど、未初期化の値の使用をトラップする既存の実装は適合実装のまま
    • 何らの変更を加えることなく現在の動作を維持できる
  • プログラムがEBによって終了した場合、プログラム開始から終了までの間でEBを発生させた可能性のある実行の全てを特定することなく、終了の理由を局所的に推論できるようになる
  • プログラムが実行状態にあり、EBを発生させるような操作を行っていない場合、それより以前のEBによって終了しないことが保証される

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

P3688R4 ASCII character utilities

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

以前の記事を参照

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

  • get_hex_digit_value()の例のバグを修正
  • §3.12. namespace ascii に設計に関する説明を追加

などです。

P3695R2 Deprecate implicit conversions between char8_t and char16_t, char32_t, or wchar_t

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

以前の記事を参照

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

  • char8_twchar_tの間の変換も非推奨にする
  • この方向性を反映してタイトル等を変更
  • §2.1. It's not hypothetical. This really happens のトートロジーに関する警告の注記を拡充
  • §3.6. Why not make these conversions narrowing? を追加
  • 文言の編集

などです。

P3702R3 Stricter requirements for document submissions (SD-7)

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

以前の記事を参照

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

  • 文書のアクセシビリティ要件を明確化(WCAG AA)
  • must を shall に変更
  • UTF-8エンコーディング要件を should から shall に変更
  • サポート対象フォーマットから、markdownとプレーンテキストを削除
  • その他の文言の修正

などです。

P3724R1 Integer division

商と剰余を様々な丸めモードで計算するライブラリ関数の提案。

以前の記事を参照

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

  • §4.6. SIMD overloads を追加
  • N5014にリベース

などです。

P3732R1 Numeric Range Algorithms

<numeric>ヘッダにある数値アルゴリズムのRange版を追加する提案。

以前の記事を参照

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

  • reduce_into, sum_into, product_intoアルゴリズムを追加
  • 文章の修正
    • reduce_into, transform_reduce_into, sum_into, product_into, dot_intoを特殊なケースとして説明
    • identity value(単位元に対応する値)を指定するための様々な設計の提示
    • reduce_firstは不要であると結論付ける

などです。

reduce_into, transform_reduce_intoranges::reduce/ranges::transform_reduceと同様に動作するものの、結果の値を返すのではなく出力範囲の最初の要素に書き出すものです。そして、sum_into, product_into, dot_intoは同名のranges::reduce/ranges::transform_reduceラッパに対するreduce_into, transform_reduce_intoのラッパです。

namespace std::ranges {

  // reduce_intoの宣言例
  template<sized-forward-range IR,
           sized-forward-range OR,
           class T = range_value_t<IR>,
           indirectly-binary-foldable<T, iterator_t<IR>> F>
  constexpr auto reduce_into(
    IR&& in_range,
    OR&& out_range,
    T init,
    F binary_op) -> /* see below */;
}

出力は範囲かつsized_rangeとして取っていますが、書き出すのはその最初の要素のみです。

P3733R1 More named universal character escapes

名前付きユニバーサル文字名のエイリアス名について、C++で使用可能ではないものを使用できるようにする提案。

以前の記事を参照

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

  • §3.3. Category stability guarantees を追加
  • この提案をDRとする根拠を追記

などです。

この提案は、SG16およびEWGにおいてDRとすることに合意が取れています、

P3737R1 std::array is a wrapper for an array!

std::arrayの実装自由度を制限する提案。

以前の記事を参照

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

  • §4. Design considerations で全体的な設計戦略を策定
  • array<T, 0>::front()/array<T, 0>::back()に関する変更を削除
    • LWG4276の採択を仮定している
  • ビットフィールドメンバをstd::arrayから削除
  • std::array<T,0>のトリビアルコピー可能性とトリビアルデフォルト構築可能性を無条件にした

などです。

P3739R3 Standard Library Hardening - using std::optional<T&>

std::inplace_vectorにおいて、std::optional<T&>を利用して堅牢化モードを導入する提案。

以前の記事を参照

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

  • 歴史的な議論に文脈を追加
  • Frequently Asked Questions セクションを追加

などです。

P3779R1 reserve() and capacity() for flat containers

std::flat_map等のflatなコンテナに、reserve()capacity()メンバ関数を追加する提案。

以前の記事を参照

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

  • 提案する文言を追加

などです。

P3786R1 Tuple protocol for fixed-size spans

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

以前の記事を参照

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

  • フィードバックにより設計上の問題を修正

などです。

P3800R0 2025-07 Library Evolution Poll Outcomes

2025年7月に行われた、LEWGにおける投票の結果。

次の提案が投票にかけられ、C++29に向けてLWGに転送されました。

また、投票の際に寄せられたコメントが記載されています。

P3804R0 Iterating on parallel_scheduler

parallel_schedulerの設計を洗練させるための提案。

P2079R10で提案されC++26にマージ済みのparallel_schedulerは、投入された処理の並行実行をサポートするschedulerです。提案段階でもかなり時間をかけて議論されていたのですが、WDへ入った後にも設計の懸念がいくつかあるようで、この提案はそれを改善することを目指すものです。

ここで提案されているのは次のことです

  • 軽微な設計の修正
    1. receiver_proxy::try_queryconst修飾できる
      • 現在されていないが、できない理由はない
    2. receiver_proxyには仮想デストラクタは必要ない
      • 多態的に破棄されることがないため
    3. receiver_proxy::try_queryはストップトークンとしてinplace_stop_tokenしか受け付けず、任意のストップトークンを使用できない
      • inplace_stop_tokenとその他の任意のストップトークンの間に実装定義のマッピングをサポートすることを推奨し、そのようなマッピングが存在する場合はクエリが成功するようにする
      • このようなメカニズムは他のプロパティにも役立つ可能性がある
    4. receiver_proxy::try_queryがサポートするプロパティのリストが実装定義となっていること
      • これは次のユースケースのサポートを意図したもの
        1. 実装者がサポートするプロパティを追加したい場合
        2. 実装者が特定のプロパティをサポートしたくない場合
      • プロパティのリストを厳密に規定してしまうとどちらもサポートできなくなってしまうため、この提案では現状維持を推奨している
  • より影響の大きい設計上の懸念事項
    1. receiver_proxy::try_queryでは、サポートされるクエリのリストを定義する必要がある
      • parallel_schedulerのバックエンドが個別のライブラリとして別にコンパイルされる場合に、receiverのプロパティ(環境とそのクエリ)を解決する方法がない
      • 現在のところそのような実装は確立していないため、現状維持
  • 命名について
    1. system_context_replaceabilityは置換可能性APIが含まれる名前空間に使用する適切な名前ではない
      • parallel_schedulerは以前はsystem_schedulerという名前で、system_context_replaceabilityはその時の名残が残っている
      • いくつか名前の候補を提示してLEWGに委ねる
  • 文言の不足
    1. parallel_schedulerにおけるbulk_unchunked/bulk_chunkedのカスタマイズに関する文言が十分に明確ではない
      • bulk_unchunked/bulk_chunkedのカスタマイズが必須と指定されているがその詳細は説明されていない
      • また、カスタマイズ部分では実行ポリシーが考慮されていない
      • P3862R0を基礎とするソリューションを構築する(後のリビジョン?

この提案はC++26に間に合わせようとはしていませんが、関連するNBコメントが提出されているようです。

P3810R1 hardened memory safety guarantees

標準ライブラリ内の基本的な操作について、未定義動作の余地がないことを規定しておく提案。

以前の記事を参照

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

  • コピペによるエラーを修正

などです。

P3824R1 Static storage for braced initializers NBC examples

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

以前の記事を参照

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

  • インライン参照をより明確化
  • 提案する文言の追加

などです。

P3826R0 Defer Sender Algorithm Customization to C++29

senderアルゴリズムのカスタマイズをC++29まで延期する提案。

C++26では、std::executionと呼ばれる並行・非同期処理の構成や実行制御のためのフレームワークとなるライブラリが導入されています。std::executionでは、実際の非同期処理を構成するためにsenderアルゴリズムというものが用意されており、これはsenderという抽象によって表現される非同期処理のより柔軟な構成をサポートするための共通部品となるものです(STLにおけるイテレータに対するアルゴリズムとの対応から、senderアルゴリズムと呼ばれます)。

namespace ex = std::execution;

// scheduler: 処理の実行場所を指定する
ex::scheduler auto sc = ...;

// sender: 非同期処理を構成する
ex::sender auto async_work =
  ex::starts_on(sc, ex::just(10)) | 
  ex::then([](int n) {
    return n * n;
  }) |
  ex::then([](int n) {
    std::println("{}", n);
    return n;
  });

// receiver: 非同期処理の結果を受信するコールバック
ex::receiver auto re = ...;

// operation_state: 実行可能な状態の非同期処理
ex::operation_state auto os = ex::connect(async_work, re);

// 非同期処理の実行開始
ex::start(os);

// 終了の待機(receiver/operation_stateによって方法が異なる
re.wait();

// 結果の取得(receiverから取得する、receiverによって方法が異なる
auto n = re.result();

ここでのex::justex::thenなどがsenderアルゴリズムです。このほかにもいくつかのsenderアルゴリズムが用意されており、このように|によってチェーンすることで非同期処理の構成をサポートします。

これらのsenderアルゴリズムはそれぞれがCPOとして定義されており、ユーザーの用意したsenderによるカスタマイズをサポートしています。これはユーザーの実行環境や要件に対応したより効率的なsenderアルゴリズムの処理を可能にするための機構であり、ハードウェアベンダやその利用者が利用することを想定しています。

通常、senderアルゴリズムのカスタマイズはその目指す方向性からschedulerに対して行われますが、senderチェーンによる非同期操作ではschedulerは上から入れる(上記例のように)ことも、下から入れる(チェーンの下方、あるいはreceiver経由)こともできます。当初のsenderアルゴリズムは上からのカスタマイズしか考慮していなかったものの、下から入れる場合のカスタマイズ(Late customization)をサポートするために途中で変更されています(P2999R3)。

しかし、その設計はまだ安定しておらず、その後もsenderアルゴリズムのカスタマイズについては問題点がいくつか指定されています(P3718R0など)。これらの修正は可能ではあるもののその設計検討にはさらに時間をかける必要があり、C++26のタイムフレームの中で行うのは困難です(この提案の提出時点でC++26の機能追加フェーズは終わっています)。

そのため、この提案はsenderアルゴリズムのカスタマイズをC++26ではオフにしておいて、カスタマイズについてより堅牢な設計をC++29で再導入しようとするものです。

P3718R0(未適用)時点でのsenderアルゴリズムのカスタマイズではsenderアルゴリズムの呼び出し時(Early customization)とreceiverとの接続時(Late customization)の2か所でのカスタマイズの検出とディスパッチをサポートしています。前述のように、カスタマイズはschedulerに対して行われるため、senderアルゴリズムは自身がどこで(どのschedulerで)実行されて完了するかを知る必要があります。

しかし多くの場合、senderは自身がどこで開始されるかを知らない限り、どこで完了するかが分からない、という問題があります。例えば、ex::just()はどこで開始されてもその場所で完了するものの、どこで開始されるかは単にex::just(10)の様に呼び出された時点(Early customization時)には分からず、Late customizationを待つ必要があります。

また、ex::then(sndr, fn)の場合もそれは同様な場合があり(sndrによる、例えばex::just()の場合など)、その場合は呼び出し時点のカスタマイズは行われずLate customization時にreceiverの持つ情報に基づいて実行開始場所を取得し、カスタマイズが行われます。しかし、P3718R0で提案されているAPIではこの場合にこのような情報を取得することができない場合があります。

提案文書より、サンプル

// GPU実行scheduler
ex::scheduler auto gpu = ...;

// GPU上で実行したい処理
ex::sender auto sndr = ex::starts_on(gpu, ex::just()) | ex::then(fn);

// GPU上で実行し結果を待機
std::this_thread::sync_wait(std::move(sndr));

gpuはGPU上で処理を実行するschedulerであり、fnはGPU上で実行されます。そのため、ex::thenはGPU実装のためのカスタマイズを利用する必要があります。P3718R0のAPIではそれは次のように判定されます

  • Early customization時、starts_on(gpu, just()) | then(fn)式の実行中、thenCPOはstarts_on(gpu, just())senderに次のように完了する場所をクエリする
auto&& tmp1 = ex::starts_on(gpu, ex::just());
auto dom1 = ex::get_domain(ex::get_env(tmp1));
  • それを受けてstarts_onsenderは次のようにjustsenderに完了する場所をクエリする
auto&& tmp2 = ex::just();
auto dom2 = ex::get_domain(ex::get_env(tmp2));
  • justの完了場所は開始場所が分かるまで分からないため、ここではその情報は入手できず、dom2default_domainとなり、これがstarts_onのドメインとしてdom1に返される
    • しかしこれは誤りであり、starts_onはGPU上で完了する
  • thenCPOはdefault_domainを利用してカスタマイズの検索を行い、デフォルト実装を使用する
  • thensendersync_waitreceiverに接続されると、Late customizationが試みられる
  • ex::connectsync_waitreceiverthensenderの開始場所を問い合わせる
auto dom3 = ex::get_domain(ex::get_env(rcvr));
  • sync_wait自体は現在のスレッドで開始するため、ここでもdefault_domainが返されて、dom3default_domainとなる
  • このドメインを使用してカスタマイズを検索するため、Late customizationでもthenのGPU実装は検索されない

このように、この例ではGPU上でのthenのためにデフォルト実装(実質的にCPU用のもの)が使用されることになりますが。これは望ましい状況ではありません。

このような問題の解決のための設計はおおむね完了しているようですが(提案のappendixで説明されています)、まだ実環境で動作したものではなく、精査が完了していないようです。このためDRプロセスに委ねることには不安があるようで(C++20 rangeの経験からの様子)、かといってC++26からstd::execution全体あるいはsenderアルゴリズムを削除することはC++における非同機構の導入と採択を実質的に3年遅らせることになります。

そのためこの提案では、senderアルゴリズムのカスタマイズ機構だけをC++26から削除して、それ以外の部分は維持することを提案しています。

これによりカスタマイズに関連する

  • default_domain
  • transform_sender
  • transform_env
  • apply_sender
  • get_domain

などは削除されます。

C++29で復帰する予定のカスタマイズにおいては、Early customizationを削除しLate customizationのみにしたうえで、senderの完了スケジューラ(完了する場所)の問い合わせ時に開始場所についての情報を一緒に渡すようにするようです。

例えば、現在のex::get_completion_schedulerクエリを拡張して、senderreceiver両方の環境を渡せるようにします。

auto sch = get_completion_scheduler<set_value_t>(get_env(S), get_env(R));

Late customizationは実質的にex::connectによって呼び出されるconnect操作のカスタマイズによって行われ、C++29ではex::connectは次の2種類のドメインを計算します

  • 開始ドメイン(Starting domain): get_domain(get_env(rcvr))
  • 完了ドメイン(Completion domain): get_completion_domain<set_value_t>(get_env(sndr), get_env(rcvr))

これらの情報を利用して、connectフェーズにおいて正確なカスタマイズの検出とディスパッチを行えるようになります。

P3828R0 Rename the "to_input" view to "as_input"

views::to_inputviews::as_inputにリネームする提案。

views::to_inputは入力のviewのカテゴリをinput_rangeに落とすだけのビューで、要素の変換を伴わないものです。しかし、to_~という名前がranges::toのように各要素がアクティブな変換を受けているように見えるとして、リネームしようとしている提案です。

リネーム先はviews::as_inputを提案しています。

この提案には関連するNBコメントがあるようで、C++26をターゲットにしています。LEWGのレビューにおいては一定のコンセンサスを得ており、適用されそうです。

P3834R1 Defaulting the Compound Assignment Operators

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

以前の記事を参照

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

  • 2番目の引数がconst&で渡される場合、それをムーブしないように変更
  • § 4.2 = default is a function body の議論を拡張し、hidden friendsのステータスを明確化
  • § 6.1 Defining operator+ in terms of operator+= にパフォーマンスに関する議論を追加
  • § 8 Implementation を追加
  • 提案する文言の追加

などです。

この提案はEWGのレビューにおいてリジェクトされているようです。

P3836R1 Make optional<T&> trivially copyable (US NB comment 134)

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

以前の記事を参照

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

  • 共著者の追加
  • US NB comment 134 の参照を追加
  • 文言の書き換え

などです。

P3841R0 Proposal for std::constructor Function Object

コンストラクタを関数オブジェクトとして扱うためのユーティリティの提案。

メンバ関数やフリー関数等通常の関数は、<functional>にあるようなユーティリティやstd::functionとそのファミリーを使用するなどして様々に活用することができます。しかし、これらのものに対してコンストラクタを関数と同じように扱わせることはできません。

コンストラクタを関数のように扱うためにはラッパが必要であり、この提案ではstd::constructor<T>()としてそれを提案しています。

提案文書より、std::size_tの範囲inputがあり、その要素の値によるサイズのstd::vectorstd::vectorを得るような処理を書く例

std::vector<std::vector<int>> result;
result.reserve(std::distance(input));
for (auto sz : input) {
  result.emplace_back(sz);
}

inputinput_rangeである場合に2回のイテレーションが可能ではない場合があり、emplace_back()のループは範囲から直接構築するよりも非効率となります。

rangeベースの現代的なソリューション

auto result = input
    | std::views::transform([] (size_t sz) {
        return std::vector<int>(sz);
      })
    | std::ranges::to<std::vector>();

まだラムダ式が簡潔ではありません。

この提案

auto result = input
    | std::views::transform(std::constructor<std::vector<int>>())
    | std::ranges::to<std::vector>();

std::constructor<T>()はコンストラクタオーバーロードの集合をCallableに変換します。これによって、コンストラクタを関数オブジェクトとして扱うことができ、既に関数オブジェクトを扱うことのできる場所で使用できるようになります。

std::constructor<T>()の宣言例

namespace std {

  template <typename T>
  struct constructor {
    template <typename... Args>
    static constexpr T operator()(Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
    {
      return T(std::forward<Args>(args)...);
    };
  };
}

P3842R0 A conservative fix for constexpr uncaught_exceptions() and current_exception()

C++26では、uncaught_exceptions()current_exception()を定数式で使用できないようにしておく提案。

P3818とP3820で、uncaught_exceptions()current_exception()を単純にconstexpr指定してしまうと破壊的変更となることが報告されており、それぞれ異なる解決策が提案されています。

LEWGでの協議の結果、C++26ではこれらの関数からconstexprを外しておくことで問題が起こらないようにする、ことが決定されました。この提案はそのための文言を提案するものです。

C++26の期限もあるため、代替案の追加や緩和策の導入などはいずれも行われず、C++26では定数式で使用できなくするのみです。

P3843R0 Reconsider R0 of P3774 (Rename std::nontype) for C++26

std::nontypestd::constant_argにリネームしないようにすることを推奨する提案

この提案は、P3774R0で提案されていた(R1で撤回された)std::fnの方向性に賛同し、std::nontypestd::constant_wrapperとは明確に異なるものとしてその方向に進化させていこうとするものです。

経緯等はP3774R0を参照

std::constant_wrapperstd::nontypeはどちらもNTTPを利用したconstexpr関数引数を実現するためのソリューションですが、std::nontypeはほぼstd::function_refにおけるメンバ関数の適切なサポートのためにのみ使用されるものであり、std::constant_wrapperのように汎用的なものではありません。

しかし、std::constant_argという名前はstd::constant_wrapperと非常に良く似ており、一見するとどちらを使用するべきかが分からない所があります。定義などはstd::constant_argの方がシンプルなのでそちらを好んで使用する人もいるかもしれません。これにより、constexpr関数引数という問題に対するソリューションとなる語彙型が2つ存在することになります。そして、それら2つの型の間には一切の互換性がありません。

現在の命名(std::constant_arg)はこの問題について真剣に検討された上でのものではありません。

P3774R0では、std::fnという名前にリネームするとともに、関数呼び出し演算子を備えることでstd::constant_wrapperとは異なる役割を与えようとしていました。

std::fnはコンパイル時に既知の関数のラッパとして、std::function_refstd::nontypeと同様に使用できるほか、次のように他の場所でも使用できます

  • 連想コンテナの比較関数やハッシュ関数の指定のために使用できる
  • アルゴリズムに渡す述語などのcallableとして使用できる

そしてどちらの場合でも、関数ポインタのサイズオーバーヘッドを削減しながらインライン化を可能にします。

これらのことから、この提案ではP3774R0の方向性を再検討することを目指しています。ただし、std::fnという名前には異論もあったことなどから、リネーム先をstd::fnではなくstd::function_wrapperにしています。

P3844R0 Restore simd::vec broadcast from int

std::simdの暗黙変換が可能なブロードキャストコンストラクタを復帰させる提案。

Parallelism TSだったころのstd::simdでは、vec<float>() + 1の様な構築(右オペランド)が可能であり、これはブロードキャストコンストラクタにおいて要素型と異なる型の値(特に整数型の値)を意図的に受け入れるようにしていることによります。

このようなAPIの意図は、浮動小数点演算コードでは* 2のような整数定数倍において* 2.0のように記述するよりもそのまま* 2とすることが多いため、これをそのままstd::simdでもサポートするためです。

float f(float x) { return x * 2; }    // converts 2 to float (at compile time)
float g(float x) { return x * 2.; }   // converts x to double (at run time)
float h(float x) { return x * 2.f; }  // no conversions

これと同等の表現をサポートするために、std::simdのブロードキャストコンストラクタは当初、int型を特別扱いして暗黙変換(縮小変換)を許可するようにしていました。

// 当初のstd::simd
using floatv = std::experimental::native_simd<float>;

floatv f(floatv x) { return x * 2; }    // converts 2 to float and broadcasts (at compile time)
floatv g(floatv x) { return x * 2.; }   // ill-formed
floatv h(floatv x) { return x * 2.f; }  // broadcasts 2.f to floatv

このコードは最終的なC++26 std::simdにおいてそのまま動作しません。少なくともfに対しては変更が必要です。

using floatv = std::simd::vec<float>;

floatv f(floatv x) { return x * std::cw<2>; }

std::simdではこのように、整数定数の様なものを扱う際にはstd::constant_wrapperを使用する必要があります。しかし、これにはコンパイルのコストがかかるため、それが望ましくない場合は2.fのようにfloat値を直接使用することが推奨されます。

ただし、8bitや16bitの整数型を要素としている場合など、対応するリテラルが無い場合は明示的変換を使用する必要があります。

template <simd_integral V>
V f(V x) {
  return x + 1; // ill-formed for V::value_type = (u)int8_t, (un)int16_t, and uint32_t
}

しかし、明示的変換を行うと値を変更してしまうような変換が診断されなくなり、バグの元となります。

template <simd_floating_point V>
V f(V x) {
  return x + V(0x5EAF00D);
}

f(vec<double>()); // OK

// compiles but
// adds 99282960 instead of 99282957
f(vec<float>());

// compiles but
// adds infinity instead of 99282957
f(vec<std::float16_t>());

ここでも、x + std::cw<0x5EAF00D>のように書いておけばこのような変換はエラーになりますが、std::cwは冗長でコンパイルコストも高いため、実際にはほぼ使用されないと思われます。

この提案は、当初のstd::simdが持っていたブロードキャストコンストラクタでの整数型の特別扱いを復帰させて、std::cwを介さなくても同じ結果が得られるようにするものです。

上記の例のこの提案後

template <simd_floating_point V>
V f(V x) {
  return x + 0x5EAF00D; // std::cwも明示的変換も不要
}

f(vec<double>()); // OK

// ill-formed: uncaught exception on
// value-changing conversion
f(vec<float>());

// ill-formed: uncaught exception on
// value-changing conversion
f(vec<std::float16_t>());

このようなチェックはstd::cwによるconstexpr引数が無いと行えないため、C++26のstd::simdでは整数型の特別扱いを行うコンストラクタは削除された上でstd::cwを使うように変更されていました。

この提案では、constevalコンストラクタとコンパイル時例外によってstd::cwを使用しなくても同等のチェックとエラー化を達成できるため、それを提案しています。

constevalコンストラクタの追加については2つの方法を提示したうえで、算術型からの変換コンストラクタをconstevalにする方法を提案しています(もう一つはexplicitコンストラクタとコンセプトの制約順序によってconstevalコンストラクタの呼び出し機会を制限したうえで現在のコンストラクタを維持する方法)。

再現コードで示すと次のようになります

template <class From, class To>
concept simd-broadcast-arg = constructible_from<To, From>;

template <class From, class To>
concept simd-consteval-broadcast-arg
  = simd-broadcast-arg<From, To>
      && convertible_to<From, To>
      && is_arithmetic_v<remove_cvref_t<From>>
      && !value-preserving-convertible-to<From, To>;

template <class T>
class basic_vec {
public:
  template <simd-broadcast-arg<T> U>
  constexpr explicit(see below) basic_vec(U&&); // #1

  template <simd-consteval-broadcast-arg<T> U>
  consteval basic_vec(U&&); // #2
};

simd-consteval-broadcast-argsimd-broadcast-argを包摂していることによって、オーバロード解決においてより優先されます。value-preserving-convertible-to<U, T>は算術型UからTへの変換が値を保持しない変換となる場合のみ選択されるためのコンセプトです。これにより、暗黙変換/明示的変換の両方において、要素型への変換が縮小変換となるような場合はconstevalコンストラクタ(#2)が選択されます。

#2のコンストラクタ内では変換に際して値が失われる(変換後に等しい値にならない)場合に例外を送出することでコンパイルエラーになります。

値を保持する変換については#1を選択し、算術型以外の型に対しても#1が選択されます。

この提案はC++26を目指していますが、専用の例外型の導入(というかどのような例外を投げるか)についてはC++26では行わないようにしています(ここでの目的はコンパイル時の診断の向上であって、例外をキャッチして処理することを目指していないため)。

P3845R0 Make std::execution's monadic operations naming scheme consistent

std::executionのモナド的操作に該当する操作の名前を標準ライブラリの既存のものと一貫させる提案。

std::executionsenderアルゴリズムの中には、senderをモナドと見た時にモナドに対する典型的な操作に該当するものがいくつかあります。標準ライブラリ中ではstd::optional/std::expectedがモナド的な型であり、それらはよく似たモナド的操作のAPIを備えています。しかし、同じモナド的操作に対応するAPIでもこの両者の間で名前が大きく異なっています。

この提案は、既存のモナド的操作(ほぼstd::optional/std::expectedのモナド的操作API)にあわせて、std::executionの一部のsenderアルゴリズムの名前を変更しようとするものです。

名前の変更しているものとその変更先候補は次のものです

  • std::execution::then -> std::execution::transform
  • std::execution::let_value -> std::execution::and_then
  • std::execution::let_error -> std::execution::or_else_error
  • std::execution::let_stopped -> std::execution::or_else_stopped

execution::thenTで値完了するsenderT -> Uの関数fを受け取って、senderが値完了した場合にその結果にfを適用した値で完了するsenderを返すものです。これは、views::transformTの範囲とT -> Uの関数fを受け取って、各要素をfで変換したUの範囲を返す)、std::optional<T>::transformoptional<T>T -> Uの関数fを受け取って、optionalが値を持っている場合にその値にfを適用した結果を保持するoptional<U>を返す)、std::expected<T, E>::transformexpected<T, E>T -> Uの関数fを受け取って、expectedが正常値を保持している場合にその値にfを適用した結果を保持するexpected<U, E>を返す)と対応しており、これはモナドに対するmapと呼ばれる操作です。

execution::let_valueTで値完了するsenderT -> senderの関数fを受け取って、senderが値完了した場合にその結果にfを適用したsenderを返すものです。これは、std::optional<T>::and_thenoptional<T>T -> optional<U>の関数fを受け取って、optionalが値を持っている場合にその値にfを適用した結果のoptional<U>を返す)、std::expected<T, E>::and_thenexpected<T, E>T -> expected<U, E>の関数fを受け取って、expectedが正常値を保持している場合にその値にfを適用した結果のexpected<U, E>を返す)と対応しており、これはモナドに対するbindと呼ばれる操作です。

execution::let_errorEでエラー完了するsenderE -> senderの関数fを受け取って、senderがエラー完了した場合にその結果にfを適用したsenderを返すものです。execution::let_stoppedsendervoid -> senderの関数fを受け取って、senderが停止した場合にfを呼びだした結果のsenderを返すものです。これらは、std::optional<T>::or_elseoptional<T>void -> optional<T>の関数fを受け取って、optionalが無効値を保持している場合にfを呼びだした結果のoptional<T>を返す)、std::expected<T, E>::or_elseexpected<T, E>E -> expected<T, F>の関数fを受け取って、expectedがエラー状態の場合にそのエラー値にfを適用した結果のexpected<T, F>を返す)と対応しており、これらはモナドの失敗チャネルにおけるbind操作です。

ただし、senderはエラーと停止の2つの失敗チャネルを持っており、execution::let_errorexecution::let_stoppedはそれに対応しています。この2つに同じor_elseという名前を使うことはできないため、or_elseを基本にしつつもこの2つのチャネルに対応した命名にする必要があります。

この提案ではその命名として、or_else_erroror_else_stoppedを採用しています。

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

変更前(現在)

// The whole flow for transforming incoming requests into responses
sender auto snd =
    // get a sender when a new request comes
    schedule_request_start(the_read_requests_ctx)
    // make sure the request is valid; throw if not
    | let_value(validate_request)
    // process the request in a function that may be using a different execution resource
    | let_value(handle_request)
    // If there are errors transform them into proper responses
    | let_error(error_to_response)
    // If the flow is cancelled, send back a proper response
    | let_stopped(stopped_to_response)
    // write the result back to the client
    | let_value(send_response)
    // done
    ;

変更後

// The whole flow for transforming incoming requests into responses
sender auto snd =
    // get a sender when a new request comes
    schedule_request_start(the_read_requests_ctx)
    // make sure the request is valid; throw if not
    | and_then(validate_request)
    // process the request in a function that may be using a different execution resource
    | and_then(handle_request)
    // If there are errors transform them into proper responses
    | or_else_error(error_to_response)
    // If the flow is cancelled, send back a proper response
    | or_else_stopped(stopped_to_response)
    // write the result back to the client
    | and_then(send_response)
    // done
    ;

P3846R0 C++26 Contracts, reasserted

NBコメント等で表明されているContracts機能への懸念に答える文書。

C++26の機能凍結とNBコメントフェーズへの移行と前後して、C++26 Contracts機能への懸念の表明や削除を求める提案やNBコメントが多数提出されています。

ただ残念ながら、それらのほとんどの主張は新しいものではなく、P2900(C++26 Contracts提案)の議論期間中に何度か提示されすでに議論されていることについてのものがほとんどです。

この文書は改めてそれらに答えるもので、最近(この提案の提出時点)提起されたそれらの異議・反対意見を整理したうえで、これまでどのように議論され対処されたかを説明するものです。

ここでは、異議・反対意見は次の17個に整理されています

  1. P2900は安全ではない / C++を安全ではなくする
  2. P2900は翻訳単位間で一貫したセマンティクスを提供しない
  3. P2900が依存関係管理に与える影響は不透明
  4. P2900はODRの精神に反している
  5. P2900はモジュールではうまく動作しない
  6. P2900は実装定義が多すぎる
  7. P2900はコンパイラがチェックできないガイドラインに依存している
  8. const化には問題がある
  9. グローバルな契約違反ハンドラには問題がある
  10. 連続性のある複数の契約アサーションをobserveセマンティクスで評価することは危険
  11. 例外を契約違反として扱うことは実装不可能
  12. P2900は静的解析をサポートしない
  13. P2900は複雑すぎる
  14. P2900には重要な機能が欠けている
  15. P2900は将来の望ましい機能追加を困難にする
  16. P2900は他の機能から構成される必要がある
  17. P2900は導入経験が不十分

また、appendixとしてこの提案で対処されるNBコメントをリストアップしています。

P3849R0 SIS/TK611 considerations on Contract Assertions

C++26 Contractsをホワイトペーパー等を経由するようにする提案。

この提案は、スウェーデンの国内委員会における調査や議論の結論を報告するものです。次のような事を報告しています

  • 契約機能に対する強い支持はほとんどない
  • 現在の契約機能について強い抵抗がある
    • 既存の提案やコミュニティの著名人が、現状の機能への問題を指摘している
  • 契約アサーションを正しく使用するにはガイドラインや追加のディレクティブが必要であり、そのような機能を言語に追加すべきではない
  • 評価セマンティクスが異なるモードでビルドされたバイナリが混合するケースにおいて、契約機能とその設定がどのように影響するか十分に調査されていない

結果として

  • WG21は、他の各国機関からのフィードバックを収集し、文書化された懸念事項に対処すること
  • WG21は、未解決の議論を関連する作業範囲内において建設的に解決するとともに、必要に応じて関連情報が容易に発見できるようにすること
  • WG21は、Contracts機能をホワイトペーパーに移行するとともにコンパイラベンダ等と協力して、標準化前に実装経験を積むこと

を要請しています。

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

P3851R0 Position on contracts assertion for C++26

C++26からContractsを削除する提案。

  • 十分な導入経験の不足
    • P2900の仕様は実装と展開の経験がほとんどない
  • 混合モードビルドの問題
    • ヘッダファイルで記述された契約アサーションを含むinline関数(関数テンプレートなども同様)は、翻訳単位によってチェックされるかが変わる可能性がある
  • 依存関係を持つアサーション
    • 例えば、ポインタのnullチェックを行うアサーションと続いてそれをデリファレンスするアサーションがある場合、終了しない評価セマンティクスでは後者のアサーションが未定義動作に陥る
  • 安全でない結果
    • 重要なチェックが省略される可能性がある
  • const
    • ほぼ同じコンテキストにあっても、契約アサーションの内外で同じ関数呼び出しに対して異なるオーバロードを選択しうる
    • コードの理解を困難にする
  • 関数ポインタと仮想関数のサポートが無いこと
    • 関数ポインタも仮想関数も多くのコードベースで使用されており、それらをサポートしないことは機能を導入する上での障害となる

これらの懸念は現時点では解消されていないため、C++26にはContractsを含めずにホワイトペーパーまたはTSに移行することを強く推奨しています。

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

P3853R0 A thesis+antithesis=synthesis rumination on Contracts

Contracts機能に関して、P3640のアプローチを再考し、ホワイトペーパーとして両者の対照実験をしてから標準に導入する提案。

P3640R0では契約アサーションのデフォルトの評価セマンティクスをenforce固定にしておき評価セマンティクスを可変にしないようにすることと、可変セマンティクスはラベルによって可能にするようにすることを提案しています。

この提案では、P3640のアプローチの場合デフォルトの構文がその動作の不確実さや複雑さを伴うことなく、シンプルに使用することができ、異なる翻訳単位でセマンティクスのモードが異なる場合のABIに関する問題や最適化に関する問題と無縁になるとして、このアプローチの採用を推奨しています。

void f(int x) pre(x >= 0);  // C++26時点
// P2900: 評価セマンティクスが柔軟であり、コンパイラオプションや違反ハンドラによって動作を調整可能
// P3640: 評価セマンティクスが固定

void f(int x) pre<somelabel>(x >= 0); // 将来の拡張
// P2900: 評価セマンティクスの固定など、評価セマンティクス等の振る舞いをより明示的に指定できる
// P3640: 評価セマンティクスの柔軟性など、振る舞いをより細かく制御できる

すなわち、pre, post, contract_assertといった最短の構文に対して、P2900は汎用的な機能を持たせようとしており、P3640はシンプルな機能を持たせようとしています。それにより、P3640では最短の構文の意味も理解しやすくなります。

さらに、デフォルトのセマンティクスをquick enforceにしておけば違反ハンドラが必要なくなり、最短の構文の単純さはさらに高まります。

このアプローチは、Fedoraとその派生となるLinuxディストリビューションにおいて配布されているC++プログラム/ライブラリのデフォルトの設定でもあります(コンパイラが提供する堅牢化機能を有効化して配布するのがデフォルトになっている)。これは数年前から行われていることですが、それらのディストリビューションのユーザーには受け入れられており、パフォーマンスや突然のクラッシュが問題になるようなことは無かったようです。

したがって、P3640のアプローチはすでに実装経験があり、既存の慣行の一つでもあります。

また、後からP2900の上にP3640を導入することは、互換性を破壊せずにはできません。

そして、P2900のContracts機能には実装経験や展開の経験が皆無であり、C++26でこれを導入することは、本番環境で試験的な機能の実験を行うようなものです。

最終的に次のようなことを提案しています

  • C++26からContractsを削除する
  • P2900とP3640の両方をホワイトペーパーまたはTSとして出荷し、この両方を切り替えることのできるメカニズムを提供する
    • おそらく、コンパイラオプション
  • 実装者や教育者の力を借りてこの試験的Contracts機能の対照実験を行い、実装と展開の経験を得る

P3640とP2900のどちらがユーザーにとって好ましいのかの判断も含めて、Contracts機能の実装経験を得ることを重視しています。

P3855R0 New Reflection metafunction - is_destructurable_type

P3856R0 New Reflection metafunction - is_destructurable_type

ある型が構造化束縛によって分解可能であるかを取得する関数の提案。

構造化束縛宣言の右辺において分解できる型は、配列型・タプルプロトコルを実装した型・すべてのメンバ変数が公開されている構造体、の3種類があります。配列型とタプルプロトコルを実装した型は既存のメタ関数によって判別することができますが、3つめのすべてのメンバ変数が公開されている構造体については簡単に判別可能ではありません。

すべてのメンバ変数が公開されている構造体と言っても、必ずしも集成体である必要はありません。また、すべてのメンバが公開されていても、基底クラスを持っていると分解できなくなる場合があります。そもそもすべてのメンバが公開されているという状態そのものも従来のメタ関数では判定することが困難でした。

C++26では静的リフレクションが利用でき、このような判別を比較的簡易かつ確実に行うことができるようになります。この提案は、リフレクションを利用した実装による型の構造化束縛可能性を判定する関数を標準ライブラリに導入する提案です。

提案されている関数はstd::meta::is_destructurable_type()という名前の関数です。これは型特性ベースの関数ではなく、リフレクションベースの関数です。

namespace std::meta {
  consteval bool is_destructurable_type(info type, access_context ctx);
}

判定したい型のリフレクションとアクセスコンテキストを渡して呼び出すことで、その型が構造化束縛可能かどうかをbool値で取得できます(構造化束縛可能であればtrueを返す)。

struct S { 
  int a;
  int b;
  int c;
};

struct C1 : S {
};

struct C2 : S {
  int d;
};

constexpr auto ac = std::meta::access_context::unchecked();

static_assert(is_destructurable_type(^^int, ac) == false);
static_assert(is_destructurable_type(^^S, ac));
static_assert(is_destructurable_type(^^C1, ac));
static_assert(is_destructurable_type(^^C2, ac) == false);
static_assert(is_destructurable_type(^^std::tuple<int, bool>, ac));

提案文書に実装例がありますがそれほど簡単に書けるものではなく、必要とされる割に実装が単純ではないことも標準ライブラリに追加する動機のようです。

// 提案文書から構造体のケースの判定部分を抽出してきた例
bool is_data_member_case(std::meta::info r, std::meta::access_context ctx) {
  // アクセス制御を無視するアクセスコンテキスト
  constexpr auto unchecked_ctx = std::meta::access_context::unchecked();

  // r(型のリフレクション)の全ての非静的データメンバのリフレクション
  auto nsdms = std::meta::nonstatic_data_members_of(r, unchecked_ctx); 
  {
    std::meta::info class_with_members = nsdms.size() > 0 ? r 
                                                          : std::meta::info{};
    // rの基底クラスをチェックする
    // 基底クラスを持つ場合は次のどちらかでなければならない
    // 1. rにはデータメンバが無い
    // 2. 非静的データメンバはすべて1つの同じ基底クラスのメンバである
    for (auto base : std::meta::bases_of(r, unchecked_ctx)) { 
      // 基底クラスの非静的データメンバのリフレクション
      auto base_nsdms = std::meta::nonstatic_data_members_of(type_of(base), unchecked_ctx); 
      
      // メンバ変数を持つ基底クラスは1つでなければならないこと
      // rがメンバを持つ場合はメンバを持つ基底クラスはあってはならないこと
      // をチェック
      if (base_nsdms.size() > 0) { 
        if (class_with_members != std::meta::info{} && 
            class_with_members != base)
        {
          return false; 
        }

        class_with_members = base;
      }
    } 
  } 

  // ctxのコンテキストにおいて非静的データメンバが全てアクセス可能かをチェック
  // アクセス可能ではないメンバがある場合、構造化束縛できない
  for (auto mem : nsdms) { 
    if (!std::meta::is_accessible(mem, ctx)) { 
      return false; 
    }
    // 匿名共用体メンバを持つ場合、構造化束縛できない
    if (std::meta::is_union_type(type_of(mem)) && !std::meta::has_identifier(mem)) { 
      return false; 
    }
  }

  return true;
}

この判定処理を見るだけでも、ユーザー定義構造体に対する構造化束縛可能性がかなり複雑な条件によって決定されることが分かると思います。

この提案ではさらに、主に次のような利点からC++29以降の型クエリは可能な限り型特性ではなくリフレクションメタ関数として実装されるべき、としています。

  • 単一のstd::meta::infoを取るシンプルで固定的なインターフェース
  • ほとんどの場合、テンプレートのインスタンス化とSFINAE関連のメモリ消費の増大を回避できる
  • 型以外のものもクエリの対象にできる
    • 値、メンバ変数、関数、関数引数、名前空間など
  • コンパイル時例外とmeta::exceptionによってエラーメッセージが改善される
  • アクセスコンテキストを使用することでアクセス制御が行われる

また、型特性とリフレクションメタ関数は異なる副作用を持つためそれぞれ独立して実装されるべきであり、リフレクションメタ関数はその実装に型特性を使用すべきではなく、型特性はその実装にリフレクションメタ関数を使用すべきではないとも推奨しています。

なお、この提案はP3855R0と全く同一の内容の様で、こちらをメインとして扱うようです。

P3857R0 Policy: A function named get should return only on success

標準ライブラリにおいて、get()という名前のメンバ関数は値を直接返すべきとする提案。

P3091R4では、std::optional<T&>を使用することでより便利に連想コンテナからキーによって要素を引き当てるAPIが提案されています。そこでは、keyによって要素を引き当てstd::optional<T&>で返す関数に対して.get()という名前が当てられています。

// std::mapに対するget()のシグネチャ例
constexpr optional<mapped_type&>       get(const key_type& x) noexcept;
constexpr optional<const mapped_type&> get(const key_type& x) const noexcept;

しかし、このようなエラーチャネルを持つ戻り値を返すことは、標準ライブラリの既存のget()という名前の関数とは動作が異なっており一貫していません。

標準ライブラリにおける既存のget()は失敗の可能性のある値を返さず、契約内で呼ばれた場合は常に成功するか、他の方法で失敗が判定できるかのどちらかになっています。P3091R4のget()はそれらとは異なっています。

この提案ではこの問題への対処として2つの提案を行っています

  1. P3091R4のget()lookup()へ変更する
  2. ポリシーの変更
    • get()という名前の関数はその通常の戻り値が暗黙的にオブジェクトの取得に成功したことを示す関数である必要がある
      • エラーステータスを返す可能性のある関数は別の名前を使用するべき

この2つの事は同時に解決せずとも独立して投票可能としています。

P3858R0 A Lifetime-Management Primitive for Trivially Relocatable Types

指定された場所でオブジェクトの生存期間を再開するstd::restart_lifetimeの提案。

C++26ではトリビアルリロケーションの言語とライブラリのサポートによって、従来リロケーション(relocation)として知られつつもUBの上で行われていた操作が言語による保証とライブラリサポートの下で安全に利用できるようになります(ライブラリサポートは最終的に削除されましたが)。

リロケーションは、ある場所に配置されてそこで生存期間が開始されているオブジェクトを別の場所に移動させる操作であり、その際ムーブとは異なり元のオブジェクトの生存期間を終了させます。これは、オブジェクトのムーブ+元のオブジェクトの破棄+移動先でのオブジェクトの生存期間再開の複合操作ですが、トリビアルリロケーションの場合この移動はmemcpyによるビットコピーで行うことができ、より効率的になります。

トリビアルリロケーションがC++23までの言語で安全に行えるのはトリビアルコピー可能な型に対してのみでしたが、多くのクラス型(多態的なクラスであっても)でも実質動作することが知られていました(例えば、std::vectorstd::unique_ptrなどでも)。もちろん従来これはUBだったのですが、典型的にはstd::vector-likeなクラスにおける配列の伸長時など、リロケーション的な操作が必要になる箇所で最適化として使用されていました。

C++26では、トリビアルリロケーション可能な型というものがトリビアルコピー可能な型とは別に定義され、それらの型ではリロケーション操作(ライブラリ関数を通したもの)が安全に行えることが保証されるようになっています。

(この提案時点の)C++26のライブラリサポートではstd::trivially_relocate()という関数によってトリビアルリロケーションを実行することができます。

// Fooはトリビアルリロケーション可能だが、トリビアルコピー可能ではない
class Foo { /*...*/ };

static_assert(
     std::is_trivially_relocatable_v<Foo>()
 && !std::is_trivially_copyable_v<Foo>()
);

void f() {
  alignas(Foo) char x1_buffer[sizeof(Foo)],
                    x2_buffer[sizeof(Foo)],
                    y1_buffer[sizeof(Foo)],
                    y2_buffer[sizeof(Foo)];

  // memcpyによるリロケーション、UBとなる
  Foo* x1 = new (x1_buffer) Foo();
  std::memcpy(&y1_buffer, x1, sizeof(Foo));
    Foo* y1 = reinterpret_cast<Foo*>(y1_buffer);
  
  y1->bar(); // UB

  // std::trivially_relocate()によるリロケーション
  Foo* x2 = new (x2_buffer) Foo();
  Foo* y2 = std::trivially_relocate(
    x2,
    x2+1,
    reinterpret_cast<Foo*>(&y2_buffer));
  
  y2->bar(); // OK
}

std::trivially_relocate()という操作は前述のように、memcpy+リロケーション元オブジェクトの破棄+リロケーション先オブジェクトの生存期間再開の3つの操作が複合したものです。このうちmemcpyとリロケーション元オブジェクトの破棄についてはユーザーサイドでも行う方法があります。一方、リロケーション先オブジェクトの生存期間再開に関してはユーザーサイドで行う方法がありません。

リロケーション的な操作はC++23以前の環境でも様々な場所で必要となり、このような場所では必ずしもstd::trivially_relocate()が利用できるわけではありません。

例えば次のようなユースケースが挙げられています

  • realloc
    • reallocを典型として、メモリアロケータライブラリにおいては特定のメモリブロックのサイズを変更しようとする再割り当て機能が提供されている
    • これらの処理では、指定されたメモリをインプレースで伸長するか、新しいメモリ領域を確保して元のメモリの内容をコピーし、元のメモリを開放する
    • これらrealloc的な操作の後で、そのメモリ領域に配置されていたオブジェクトの生存期間を再開する方法がないため、このような場所ではトリビアルコピー可能な型のみで効率的なリロケーションが行える
      • realloc的な操作はあくまでメモリしか扱わないため、その領域のオブジェクトの管理は利用者が行う必要があるが、realloc的な操作内部でmemcpyが実行されていることによってstd::trivially_relocate()を利用できない
  • シリアライズ
    • インメモリデータベースや階層型キャッシュシステムにおいて、データをメモリとディスクの間で頻繁にリロケーションする必要がある
    • このユースケースのためのトリビアルリロケーションについてのライブラリ機能は十分ではなく、トリビアルコピー可能な型にのみ制限される
  • 特殊なmemcpy実装
    • std::memcpyよりも高速なmemcpy独自実装を利用する場合、std::trivially_relocate()内でのmemcpyを置き換える方法がない
    • cudaMemcpyでCPU-GPU間でオブジェクトを転送する際も同様の問題がある
  • Rustとの相互運用
    • Rustのムーブが実質的にトリビアルリロケーションであることで、RustとC++の双方からアクセス可能なオブジェクトを安全に扱うにはトリビアルリロケーション可能な型にのみ制限するか、ヒープに確保する必要がある
    • これは、パフォーマンスの劣化および使用する際の手続きの煩雑化の問題がある

これらにおいてはいずれも、memcpy部分を独自で行っていることによって複合操作であるstd::trivially_relocate()を使用することができません。そして複合操作のうちリロケーション先オブジェクトの生存期間再開の方法が無いことによって、トリビアルコピー可能な型のみでしかトリビアルリロケーションを利用できません。

この提案は、このリロケーション先オブジェクトの生存期間再開を行うライブラリ関数を追加することで、これらのユースケースにおいてより多くの型でトリビアルリロケーションを可能にしようとするものです。

提案されている宣言

namespace std {

  template<class T>
  T* restart_lifetime(void* p) noexcept;

  template<class T>
  volatile T* restart_lifetime(volatile void* p) noexcept;
}

std::trivially_relocate()そのものもこれを使用して記述することができます

template<class T>
  requires /* ... */
T* trivially_relocate(T* first, T* last, T* result)
{
  std::memcpy(result, first, (last-first)*sizeof(T));

  for (size_t i = 0; i < (last-first); ++i) {
    std::restart_lifetime(result[i]);
  }
}

このstd::restart_lifetimeによってstd::trivially_relocate()に複合されていた操作が単離され、ユーザーはオブジェクトの値表現のコピー方法によらずにトリビアルリロケーションを安全に利用できるようになります。

GPUメモリとの間でクラスオブジェクトをやり取りする例

void * host_buffer = ...;
void * device_buffer = ...;

// ホスト側で`Foo`オブジェクトを作成
Foo* x = new (host_buffer) Foo{};

// それをGPU側に転送
cudaMemcpy( device_buffer,
            host_buffer,
            sizeof(Foo),
            cudaMemcpyHostToDevice );

// host_bufferを他の目的に再利用する

// `Foo`オブジェクトをGPUからホスト側に戻す
cudaMemcpy( host_buffer,
            device_buffer,
            sizeof(Foo),
            cudaMemcpyDeviceToHost );

// ホスト側で生存期間を再開する
x = std::restart_lifetime<Foo>(host_buffer);

// ... continue using *x

std::restart_lifetimeの実装はほとんどのプラットフォームにおいてno-opとなるはずです。現在その例外として知られているのがARM64e環境で、Pointer Authentication Codeと呼ばれる機能によって多態的な型においてvtableのポインタ署名の修正が必要になることが知られています。std::trivially_relocate()はその関数内でそれを処理する機会を提供することができる点も利点でしたが、std::restart_lifetimeでも同様にポインタの再署名処理の機会を提供することができ、ARM64e環境ではそのための処理が必要になります。

このARM64e環境における安全な実装の検討のために提案の提出が遅れていたようですが、現在のところセキュリティリスクのない実装が可能であり実装上の問題は無いとしています。

この提案はC++26に対するバグ修正であるとして、C++26に導入することを推奨しています。とはいえ、この記事執筆時点では導入されていません。

P3859R0 Assertions are not necessarily for changing program behavior

契約アサーションの周辺ツールに対する情報提供手段としての側面を説明する提案。

この提案は、C++に契約アサーションを導入するモチベーションの一つは、実行時の契約チェックを行うことよりもコンパイラ以外のツールに対してプログラムの正確性を判断する情報を提供する標準的な方法を用意することにあるとして、P3835R0などの批判に回答するものです。

現在のC++には関数契約についての情報をコードで記述する標準的な方法は存在していません。Cのアサートはその立ち位置に近いところにありますが、事前条件と事後条件という追加の情報を適切に表明することができていません。これによって、関数契約に基づいた静的解析やプログラム検証を行う外部ツールはC++のコードを理解したうえでより詳細に解析をする必要があり、難易度が高くなるとともに誤検知の可能性が高くなります。結果として、C++に対するそのようなツールはあまりメジャーではありません。

Contracts機能では標準的に統一された方法によって、関数の事前条件と事後条件を記述する方法を提供します。これらのアサーションは、それが実行時にどのような振る舞いをするかとは無関係に、C++標準の外にあるツールにとってプログラム中の状態やその仮定に関する情報を取得する標準的な方法として有用となります。

C++26 Contractsの評価セマンティクスがignoreのみで出荷されていたとしても、この標準化された表記法が存在するだけで外部ツールやC++開発者への情報伝達の手段として有益なものとなります。

このような観点からは、契約アサーションは周辺のツールや人間が明確にそれと認識できるマーカーである必要があります。すなわち、P2900と同じように動作するアサーション相当のものが書かれていても、それがより汎用的な言語機能の応用の一つとして記述されている場合、そのアサーション相当のものが提示しているのが関数契約であるかどうかあいまいになる可能性があります。

P3829R0ではまさにそのように、より汎用的な言語機能による構成によってContracts機能を実現しようとしています。これは、ツールや人が明確に関数契約を認識することのできるマーカーとしてのアサーションの役割を軽視しており、ここで説明されているようなメリットが得られなくなります。

また、提案では改めて、契約アサーションの評価セマンティクスの選択に関して標準内で厳格に規定していないことについて、なぜそうしているのかの説明を行っています。

P3860R0 Proposed Resolution for NB Comment GB13-309 atomic_ref is not convertible to atomic_ref

std::atomic_ref<T>においてstd::atomic_ref<const T>への変換コンストラクタを追加する提案。

C++26では、P3323R1の採択によってstd::atomic_ref<T>Tconstを付加することができるようになっています。しかし、std::atomic_ref<T>からstd::atomic_ref<const T>への変換はできませんでした。

これはおそらく単に見落としであるため、そのような変換を許可する変換コンストラクタを追加する提案です。

int main() {
  int n = 0;

  std::atomic_ref<int> ar1{n};         // ok
  std::atomic_ref<const int> ar2{ar1}; // この提案以前はできなかった
  std::atomic_ref<int> ar3{ar2};       // ng、これは許可されない
}

これはC++26へのNBコメントを受けて、その解決のための提案です。そのため、C++26がターゲットです。

P3861R0 Pragmatic approach to standard structural types

std::string_viewstd::spanstd::tupleを構造的な型とする提案。

NTTP(定数テンプレートパラメータ)で使用可能な型は次の条件を満たす必要があります

  • 構造的な型である
  • その初期化が定数式であること
    • 一時的なメモリ割り当てを行わないこと

構造的な型の条件としてはまず、非publicメンバを持つ型が除外されます。これによって、通常のクラス型のほとんどはNTTPとして使用できません。これは標準ライブラリのクラス型でも同様のルールが適用されます。

構造的な型という条件が要求しているのは、そのクラスの構造表現が常に正規化されていることを保証するためです。これは、NTTPの構造表現がテンプレートの等価性に影響を与えるためです。

構造的な型の要件の緩和等は試みられてはいるものの、容易に解決できる問題ではないため多くのクラス型がNTTPとして使用できるようになるにはさらに時間がかかることが見込まれています。その一方で、静的リフレクション機能によるコンパイル時プログラミングでは、標準ライブラリの型が構造的な型になるとより便利になることが分かっています。

この提案は、構造的な型の要件について変更するのは避けて、一部の標準ライブラリクラス型を構造的な型であると指定することを提案しています。

対象は次のものです

  • std::string_view
  • std::span
  • std::tuple
    • 要素型が全て構造的な型である場合のみ

これらのクラス型における問題点は、いずれもプライベートメンバを持つという点です。ただし、標準ライブラリのクラス型でありその実装がほぼ特定されていることや標準ライブラリの実装を信頼することにより、構造的な等価性を定義できるようになります。

提案では、これらの型について構造的な型であると指定するとともに、template-argument-equivalent(2つのテンプレート引数が等価であること)の定義を指定しています。

  • std::string_view
    • size()data()の値がtemplate-argument-equivalentであるとき
  • std::span
    • size()data()の値がtemplate-argument-equivalentであるとき
  • std::tuple
    • 対応する要素同士が全てtemplate-argument-equivalentであるとき

この実装はおそらくコンパイラの特別扱いによって行われ、ユーザー定義型では利用できません(メンバに持ったときなどに間接的には利用できます)。

P3862R0 Postpone basic_string::subview and wait for cstring_view

cstring_view::subviewとの一貫性のために、basic_string::subviewを延期する提案。

C++26では、std::string/std::string_view.subview()メンバ関数が追加されています。これは.substr()の戻り値がstd::string_viewになるバージョンであり、std::string.substr()の戻り値(std::string)を変更できなかったため別の関数として追加されています(std::string_viewはインターフェース一貫性のために追加されている)。

P3655で提案中のstd::cstring_viewはnull終端保証のあるstd::string_viewですが、インターフェースはstd::string等と一貫しています。そこでも.subview()は用意されているのですが、std::cstring_viewが持つnull終端という情報を伝達するためにstd::string/std::string_viewとは異なる戻り値型を取っています。

// std::stringのsubview()
template<...>
class basic_string {

  ...

  constexpr auto subview(size_type pos = 0, size_type n = npos) const -> basic_string_view<...>;

  ...
};

// std::cstring_viewのsubview()
template<...>
class basic_cstring_view {
  ...

  constexpr auto subview(size_type pos = 0) const -> basic_cstring_view<...>; 
  constexpr auto subview(size_type pos, size_type n = npos) const -> basic_string_view<...>;
  
  ...
};

オーバロードは2つに分かれていますが提供する機能性は同様になります。ただし、std::cstring_view.subview()は終端を含むような部分文字列を取得する場合にstd::string_viewではなくstd::cstring_viewを返す点が異なっています。

std::cstring_viewはnull終端についての情報を型レベルで保持しているため、その文字列からの部分文字列の取得時にnull終端の情報をできるだけ保持しようとします。std::string_viewはnull終端についての情報を持たないので選択肢は無いですが、std::stringは同様にnull終端についての情報を持っているためstd::string.subview()std::cstring_viewと同様のアプローチを取ることが望ましいです。

しかし、C++26でstd::string.subview()を今のままにしておくと、あとからこのようなオーバロードを追加することはできません。なぜなら、戻り値型の変更が必要になり、それは破壊的変更となるからです。

この提案は、std::string.subview()std::cstring_viewと一貫させるために、std::string.subview()に後からこの変更を適用できるようにしておくことを提案するものです。

そのための方法として次の2つのアプローチを提示しています

  1. std::string.subview()を延期(C++26から削除)する
  2. std::string.subview()のデフォルト引数を削除する

どちらのアプローチをとっても、将来(おそらくC++29)std::cstring_viewを用いたnull終端情報を保持する.subview()を導入する事ができます。ただし、1の方法だとC++26ではstd::string.subview()が利用できなくなります。2の方法だと、C++26ではstr.subview(0, 10)のように必ず開始位置と長さを指定して使用しなければならないので、あとからstd::cstring_viewのように2つのオーバロードに分けた時でも動作も戻り値も同じオーバロードが選択されます。

しかし、C++26に対して何も対処をしなければ、std::string.subview()はnull終端情報を持たないstring_viewを返すことしかできず、情報の欠落が発生するとともにstd::cstring_viewとの一貫性が無くなります。.substr()と同様の理由により後からの修正は困難であり、やるとしたら新しい関数を追加するしかありません。

この提案ではどちらか一方を選択しているわけではなく、LEWGの決定に委ねています。

P3863R0 Minimal fix for CWG3003 (CTAD from template template parameters)

テンプレートテンプレートパラメータからのCTADを正式に許可する提案。

C++23では、ranges::toによって範囲空コンテナへの変換が簡単に行えるようになっています。

std::views::iota(0, 42) | std::ranges::to<std::vector<int>>();
std::views::iota(0, 42) | std::ranges::to<std::vector<double>>()

このとき、ranges::to<C>ではCのコンテナのテンプレートパラメータを省略することもできます。

std::views::iota(0, 42) | std::ranges::to<std::vector>();

このようにしている場合でも、ranges::to<C>は入力の値型からCのテンプレートパラメータを補ってくれます。

これは内部的にはCTADを利用することで実装されています。細部を省略すると、次のようなコードでこのコンテナのテンプレートパラメータの補完が行われています。

template <template <typename...> typename TT, typename R>
auto to(R&& r) {
  // ranges::to<C>の形式からranges::to<C<T>>の形式へ委譲
  return std::ranges::to<decltype(TT(std::from_range, std::declval<R&&>()))>(r);
}

ここのTT(std::from_range, std::declval<R&&>())ではテンプレートテンプレートパラメータTTに対してCTADが実行されることでTTのテンプレート引数を導出しています。

このテクニックは、rangeライブラリの元になったrange-v3ライブラリで発見され、主要なコンパイラではサポートされており、このように標準ライブラリ自身の実装にも使用されています。しかし、実際にはコア言語にはこのような機能は無く、テンプレートテンプレートパラメータに対してCTADを実行することは禁止されています。

LWG Issue 4381ではこのことを指摘して、ranges::toのこのオーバーロードを削除することで問題を解決しようとしています。また、CWG Issue 3003ではこの禁止事項(テンプレートテンプレートパラメータがCTADの対象ではない事)をより強調することを提案しています。

これらに対してこの提案は、広く使用されほぼすべての実装でサポートされているこの機能について、既存の慣行を反映して標準機能として許可することを提案するものです。これにより、ranges::toのこの推論を行うオーバーロードには非標準の機能に依存している現状が解消されます。

この提案では、これをDRとすることを提案しています。

EWGの議論では、この不完全な仕様の修正は後日行うこととして、ranges::toはとりあえず現状のまま維持することで合意したようです。それに伴ってこの提案は一旦クローズされています。

P3864R0 Correctly rounded floating-point maths functions

正しい丸め(correct rounding)を行う浮動小数点数演算関数の提案。

四則演算等の浮動小数点数演算においてはほとんどの場合に計算後に丸めが行われます。この時にどのように丸めが行われるのかは、丸めモードという浮動小数点環境の状態の一部としてスレッドごとにグローバルな状態によって設定されています。

したがって、ある浮動小数点数演算において丸めモードを指定しようとすると主に次の2つの問題が発生します

  1. 正しい丸めが必要な計算において、丸めモードを変更する方法が人間工学的に適切ではない
  2. ライブラリ内部実装で丸めモードを変更する場合、その時点の浮動小数点環境を保存し復帰する必要がある

丸めモードについてのこのような問題はC++だけの問題ではなくCでもよく知られた問題であり、C規格のAnnex FではIEC 60559で指定されている数学演算に完全に準拠した関数に対するプリフィックスとしてcr_を予約しています。この関数は丸めに関して正しい丸めを行うことが保証された関数であり、それが必要な場合に上記の問題を気にせずに使用できるものです。

この提案は、加減乗除と平方根計算のcr_付き関数を標準ライブラリに追加する提案です。

// 提案する関数の宣言
namespace std {
  constexpr floating-point-type cr_add(floating-point-type x, floating-point-type y);
  constexpr floating-point-type cr_sub(floating-point-type x, floating-point-type y);
  constexpr floating-point-type cr_mul(floating-point-type x, floating-point-type y);
  constexpr floating-point-type cr_div(floating-point-type x, floating-point-type y);
  constexpr floating-point-type cr_sqrt(floating-point-type x);
}

これらの関数は無限精度であるかのように演算が実行された後、その結果を最近傍偶数丸め(round-nearest-to-even)によって丸めて返します。floating-point-typeはプレースホルダ型であり、std::numeric_limits<F>::is_iec559trueとなるような浮動小数点数型Fである必要があります。

これらの関数によっても2の問題は完全には解決できないですが(実装によっては浮動小数点環境に影響を与える可能性があるため)、この関数の利用に当たって丸めモードを変更する必要は無いため1の問題は解決されます。

P3865R0 Class template argument deduction (CTAD) for type template template parameters

型テンプレートテンプレートパラメータに対するCTADを許可する提案。

モチベーションなどは少し上のP3863R0と共通しているのでそちらを参照してください。

この提案でもP3863R0と同様に型テンプレートテンプレートパラメータに対するCTADを明示的に許可し、それをC++17へのDRとすることを提案していますが、ここではさらにそのセマンティクスについて詳細に検討されています。

この提案では、テンプレートテンプレートパラメータに指定されたデフォルト引数やテンプレートテンプレートパラメータがそれに指定されたテンプレートよりも特化している場合に生じている制約を尊重するようにCTADが実行されるようにすることを提案しています。

これはエイリアステンプレートのCTADの動作と一貫しており、この提案でのテンプレートテンプレートパラメータに対するCTADでは対応するエイリアステンプレートを仮設してそれを使用してCTADを行うようにしています。

すなわち、型テンプレートテンプレートパラメータに対するCTADにおいては、テンプレートテンプレートパラメータに指定されたテンプレート実引数を表すエイリアステンプレート(そのテンプレートテンプレートパラメータ(not実引数)と同じテンプレートパラメータを持つ)によって置き換えて、そのエイリアステンプレートに対して実行されたかのようにCTADが実行されます。

提案文書より、単純な例

template<typename T>
struct C {
  C(T);
};

template<template<typename> class X>
void f() {
  X x(1); // ok、XにはC<int>が推論される
}

template void f<C>();

テンプレート実引数よりもテンプレートテンプレートパラメータの方が特化しているケース

template<typename ... T>
struct C {
  C(T ...);
};

template<template<typename> class X>
void f() {
  X x1{1};    // ok、XにはC<int>が推論される
  X x2{1, 2}; // ng
}

template void f<C>();

このようなCTADは、次のようなエイリアステンプレートを用いた推論によって行われます

// 仮設のエイリアステンプレート
// テンプレートテンプレートパラメータXと同じテンプレートパラメータを持ち、テンプレート実引数Cを表すもの
template<typename T>
using XC = C<T>;

void g() {
  XC x1{1};     // ok、XCにはC<int>が推論される
  XC x2{1, 2};  // ng
}

デフォルト引数のケース

template<typename T = int>
struct C {
  C(int);
};

template<template<typename = long> class X>
void f() {
  X x{1}; // ok、XにはC<long>が推論される
}

template void f<C>();

このようなCTADは、次のようなエイリアステンプレートを用いた推論によって行われます

// 仮設のエイリアステンプレート
// テンプレートテンプレートパラメータXと同じテンプレートパラメータを持ち、テンプレート実引数Cを表すもの
template<typename T = long>
using XC = C<T>;

void g() {
  XC x{1}; // ok、XCにはC<long>が推論される
}

P3866R0 V2: An Evolution Path for the Standard Library

ライブラリ機能を将来にわたって改善していくための標準ライブラリ中でのバージョン分離ルールの提案。

C++は言語もライブラリも後方互換性を維持することを強く志向しています。それは利用者にとってはバージョンアップのコストが低いというメリットをもたらしています。しかしその一方で標準化、特に標準ライブラリの進化はこのことに強く制約を受けており、既存のものの改善が必要な場合は別の名前を付けたり、別の名前空間に配置したり、一貫性が無く場当たり的な対応が行われています。

// old
std::lock_guard x(m);

// new
std::scoped_lock x(m);
// old
std::sort(v.begin(), v.end());

// new
std::ranges::sort(v.begin(), v.end());
// old
std::function f = ...;

// new
std::copyable_function f = ...;
// old
std::thread t = ...;

// new
std::jthread t = ...;

いずれの方法を取ったとしても、改善後のバージョンが分かりづらく、両者が混在していることで経緯を知らない利用者に混乱と学習の困難さをもたらしています。さらに、このような適切な名前を見つけなければならないことは機能の改善そのものの障害になっています。

この提案は、標準ライブラリ機能を将来にわたって一貫して改善していくための構造化フレームワークV2を提案しています。

V2は、C++標準ライブラリの破壊的変更を管理するためのバージョン管理戦略であり、主に次の2つの指針を立てています

  • ライブラリコンポーネントにインターフェースを変更するような変更が行われた場合、数値サフィックスを付加した新しいバージョンを導入する
    • 例えば、std::foo -> std::foo2 (-> std::foo3)
  • std名前空間内のバージョン管理されたエンティティを補完するために、標準(バージョン)固有の名前空間を導入する
    • std::cpp29std::cpp32など
    • これらの名前空間には、そのC++標準で利用可能なコンポーネントのその時点での最新版へのエイリアスが含まれる
    • 推奨される運用方法として、開発者はこれらのバージョン管理付き名前空間をデフォルトで使用することが望ましい
      • その後、std名前空間は古いバージョンの廃止されたAPIに明示的にアクセスする必要がある場合にのみ使用される
namespace cpp = std::cpp29;

void f() {
  // std::cpp29::stringはstd::stringのエイリアス
  cpp::string s;
  
  // std::cpp29::unordered_mapはstd::unordered_map2のエイリアス
  cpp::unordered_map<int,int> map;
}

また、これに関連するポリシーもいくつか提案しています。

まず、バージョンインクリメントは次の変更に適用されます

  • ソース互換性が損なわれる
    • ユーザーコードでコンパイルエラーが発生する
  • 主要プラットフォームにおいてABI互換性が損なわれる
  • 既存の機能を変更する動作のレグレッションや意味的変更を導入する

バージョンインクリメントはそうそう気軽に行われるものではなく、実行するには強力な正当性が必要になります。破壊的変更を導入する前に次のことを考慮すべきです

  • 移行パス
    • ユーザーがコードベースを半自動/自動で更新できるパスは用意できるか?
  • 概念の一貫性
    • 変更後のコンポーネントは、変更元の概念的なアイデンティティを維持しているか?
      • 変更は、ユーザーがその目的と使用法についてのメンタルモデルを適応させることが困難になるほど劇的であってはならない
  • 費用対効果の分析
    • 変更によるメリットは関連するコストを明確に上回っているか
      • コストにはたとえば、ユーザーがコードベースを更新するためのエンジニアリング労力、新しいインターフェースを習得するための学習コスト、標準ライブラリに別バージョンを導入することによる複雑さの増大、などが含まれる

バグ修正等の非破壊的な変更はコンポーネントの最新バージョンに適用する必要があり、以前のバージョンにバックポートするかはケースバイケースで判断できるものの、可能な限り広いユーザーにメリットをもたらすために、そのような改善は基本的にバックポートすることを推奨しています。

V2の下では、冒頭に示した例は次のように改善されます

// old
std::lock_guard x(m);
std::cpp11::lock_guard y(m);

// new
std::lock_guard2 x(m);
std::cpp17::lock_guard y(m);
// old
std::sort(v.begin(), v.end());
std::cpp17::sort(v.begin(), v.end());

// new
std::sort2(v.begin(), v.end());
std::cpp20::sort(v.begin(), v.end());
// old
std::function f = ...;
std::cpp11::function g = ...;

// new
std::function2 f = ...;
std::cpp26::function g = ...;
// old
std::thread t = ...;
std::cpp11::thread u =...;

// new
std::thread2 t = ...;
std::cpp20::thread u = ...;

V2フレームワークの利点は、ユーザーがバージョンアップをしようと考えた時でもすべてのものを一度に更新する必要はない点です。一度に全部更新することも、一部のものから順にアップデートしていくこともでき、ユーザーはそのペースを選択することができます。

P3867R0 define_static_string as a STATICALLY_WIDEN replacement

文字列リテラルを型に合わせた文字列型に変換する関数の提案。

標準規格文書中では、型Tcharであるかwchar_tであるかに合わせて自動的に文字列リテラルを変換するユーティリティ、STATICALLY-WIDENが定義されています。

Let STATICALLY-WIDEN<charT>("...") be "..." if charT is char and L"..." if charT is wchar_t.

これはほぼ同等なものをマクロを活用することで実装することができます

template <typename _CharT>
constexpr const _CharT* __statically_widen(const char* __str, const wchar_t* __wstr) {
  if constexpr (same_as<_CharT, char>)
    return __str;
  else
    return __wstr;
}

#define STATICALLY_WIDEN(_CharT, str) statically_widen<_CharT>(str, L##str)

このような実装はプリプロセスに依存するため文字列リテラルでしか使用できません。一方、このようなユーティリティはコンパイル時の文字列全般およびそのほかの文字列型(char8_t文字列など)において有用である可能性があります。

この提案は、STATICALLY-WIDENをコンパイル時リフレクションを利用してあらゆるコンパイル時の文字列と任意の文字列型で動作するもので置き換えて、なおかつそれを標準ライブラリ機能にしようとする提案です。

この提案では、std::meta::define_static_string()を参考に、char8_t文字列を受け取りそれを指定されたテンプレートパラメータが表す文字型のエンコーディングで文字列リテラルを生成するdefine_encoded_static_string()を提案しています。

namespace std::meta {
  template<typename CharT, ranges::input_range R>
    requires same_as<char8_t, ranges::range_value_t<R>>
  consteval const CharT* define_encoded_static_string(R&& r);
}

使用例

const char* hello = define_encoded_static_string<char>(u8"Hello");
const wchar_t* wello = define_encoded_static_string<wchar_t>(u8string_view(u8"Hello"));

入力として使用可能なのはchar8_t文字型によるUTF-8文字列ですが、その形式は文字列リテラルでなくてもokです。戻り値として、渡された文字列を指定された文字型の文字列に変換した文字列リテラルを返します。

提案では特に実装は示されていませんが、リフレクションに加えてコンパイラのサポートも必要なようです。コンパイラは規格に準拠したコンパイルのためにほぼ同等の処理を内部的に利用しているはずであり、これはそれを標準ライブラリ経由で公開するだけであるはず、とされています。

P3868R0 Allow #line before module declarations

モジュール宣言の前に#lineディレクティブを置けるようにする提案。

C++20のモジュール仕様では、モジュール宣言の前にプリプロセッシングディレクティブを置くことができません。Clangでこのことを実装したところ、次のようなコードがコンパイルエラーになることが発見されました。

#line 1 "A.cppm"
export module a;

これはオリジナルのソースコードの行数を参照するプログラムなどが良く行うことでであり、そのようなプログラム(build2, ccache, distccなど)において問題となります。

この提案は、この制限を緩和しようとするものです。

ただし、置くことを許可するのはこの#lineディレクティブのみです。

この提案には対応するNBコメントがあるようで、C++26をターゲットにしています。

P3870R0 Renaming std::nontype to std::tag

std::nontypestd::tagにリネームする提案。

std::nontypeに関してはこちらのスライドを見てもらうか、以前の記事を参照

この提案では、std::nontypeが実質的にコンストラクタタグ型であることから、std::tagとすることを提案しています。

この提案の名前は支持を得られず、結局std::constant_argになったようです。

P3872R0 2025-10 Library Evolution Polls

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

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

P3774R1はC++26のDR、P3798R1はC++29を目指してLWGに転送するための投票です。

P3874R0 Safety Strategy Requirements for C++

C++の安全性についての戦略として、構成による安全性(Safety by Construction)を採用すべきとする提案。

この提案では、C++が将来にわたって新規開発のための言語としての選択肢であり続けるための安全性の保証として、構成による安全性(Safety by Construction)を採用し、それをC++の安全性戦略ビジョンとしてコミットメントすることを推奨するものです。

構成による安全性とは、RustやSwift等モダンな言語(Rustを除けば非システムプログラミング言語)のデファクトとなっている、デフォルトが安全なモードである状態の事です。C++の現状は逆で、デフォルトがアンセーフになっています。

また同時に、既存のC++コードへの直接アクセス(後方互換性)も重要視しています。

この提案における安全性とはUBが起こらない事を指しており、それを達成するために最も障害になるのがメモリ安全性であるとしています。また、この提案の目標としてはUBをゼロにすることで、UBを減らすことではありません。

かなり要約すると次のようなことを主張しています

  • UBによる欠陥(脆弱性)のほとんどは、新しいコードで発生する
    • 脆弱性の存在確率は、コードの寿命とともに指数関数的に減少する
  • 古いコードを安全に書き換えることよりも、新しく書かれるコードを安全にすることが重要
  • 現在のC++のビジョンには、この観点が欠けている
    • プロファイル機能は構成による安全性を補完するもの、レガシーコードの改善には役立つがこれによって構成による安全性を実現するのは困難
    • 暗黙の契約アサーションはUBフリーの良い一歩となる可能性がある
      • ただし、構成による安全性とは異なるアプローチ
  • 現状のWG21のコミットメントでは、メモリ安全性(ひいてはUB安全性)をセキュリティの一部として捉えており、直近それに対処されることが期待できない
    • 安全性の向上やUBの削減に向けた取り組みは行われており、C++の実績を考えるといずれ達成はされると思われる
  • 欠けているのはビジョンへのコミットメントにある
    • 言語の明確なビジョンを打ち出すことは、複数のシステム言語の中で実績のある選択肢として残るか、レガシーツールとして残るかの違いを生む可能性がある

このような観点から、構成による安全性(+現状と同等の後方互換性)によるUBフリー(結果的にメモリ安全)をC++の安全性戦略の到達目標として、そのコミットメントをより明確に打ち出すことを提案しています。

特筆すべき点として、この提案はRust Foundationの人の手によって書かれています。

P3875R0 Defining -ffast-math is hard!

-ffast-mathのような浮動小数点演算最適化を許可するために標準仕様として正確に定義するのは実現不可能だとする文書。

SG6では-ffast-mathによって有効になる最適化と同様に、正確な値を保持しない浮動小数点演算の最適化を明示的に許可することについて議論が行われていたようです。この文書は、それに対してそのような最適化の緩和条件のほとんどは正確に記述することが困難であることを指摘するものです。

ここでの正確とは、ユーザーがコンパイラの最適化による予期しない挙動を回避したり、プログラムの正しさについて厳密に推論したりできるほど十分明確に記述できること、を言います。

例えば浮動小数点演算において、再結合(reassociation)を許可することを考えます。再結合においては、浮動小数点数演算の式を場所によって異なる順序で計算することが許可されます。

例えば次のような関数

inline float f(float x, float y, float z) { 
  return x + y + z; 
}

がインライン展開され2か所で使用されている場合、異なる結合方法によって計算することでそれら一見同じであるはずの値によって呼ばれる純粋関数が異なる値を返すことはあり得るでしょうか?

仮に次のように使用されている時

w = f(a, b, c); 
use w; 

... 

use w again;

コンパイラが2回の使用の間(...)でwをレジスタスピルする必要がある場合、代わりに異なる結合方法でwを再計算(...中にある計算結果を再利用するなど)することで、wが僅かに異なる値を取ることは可能でしょうか?

今日の最適化においては、このようなことは実際に行われているようです。

異なる結合方法で同じ値を異なる時点で計算した場合、コンパイラはその結果が必ず同じであると仮定し、そうでない場合には完全に誤った結果を生成しても良いのでしょうか?明らかにそうではないのですが、このことをどのように仕様として規定するかは不透明です。

別の例として、Flush-to-zeroでも同様の問題が起こります。Flush-to-zeroでは非正規化数が0にされることを許可する最適化ですが、これは必ずしもすべての場合に適用されません。また、これは多くの場合別の翻訳単位で指定されることで、ある翻訳単位のコンパイラからはそれが設定されていることが観測できない場合が良くあります。

筆者の方による特に直観に反する例

int main() { 
  double smallest = nextafter(0.0, 1.0);

  if (smallest > 0.0) { 
    printf("Positive: %g\n", smallest); 
  } else { 
    printf("Wrong: %g\n", smallest); 
  }

  return 0; 
}

これをclang 16で-ffast-mathオプションを付けてコンパイルし実行すると

Wrong: 4.94066e-324

これは、smallestにはnextafter()の結果が正しく格納されているものの、Flush-to-zeroによってsmallest > 0.0の比較結果が0にフラッシュされているようです。

このような動作は正しく定義したり、実装詳細を理解していないユーザーに説明することが困難です。非正規化数は一部のコンテキストでは生成されうるものの、他のコンテキストでは生成されず、もはや表現可能な値(representable values)のwell-definedな集合は定義できません(このため、nextafter()の効果を正しく規定することができません)。

提案にはもう一例挙げられていますが、このように-ffast-mathが行っているような最適化は時に予測不可能なものであり、標準規格として妥当な形で規定することは無理ではないか?というのがこの文書の主張するところです。だからやめようとまでは提案しておらず、議論の一環としての文書のようです。

おわり

この記事のMarkdownソース

[C++]Eigen::Transformの分かりづらいところ

はまったことのメモです。

Eigen::Transform

Eigen::Transformはアフィン変換を表現するクラス型です。

#include <Eigen/Core>
#include <Eigen/Geometry>

// ないらしい
using Transform3d = Eigen::Transform<double, 3, Eigen::TransformTraits::Affine>;

int main() {
  // 平行移動を表すアフィン変換を構成
  Eigen::Translation3d tr{10.0, 5.0, 0.0};
  Eigen::Transform3d transform{tr};

  Eigen::Vector3d vec{1.0, 2.0, 3.0};
  // 平行移動を適用
  Eigen::Vector3d res = transform * vec;
}

その他回転や拡大縮小などを行えます。ベクトルとの*での積の際も、同次行列を考慮して次元を足して1入れてみたいなことを内部で自動でやってくれます。

ただ、これだけならまだ普通にEigen::Matrixを用意してやった方が良い気持ちが大きいですが、このクラスの良いところは多段のアフィン変換の合成をサポートしているところです。

合成は二項*あるいは*=によって行えます。

#include <numbers>

#include <Eigen/Core>
#include <Eigen/Geometry>

int main() {
  // 3次元座標変換を表すアフィン変換を構成
  // 例えば、ローカル座標系からグローバル座標系への変換
  Eigen::Translation3d tr{10.0, 5.0, 0.0};
  Eigen::AngleAxisd rot{std::numbers::pi, Eigen::Vector3d::UnitZ()};
  Transform3d transform{tr};
  
  transform *= rot;
  // こうしても同じ
  //transform = transform * rot;

  Eigen::Vector3d vec{1.0, 2.0, 3.0};
  // 座標変換を適用
  Eigen::Vector3d res = transform * vec;
}

*による合成はそのままアフィン変換行列の積と思えば良く、アフィン変換行列T1, T2, T3がありT1 -> T2 -> T3の順で変換を適用したい場合、そのままT3 * T2 * T1のように書くことができます。つまり*の右側にあるものほど先に適用されます。

ただし、Eigenの場合*の両辺はかなり柔軟で、アフィン変換行列そのものではなくアフィン変換を表すものを直接指定することができます(上記例だとEigen::Translation3dEigen::AngleAxisd)。Eigen::Transformおよびこれらの型でオーバロードされた*演算子内で行列に変換して積を取った時と同等の事が行われています。

Eigen::Transformそのものへの変換の渡し方/適用方法も柔軟になっています。

// *してから渡す
Transform3d transform{tr * rot};

// *したものを代入
Transform3d transform{};
transform = tr * rot;

// 2つのTransformの合成
Transform3d t_rot{rot};
Transform3d t_translate{tr};
Transform3d transform = t_translate * t_rot;

この例はすべて一個前の例のtransformと同じ変換を表すはずです。

メンバ関数によるアフィン変換のセット・合成

Eigen::Transformへのアフィン変換の適用方法にはメンバ関数も利用できます。

Transform3d transform = Eigen::Affine3d::Identity();
transform.translate(tr);
transform.rotate(rot);

こちらは*よりも起きていることが分かりやすいのでより説明的かもしれません。

ただ、ここで問題になるのはこのように1つのEigen::Transformにメンバ関数で変換を適用していった際の変換の適用順が分かりづらくなることです。この例のtransformの場合、transform * vecという風に変換を適用した時、tr * rot * vecになるのかrot * tr * vecになるのかわかりづらくなります。別の言い方をすると、メンバ関数で変換をセットしていくと、セットした変換がする前の変換の左右どちらに適用されるか分かりづらくなります。

答えは、後にセットしたものがより右側に適用される(メンバ関数の呼び出し順に*で合成した時と同じ)です。上記のメンバ関数の例は前節での*=の例と同じ変換を表し、rot -> trの順に変換が適用されます(tr * rot * vec)。

座標変換が典型ですが、複数の変換を合成したアフィン変換ではその順番が重要になるため、注意が必要になります。

// T1 -> T2 -> T3 -> T4の順で変換を行うアフィン変換を構成したい
Eigen::Translation3d T1 = ...;
Eigen::AngleAxisd T2 = ...;
auto T3 = Eigen::Scaling(...);
Eigen::Translation3d T4 = ...;

// *による合成
Transform3d transform1 = T4 * T3 * T2 * T1;

// メンバ関数による合成
Transform3d transform2 = Eigen::Affine3d::Identity();
transform2.translate(T4);
transform2.scale(T3);
transform2.rotate(T2);
transform2.translate(T1);

このtransform1transform2は同じ変換を表します。

私はこれにはまって時間を溶かし同僚に迷惑をかけたわけですが、なんかこうして書き起こしてみると全然罠でもなく自明ですね・・・

メンバ関数で適用順を制御する

メンバ関数による変換の合成時にも何らかの都合で右に追加するのではなく左に追加したいことがあるかもしれません。その場合は、メンバ関数名の先頭にpreを付けたメンバ関数によってそれを行えます。

// T1 -> T2 -> T3 -> T4の順で変換を行うアフィン変換を構成
Transform3d transform3{T2};
transform3.translate(T1);    // 右に適用
transform3.prescale(T3);     // 左に適用
transform3.pretranslate(T4); // 左に適用

.rotate()に対応するのは.prerotate()です。

このtransform3transform1transform2と同じ変換を表します。が、余計にわかりづらくなるのであまりやらない方が良さそうに思えます。

デフォルト構築

これは書いてて気づいたおまけです。

ここまでの例では黙って回避していましたがEigen::Transformをデフォルト構築すると恒等変換ではなく無の変換が構成されます。これはおそらく内部の変換行列の係数が不定値を取るため、これに対して変換を適用してもおかしな結果を招くでしょう。

Transform3d transform{};
transform *= T4;  // 意図通りの変換が構成されない

デフォルト構築はクラスメンバにしたときにそのクラスのデフォルトコンストラクタを無効化しないために用意されているだけだと思われます。デフォルト構築した後は代入演算子(=)によって上書きするか、そもそもEigen::Affine3d::Identity()などによって明示的に恒等変換で初期化する必要があります。

Transform3d transform{};
transform = T4;  // T4の変換と同等の変換を構成
// 恒等変換を構成
Transform3d transform = Eigen::Affine3d::Identity();

参考文献

この記事のMarkdownソース

[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ソース

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

文書の一覧

全部で26本あります。

もくじ

N5013 Programming Languages - C++

C++26規格書のCommittee Draft。委員会に所属していないと見られないようです。

N5014 Working Draft, Standard for Programming Language C++

C++26のワーキングドラフト第8弾。

N5015 Editors' Report - Programming Languages - C++

↑の変更点をまとめた文書。

N5019 Business Plan and Convener's Report: ISO/IEC JTC1/SC22/WG21 (C++)

ビジネスユーザ向けのC++およびWG21の現状報告書。

P2414R10 Pointer lifetime-end zap proposed solutions

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

以前の記事を参照

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

などです。

P2843R3 Preprocessing is never undefined

プリプロセッサに存在する未定義動作を取り除く提案。

以前の記事を参照

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

  • EWGにおいて設計上の選択を確認
    • キーワードに一致するマクロはill-formedとなるべき
    • //コメント内で垂直方向のホワイトスペースをサポートする
  • フィードバックにより、一部のケースで現在のIFNDRを維持する
    • #includeディレクティブにおいて、マクロ展開後の標準形式のいずれとも一致しない場合
    • マクロが定義済みのpreprocessor operatorに展開される場合

などです。

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

P3100R4 A framework for systematically addressing undefined behaviour in the C++ Standard

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

以前の記事を参照

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

  • P3754R0に合わせて提案を更新
  • Sofia会議でのフィードバックを適用
  • UBリストからUBとして指定されるべきではなく、CWG Issueがあるものを削除
  • noexceptとの相互作用に関する議論を追加
  • 提案の構成を改善
  • 提案のスコープを反映してタイトルを更新

などです。

P3337R0 Graph Library: Library Comparisons

提案中のグラフライブラリと既存のグラフライブラリの比較を行った文書。

この文書は一連のグラフライブラリ提案の一部として、提案中のグラフライブラリ(の参照実装graph-v2)と既存のグラフライブラリ(主にBoost Graph: BGL)との書き味とパフォーマンスについての比較を行った結果を報告するものです。

文書では、まずP3128 でTier1として提示されているアルゴリズムの一部について比較を行っています。扱われているアルゴリズムは次のものです

両ライブラリでこれらのアルゴリズムがどのように記述できるかを比較し、大きなデータセットにおけるその実行パフォーマンスを比較しています。

提案文書より、幅優先探索コードの比較。

BGL

using namespace std;
using namespace boost;

using G = compressed_sparse_row_graph<directedS, no_property, no_property>;
using Vertex = graph_traits<G>::vertex_descriptor;

G g;
// populate g

vector<Vertex> parents(num_vertices(g));

auto vis = make_bfs_visitor(
  make_pair(
    record_predecessors(parents.begin(), on_tree_edge())
  )
);

breadth_first_search(g, vertex(0, g), visitor(vis));

graph-v2

using namespace std;
using namespace graph;

using G = container::compressed_graph<void, void, void, uint32_t, uint32_t>;
using VId = vertex_id_t<G>;

G g;
// populate g

vector<VId> parents(size(vertices(g)));

auto bfs = edges_breadth_first_search_view<G, void, true>(g, 0);

for (auto&& [uid, vid, uv] : bfs) {
  parents[vid] = uid;
}

詳細な比較結果については提案を参照してください。

全体として、最新のC++機能を使用することでよりシンプルに記述できつつ、多くの場面でBGLを大きく上回るパフォーマンスを発揮しています。

P3347R4 Invalid/Prospective Pointer Operations

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

以前の記事を参照

このリビジョンでの変更は良くわかりませんが、編集者を追加したり、文書と文言の修正などのようです。

P3427R2 Hazard Pointer Synchronous Reclamation

ハザードポインタライブラリにSynchronous Reclamation機能拡張を追加する提案。

以前の記事を参照

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

  • hazard_pointer_asynchronous_reclamationhazard_pointer_try_reclamationにリネーム
  • retire_to_cohortの文言に、"May reclaim possibly-reclaimable members of c."を追加

などです。

P3428R2 Hazard Pointer Batches

ハザードポインタライブラリに複数のハザードポインタをまとめて構築・破棄する機能拡張を追加する提案。

以前の記事を参照

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

  • 事前条件を緩和し、それを文言に反映
  • reset_hazard_pointer_batchclear_hazard_pointer_batchにリネーム
  • span1, span2span_from, span_toに変更
  • なるべくNの代わりにspan.size()を使用する

などです。

P3643R1 std::to_signed and std::to_unsigned

整数型を対応する符号付/符号なしの整数型に簡易に変換する関数の提案。

以前の記事を参照

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

  • 制約を厳格化し、cv boolだけでなくCV修飾型全般を除外
  • 機能テストマクロにfreestandingを追加

などです。

P3688R2 ASCII character utilities

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

以前の記事を参照

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

  • ascii_case_insensitive_compare()の戻り値型の誤りを修正
  • ascii_case_insensitive_compare()の定義におけるstrong_orderingstd::プリフィックスを削除

などです。

P3692R2 How to Avoid OOTA Without Really Trying

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

以前の記事を参照

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

  • volatileセマンティクスに関する変更について再検討
  • この提案が他のOOTA研究者にもたらす利点について言及
  • 著者リストの更新

などです。

P3702R1 Stricter requirements for document submissions (SD-7)

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

以前の記事を参照

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

などです。

P3719R1 std::is_vector_bool_reference

std::vector<bool>を検出する型特性を追加する提案。

以前の記事を参照

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

  • 概念・提案する文言・実装経験、について追加

などです。

P3739R2 Standard Library Hardening - using std::optional<T&>

std::inplace_vectorにおいて、std::optional<T&>を利用して堅牢化モードを導入する提案。

以前の記事を参照

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

  • exception_ptrの検査を追加
  • define_static_{string,object,array}を追加
  • constを追加

などです。

P3774R1 Rename std::nontype, and make it broadly useful

std::nontypestd::constant_argにリネームする提案。

以前の記事を参照

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

  • LEWGレビューを反映
    • std::nontypestd::constant_argへのリネーム
  • この提案はstd::nontypeのリネームのみを行うものであることを明確化
    • 以前の提案内容も残しておく
  • §2.2. Post-Sofia decisions にLEWGレビューの決定事項を要約
  • abstractの更新

などです。

この提案は、2025年11月の全体会議を通過し、C++26に採択されています。前述のように、リネームのみを行っています。

P3775R0 Slides for P3774R0 - Rename std::nontype

P3774R0の紹介スライド。

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

このスライドではC++26タイムフレーム内でのstd::nontypeの経緯を説明し、P3774R0での変更を簡単に紹介しています。

P3779R0 reserve() and capacity() for flat containers

std::flat_map等のflatなコンテナに、reserve()capacity()メンバ関数を追加する提案。

std::flat_setstd::flat_mapstd::flat_multisetstd::flat_multimapはメモリ上で連続した領域にソート済み配列を構築することでメモリの局所性を高め、イテレーションのパフォーマンスを向上させた連想コンテナです。

これらのコンテナはいずれも内部で別のシーケンスコンテナを使用しており、デフォルトではstd::vectorが使用されています(最後の方のテンプレート引数で変えられる)。

namespace std {
  // flat_mapの宣言例
  template <class Key,
            class T,
            class Compare = less<Key>,
            class KeyContainer = vector<Key>,
            class MappedContainer = vector<T>>
  class flat_map;
}

flatコンテナはこれらの内部コンテナに関する操作をほとんど公開しておらず、特にreserve()の様なパフォーマンス上重要なインターフェースも公開していません。

そのため、flatコンテナでreserve()を行おうとすると、.keys()/.values()を使って得たconst参照をconst_castするか、.extract()を使って取り出したコンテナを.replace()で戻す、という方法を取らざるを得ません。

std::flat_map<std::string, std::string> fmap = ...;

// 1. 内部コンテナ参照をconst_cast
const_cast<std::vector<std::string>&>(fmap.keys()).reserve(100);     
const_cast<std::vector<std::string>&>(fmap.values()).reserve(100); 

// 2. 取り出した内部コンテナを戻す
auto tmp = std::move(fmap).extract(); 
tmp.keys.reserve(100); 
tmp.values.reserve(100); 
fmap.replace(std::move(tmp.keys), std::move(tmp.values)); 

1つ目の方法は、set系のコンテナが.keys()/.values()を提供していないためflat_setなどでは使えません。2つ目の方法は、これらのインターフェース特有の事情による注意事項がいくつかあるためリスクがあります。

この提案はこの問題の解決のために、.reserve().capacity()flatコンテナに追加することを提案しています。

std::flat_map<std::string, std::string> fmap = ...;

// proposed: 
fmap.reserve(100); 

// proposed: 
if (fmap.capacity() == fmap.size()) { 
  fmap.reserve(100); 
} 
  • .reserve()
    • 内部コンテナのどちらか片方がサポートしている場合に提供
    • サポートしている内部コンテナに対して対応する.reserve()を呼び出す
  • .capacity()
    • 内部コンテナの両方がサポートしている場合に提供
    • 内部コンテナの返す.capacity()の値の最小値を返す

そして、set系のコンテナに対して.keys()/.values()メンバ関数を追加することも提案しています。

std::flat_set<std::string> fset = ...;

// どちらの関数からも同じコンテナが取得できる
const auto& data1 = fset.keys();     
const auto& data2 = fset.values(); 

set系のコンテナの場合、key=valueであるので.keys()/.values()メンバ関数はどちらも同じ内部コンテナへのconst参照を返します。

P3790R1 Pointer lifetime-end zap proposed solutions: Bag-of-bits pointer class

ポインタの参照先の寿命が尽きた後でも使用可能なライブラリユーティリティを提供する提案。

以前の記事を参照

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

  • launder_bag_of_bits_ptr()bag_of_bits_ptr<T>launder_ptr_bits()ptr_bits<T>にリネーム
  • 以前のusable_ptr<T>に対する別名候補の追加

などです。

P3796R1 Coroutine Task Issues

std::taskの設計上の問題点を修正する提案。

以前の記事を参照

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

  • co_return { ... };をサポートする提案を追加
  • P3801R0への回答を追加
  • co_yield with_error(e)が使いにくい理由を追記
  • change_coroutine_schedulerschedulerが代入可能であることを要求するという議論を追加
  • operator co_awaitの追加に関する議論を追加
  • bulktask_schedulerに関する議論を追加
  • 右辺値修飾子の欠落に関する議論を追加
  • return_valuereturn_voidに関する仕様が欠落しているという問題を追加
  • その他小さな修正

などです。

この提案では、以前の記事の分類における"その他の設計上の修正"のカテゴリに次の問題を追加しています

  • co_return { ... };のサポート追加
    • プロミス型のreturn_value()はテンプレートパラメータにデフォルト値が無いことで{}を使用できない
    • テンプレート引数のデフォルトを設定することで{}による初期化をサポートする
  • co_await change_coroutine_scheduler(sched)では代入可能なschedulerが必要になっている
    • 効果の規定でstd::exchangeを使用していることでschedに代入可能であることを要求しているが、schedulerは通常それが必須ではない
    • -> std::exchangeを使用しない定義で置き換えるか、代入可能性をschedulerの置換可能性の判定に使用する
  • sender非対応コルーチンはtaskco_awaitできるべき
    • いくつかの制約(提案文書 3.5.15 Sender Unaware Coroutines Should Be Able To co_await A task を参照)を考慮すると、現時点ではtaskoperator co_awaitを追加しないことが妥当と思われる
  • task_schedulerでのbulkの使用に関する問題
    • 通常のsenderと異なり、コルーチンで使用されるschedulerの型はtaskの作成時に認識されている必要があり、接続時にカスタムschedulerを使用するためには、型消去スケジューラであるtask_schedulerを使用する
    • senderアルゴリズムを特定のスケジューラ向けにカスタマイズする場合、task_schedulerが(型消去されていることによって)そのカスタマイズを伝達できないことで、意図通りに動作しない場合がある
    • bulkアルゴリズムparallel_schedulerがその典型例
    • -> いくつかの選択肢を提示
  • 右辺値修飾子の欠落
    • task_schedulerにネストされたsenderは必ずしもコピー可能ではなく、操作は型消去されるため、task_scheduler::ts-sender::connectは右辺値修飾する必要がある
    • コルーチンはコピーできず、コルーチンハンドルを別のオブジェクトに転送する場合その操作は右辺値修飾する必要があるため、task<T, E>::connectも右辺値修飾する必要がある
  • return_valuereturn_voidの仕様の欠落
    • プロミス型のreturn_valuereturn_voidは宣言されてはいるもののその仕様が欠落している
    • -> 対応する文言を提案

また、P3801R0への回答では提示されている問題点はこの提案でもある程度カバーされており理解できるものの、std::execution::taskを削除する理由にはならないとしています。

こことP3801で挙げられている問題のうち緊急性のあるものについてはNBコメント対応やLWG Issue対応としてC++26に向けて修正するつもりのようです。

P3798R1 The unexpected in std::expected

std::expected.has_error()を追加する提案。

以前の記事を参照

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

  • 例ではstd::expected<void, std::string>を使用するようにした
  • フィードバックによる文言の改善
  • __cpp_lib_expectedの値を更新
  • 謝辞の追加

などです。

P3806R0 views::cycle

入力範囲を無限に循環する範囲を生成するRangeアダプタ、views::cycleの提案。

views::repeat(v)vの繰り返しによる範囲を生成するのに対して、views::cycle(r)は範囲rを循環イテレーションすることによる繰り返し範囲を生成するものです。現在これは、views::repeat(r) | views::joinを用いて近似できます。

std::vector<int> r = {1, 2, 3};

for (auto&& v : views::repeat(r) | views::join) {
  // 1, 2, 3, 1, 2, 3, 1, 2... という要素列をイテレーションし続ける
  ...
}

これにはviews::joinを通すことによってもっとも強くてもbidirectional_rangeにしかならないなどの問題があり、専用のアダプタを用意することで直観的に使用できるようになるとして、views::cycleを追加することを提案しています。

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

for (auto&& song : playlist | views::cycle | views::take(100)) {
  play(song);
}

元の範囲を繰り返す性質上マルチパス保証が必要となるため、views::cycle(r)rforward_rangeである必要があります。

元の範囲をRとして、その他の性質は次のようになっています

  • rangeカテゴリ
    • Rrandom_access_rangeかつsized_range: random_access_range
    • Rbidirectional_rangeかつcommon_range: bidirectional_range
    • それ以外: forward_range
  • common_range: ×
  • borrowed_range: ×
  • sized_range: ×
  • const-iterable: const Rforward_rangeであれば

Range-v3にある同等のアダプタviews::cycledでは元の範囲の終端イテレータを積極的にキャッシュすることで、元の範囲のカテゴリを可能な限り維持しようとしますが、views::cycleではそれを行っていません(カテゴリがbidirectional_range/random_access_rangeになる場合の条件)。

また、views::cycleは同じ範囲を循環していることからiter_swapを提供しません。これは、イテレータとして異なる位置にある要素でも元の範囲上では同じ要素を指している可能性があり、その状況でiter_swapすると自己代入のようなことが起こってしまうためです。

P3809R0 Should we make std::linalg reductions deduce return types like fold algorithms?

std::linalgにあるリダクション系の関数の戻り値型の決定方法を変更しないようにすることを促す提案。

std::linalgにあるdot()vector_two_norm()などのリダクション(std::accumulateranges::fold_leftなどのように、範囲の各要素に何か処理をしてその結果を集計していくような計算)を行うような関数の戻り値型の決定方法は、std::reduceなどの設計を踏襲して初期値の型を戻り値型として使用します。しかし、C++23のranges::fold_leftなどfold操作においては、初期値と範囲の参照型を使用した二項演算の結果の型を推論して戻り値型として使用します。

// linalgのリダクション操作
std::mdspan<double, std::dims<1>> vec1 = ...;
std::mdspan<double, std::dims<1>> vec2 = ...;

std::same_as<int> auto dot = std::linalg::dot(vec1, vec2, 0); // ✅

// ranges::fold系リダクション操作
std::vector<double> vec3 = ...;

std::same_as<double> auto foldl = std::ranges::fold_left(vec3, 0, std::plus<>{}); // ✅

// std::reduce
std::same_as<int> auto reduce = std::reduce(vec3.begin(), vec3.end(), 0, std::plus<>{}); // ✅

このように、C++23以降のよく似た操作であってもその戻り値型の決定過程が異なります。また、ranges::fold_leftの戻り値型決定方法は、オリジナルのstd::accumulatestd::reduceも同様)において初期値の型で戻り値型が決まってしまうことが問題点として認識されていたことからそれを改善したものです。

また、数値アルゴリズムのRange版の提案(P3732R0)でもranges::fold_leftの設計を踏襲して同じ戻り値型の決定方法を採用していることもあり、std::linalgリダクション系の関数でもranges::fold_leftと同様の戻り値型の決定方法を取るべきではないかという声が上がったようです。

この提案は、それらの設計選択は合理的なものとしつつも、std::linalgは現在の動作が理に適ったものであるとして変更しない様にすることを提案するものです。

この理由としては次の事を挙げています

  1. 式テンプレートとの相性の良さ
    • std::reduceは式テンプレートによって計算が行われるような要素型をうまく扱えるが、ranges::fold_leftはそうではない
    • 数値計算分野においては式テンプレートが比較的良く使用されるため、考慮する必要がある
  2. BLAS規格との一貫性
    • BLASFortran 95インターフェースは、初期値の型によって戻り値型が決定される
  3. 戻り値型の簡単な制御
    • 線形代数領域と混合精度数値演算に精通しているユーザーは、戻り値型を推測するのではなく明示的に指定することを好む傾向にある

これらの理由から、std::linalgの現在の動作は線形代数計算の領域においては理に適ったものであるため、現状のまま変更しないことを推奨しています。

P3810R0 hardened memory safety guarantees

標準ライブラリ内の基本的な操作について、未定義動作の余地がないことを規定しておく提案。

C++26では標準ライブラリに堅牢化モードを導入し、一部の事前条件が堅牢化された事前条件としてC++26 Contractsの枠組みによって実行時にチェックされるようになります(P3471R4/P3607R0)。

コンテナ型はそこには当然含まれていますが、そのチェックにおいては.size().empty()といったメンバ関数が使用されます。これは実装を考慮すると未定義動作の余地なく実装可能と考えられますが、標準ではそのようなことを一切指定していません。

C++26 Contractsそのものが未定義動作が無いこと(契約チェック時のUBフリー)を保証してはいませんが、契約チェックに使用されるこれらの関数がUBフリーであることを保証できれば、少なくとも標準ライブラリの堅牢化モードの範囲においてはそのチェックがUBを伴わないことを保証することができます。

これらの理由からこの提案では、堅牢化モードのチェックに使用される標準ライブラリ中の基本的とみなされる操作について、未定義動作無しの実装を要求することを提案しています。

提案での対象は次の関数です

  • .size()
  • .empty()
  • イテレータの差分計算(-
  • .static_extent()
  • .extent()
  • .has_value()
  • .valueless_by_exception()
  • std::holds_alternative<I>()

これらの関数はメンバ変数の値を返すだけの単純な実装になるはずであり、UBの余地なく実装できるはずです。そしてそれはすでに行われているはずでもあるため、この提案による実装の変更は必要なく、実装者にUBフリーであることを要求することは不合理ではないとしています。

おわり

この記事のMarkdownソース