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

文書の一覧

全部で22本あります。

もくじ

P1255R11 A view of 0 or 1 elements: views::maybe

P1255R12 A view of 0 or 1 elements: views::maybe

任意のオブジェクトやstd::optional等のmaybeモナドな対象を要素数0か1のシーケンスに変換するRangeアダプタviews::maybe/views::nullableの提案。

以前の記事を参照

R11での変更は、Historyセクションに過去の履歴を移動したことなどです。

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

  • 設計と実装の詳細を拡張
  • maybe_viewのモナディック関数をDeducing thisを使用するように変更
  • Constraints, Mandates, Returns, Effectsの調整

などです。

P1709R5 Graph Library

グラフアルゴリズムとデータ構造のためのライブラリ機能の提案。

以前の記事を参照

このリビジョンでは

  • 深さ/幅優先探索及びトポロジカルソートを行うviewbasic_*バージョンを追加
  • 長い名前を避けるために深さ/幅優先探索についてdfs/bfsを使用するようにview名を変更
  • 定義を簡素化するために、アルゴリズム内のadjacency_listコンセプトをindex_adjacency_listコンセプトに置き換え
  • 最終的な定義を含めて、最短パスアルゴリズムを更新
  • トポロジカルソートアルゴリズムの説明を追加
  • compressed_graphの概要テーブルを追加
  • アルゴリズムの説明を追加及び更新
  • グラフ演算子の章を追加

などです。

P2019R5 Thread attributes

std::thread/std::jthreadにおいて、そのスレッドのスタックサイズとスレッド名を実行開始前に設定できるようにする提案。

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

  • thread_namethread::name_hintへリネーム
  • thread_stack_sizethread::stack_size_hintへリネーム
  • 単一引数のコンストラクタをexplicitにした
  • thread::name_hintをコピー及びムーブ不可能な型に変更し、その引数をコピーする必要がないようにした
    • これによって、スタックを浪費することなく長いスレッド名を渡すことができる
  • jthreadのための完全な文言を追加

このリビジョンではスレッド属性クラスはstd::threadの内部クラスになっています

namespace std {
  
  class thread {
    class id;
    class name_hint;
    class stack_size_hint;
  };

  class jthread {
    using id = thread::id;
    using name_hint = thread::name_hint;
    using stack_size_hint = thread::stack_size_hint;
  };
}

また、name_hintクラスは次のような実装になります

namespace std {

  template<typename T>
  class thread::name_hint {
    explicit constexpr name_hint(std::basic_string_view<T> name) noexcept;
    
    name_hint(name_hint&&) = delete;
    name_hint(const name_hint&) = delete;

  private:
    std::basic_string_view<T> __name; // exposition-only
  };
}

P2527R3 std::variant_alternative_index and std::tuple_element_index

std::variantに対して、型からそのインデックスを取得するための方法を追加する提案。

以前の記事を参照

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

  • constな部分特殊化を削除
  • LEWGへの質問を追加
  • 実装可能性セクションを更新
  • 文言の修正

などです。

この提案はすでにLEWGのレビューを終えて、LWGへ転送されています。

P2664R6 Proposal to extend std::simd with permutation API

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

以前の記事を参照

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

  • 名前付き順列生成関数に関するセクションを個別の提案へ分離
  • gather_fromscatter_toイテレータの代わりに範囲を使用するように変更
  • 動作詳細と実装経験を更新
  • 非メンバ添字演算子に関するセクションを削除

などです。

P2748R3 Disallow Binding a Returned Glvalue to a Temporary

P2748R4 Disallow Binding a Returned Glvalue to a Temporary

glvalueが暗黙変換によって一時オブジェクトとして参照に束縛される場合をコンパイルエラーとする提案。

以前の記事を参照

R3での変更は

  • 参照が式ではなくオブジェクトに束縛されることをハンドルするために文言を調整
  • std::is_convertibleの規定における区別を削除

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

  • std::is_convertibleの規定における区別を復帰
  • LWG issue 3400の議論における不正確さの修正

などです。

現在のstd::is_convertiblereturn文を使用してその変換可能性が規定されているため、この提案による変更の影響を受けて結果が変わってしまいます。std::is_convertiblereturn文で変換できるかどうかではなく、暗黙変換が可能かどうかということを検出しているため、return文に限定された特別扱いであるこの提案の影響を受けることは望ましくありません。

その扱いに関しては紆余曲折あったようですが、現在のリビジョンではstd::is_convertibleに限定された除外規定を設けることでこの問題に対処しています。

P2835R2 Expose std::atomic_ref's object address

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

以前の記事を参照

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

などです。

P2894R2 Constant evaluation of Contracts

定数式においても契約チェックを有効化する提案。

以前の記事を参照

このリビジョンでは、全体的に文章の改善や関連する提案の更新などが行われているようですが、基本的なところはR1と変化はないようです。

P2900R4 Contracts for C++

C++ 契約プログラミング機能の提案。

以前の記事を参照

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

  • 契約注釈の定数評価のルールを追加
    • P2894R2を採択
    • 定数評価できない契約条件式は契約違反、定数式における契約注釈のセマンティクスは実装定義(エラーになるかは選択されたセマンティクス次第)
  • <contracts>をフリースタンディング指定
  • enforceセマンティクスでプログラムの終了時に、実装定義の方法で終了するとしていたのをstd::abort()を呼び出すように変更
  • チェックされた契約条件式の副作用は、評価が正常にリターンしたときにのみ省略できることを明確化
  • contract_violationオブジェクトのメモリがoperator newを通して割り当てられないことを明確化(例外オブジェクトと同様)
  • Design Principlesセクションを追加

などです。

P2932R3 A Principled Approach to Open Design Questions for Contracts

契約機能に関する未解決の問題についての設計原則に基づく解決策の提案。

以前の記事を参照

このリビジョンでの変更は、基本原則2を明確化したことです。

P2946R1 A flexible solution to the problems of noexcept

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

以前の記事を参照

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

  • オプションでブール引数を取るようにした
  • [[throws_nothing]]noexceptの両方の指定を持つ場合の振る舞いについて注記を追加
  • [[throws_nothing]]を付加する場所に式を含めることを検討するセクションを追加

などです。

このリビジョンでは、[[throws_nothing(expr)]]として追加のbool引数を取ることができるようになりました。これはnoexcept(expr)演算子ではない方)と同様に、exprfalseに評価される場合には[[throws_nothing]]属性は効果を持たなくなります(指定されていない場合と同様になる)。

P2957R1 Contracts and coroutines

コルーチンに対して契約を有効化した場合に、各種の契約がどのように動作するのかについての提案。

以前の記事を参照

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

  • 事前条件が関数引数のコピーの前後どちらのタイミングにおけるものを参照するかについて、未規定とした
  • コルーチンにおける事後条件指定を提案しない

などです。

P2963R1 Ordering of constraints involving fold expressions

コンセプトの制約式として畳み込み式を使用した場合に、意図通りの順序付を行うようにする提案。

以前の記事を参照

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

  • 畳み込み式の包摂関係を調べるためにパックのサイズにアクセスしないようにした
  • 実装経験セクションを拡張して、Compiler Explorerで利用可能なこの提案の完全な実装について追記
  • 例を追加

などです。

P2988R1 std::optional<T&>

std::optionalが参照を保持することができるようにする提案。

以前の記事を参照

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

P3044R0 sub-string_view from string

std::stringから直接string_viewを取得する関数を追加する提案。

文字列の非所有参照(ビュー)としてstd:string_viewC++17で標準ライブラリに追加されましたが、std::stringAPIstd::string_viewを受けるものはあっても返すものはありません。現在の環境でstd::stringAPIを設計することを考えると、std::string_viewを返すことが妥当である関数として.substr()が挙げられます。特に、substr() const &は即時コピーを必要とするコンテキストではほとんど間違いなく呼び出されません。

現在の.substr()const左辺値参照オーバーロードは、部分文字列をコピーして返しています。この戻り値型をstd::string_viewに変更するのは互換性の問題から不可能であるため、この提案では新しいメンバ関数を追加することで部分文字列をstd::string_viewで取得可能にすることを提案しています。

提案しているメンバ関数.subview()という名前です。

template<typename charT, typename traits = char_traits<charT>, typename Allocator = allocator<charT>>
struct basic_string {
  ...
  
  // 既存のsubstr()
  constexpr basic_string substr(size_type pos = 0, size_type n = npos) const &;
  constexpr basic_string substr(size_type pos = 0, size_type n = npos) &&;

  // 提案するsubview()
  constexpr basic_string_view<charT, traits> subview(size_type pos = 0, size_type n = npos) const;

  ...

};

同時に、std::string_viewとのAPI一貫性のために、std::string_viewにもこの.subview()を追加することを提案しています。

また、さらにこの関数のcontiguousなコンテナに対する一般化として、.subspan()arrayvectorstring(_view)に対して追加する方向性を示しており、LEWGはその方向性に興味を持っているようです。

P3054R0 2023-12 Library Evolution Poll Outcomes

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

投票にかけられた提案は次のものです

全てC++26に向けてLWGに転送されています。文書では、投票の際に寄せられたコメントが記載されています。

P3079R0 Should ignore and observe exist for constant evaluation of contracts?

定数式で契約述語を評価する場合に、チェックする(enforce)以外のセマンティクスを考慮する必要があるかどうかについて問うスライド。

P2894などで議論されている、定数式における契約条件のチェックに関しては、実行時と同様のセマンティクス(3つのセマンティクスから実装定義)とすることが提案されています。

このスライドは、それに対して、enforce以外のセマンティクスを定数式における契約チェック時に考慮する必要があるのかについて問うものです。

ignoreセマンティクスは契約注釈を全て無視することでチェックにかかるコストをゼロにするもので、observeはチェックはするもののエラーにはしないものです。どちらも、実行時コストの最小化や実行時エラーが起きてもプログラムの実行を継続するなど、実行時においては有用性があります。

ただ、それは定数式には当てはまらないと思われます。

定数式では実行時と異なり、契約違反(=プログラムのバグ)を検出したとしてもそれを無視する合理的理由が見当たりません。実行時であれば、とにかく継続することが重要となるプログラムが想定されますが、定数式(すなわちコンパイル時)に検出されたプログラムのバグがその実行にとって致命的となることはなく、むしろバグを早期発見できているので通常のバグ修正フローに従ってバグを修正すべきです。

契約条件が定数式で実行できない場合は無視するよりもそれを定数式で動作するように変更すべきであり、契約注釈が間違っているのであれば検出して早期に修正するべきです。定数式において契約を無視することはそれによって得られるメリット(コンパイラ実装間の挙動差異を無視するなど)よりもデメリットが勝ると考えられます。

残された問題は、定数式における契約チェックにかかるコスト(コンパイル負荷)に関してですが、これについては契約チェックがどれほどコンパイル時コストに影響するのかについての報告が乏しいため、現時点では(定数式において契約を無視できるようにする)説得力のある理由ではない、としています。

このスライドではまとめとして

  • 定数式における契約のセマンティクスとしてignoreobserveを許可することについては、その正当性を示す必要がある
  • コンパイル時の契約評価のコストはその理由として十分かもしれないが、根拠を示す必要がある

としています。

P3084R0 Slides for LEWG views::maybe 20240109

P1255で提案されている、views::nullable/views::maybeの紹介スライド。

提案の主張等が簡単にまとめられています。

P3086R0 Proxy: A Pointer-Semantics-Based Polymorphism Library

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

これは以前にP0957で提案されていたものをさらに改善したものです。モチベーション等はそちらと共通するので以前の記事を参照

P0957はLEWGにおいて議論に時間をかけるコンセンサスが得られなかったため追及はストップされていました。

P0957R9と比較して、この提案では次のような変更が加えられています

  • 以前にあったdispatchfacadeの定義を支援する機能は削除された
  • proxiable_ptr_constraints構造体はポインタへの制約の抽象化
  • 1つのdispatch定義で複数のオーバーロードを制御可能になった
  • proxy::invoke()const修飾
  • dispatchが1つだけの場合、proxy::operator()を追加
  • basic_facade, facadeコンセプトを追加

IDrawableインターフェースのコードを書き直す例

// Draw()のメタデータ
struct Draw {
  using overload_types = std::tuple<void()>;

  template<class T>
  void operator()(T& self)
    requires(requires{ self.Draw(); })
  {
    self.Draw();
  }
};

// Drawableのインターフェース定義
struct FDrawable {
  using dispatch_types = Draw;

  static constexpr auto constraints = std::relocatable_ptr_constraints;
  
  using reflection_type = void;
};

// proxyへの登録
PRO_DEF_MEMBER_DISPATCH(Draw, void());
PRO_DEF_FACADE(FDrawable, Draw);

class Rectangle {
 public:
  void Draw() const;

  void SetWidth(double width);
  void SetHeight(double height);
  void SetTransparency(double);
  double Area() const;
};

class Circle {
 public:
  void Draw() const;

  void SetRadius(double radius);
  void SetTransparency(double transparency);
  double Area() const;
};

class Point {
 public:
  void Draw() const;

  constexpr double Area() const { return 0; }
};

void DoSomethingWithDrawable(std::proxy<FDrawable> p) {
  p.invoke<Draw>();

  // FDrawableに1つしかディスパッチ定義がないなら
  p.invke();
  // もしくは
  p();
}

Area()もインターフェースに追加したくなった場合は

// Draw()のメタデータ
struct Draw {
  using overload_types = std::tuple<void()>;

  template<class T>
  void operator()(T& self)
    requires(requires{ self.Draw(); })
  {
    self.Draw();
  }
};

// Area()のメタデータ
struct Area {
  using overload_types = std::tuple<double()>;

  template<class T>
  double operator()(T& self)
    requires(requires{ {self.Area()} -> std::same_as<double>; })
  {
    return self.Area();
  }
};

// Drawableのインターフェース定義
struct FDrawable {
  using dispatch_types = std::tuple<Draw, Area>;

  static constexpr auto constraints = std::relocatable_ptr_constraints;
  
  using reflection_type = void;
};

// proxyへの登録
PRO_DEF_MEMBER_DISPATCH(Draw, void());
PRO_DEF_MEMBER_DISPATCH(Area, double());
PRO_DEF_FACADE(FDrawable, PRO_MAKE_DISPATCH_PACK(Draw, Area));

...

void DoSomethingWithDrawable(std::proxy<FDrawable> p) {
  // .Draw()呼び出し
  p.invoke<Draw>();

  // .Area()呼び出し
  p.invoke<Area>();
}

P3087R0 Make direct-initialization for enumeration types at least as permissive as direct-list-initialization

スコープ付き列挙型の値の初期化時に、直接初期化を許可する提案。

スコープ付き列挙型の値の初期化宣言においては、直接リスト初期化は許可されている一方で直接初期化は許可されていません。

enum class E {};

E a{0}; // ok、直接リスト初期化
E b(0); // ng、直接初期化

直接リスト初期化は縮小変換を行わないない直接初期化として、直接初期化のより制限的な形式として認識されています。しかし、スコープ付き列挙型の初期化時にはその直感が成立していません。ここで直接初期化を許可することでその直感が復帰し、言語の一貫性が増します。

また、()による集成体初期化の許可と同様に、コンテナに完全転送する際の使いやすさを向上させることができます。

std::vector<std::byte> bytes;

bytes.emplace_back(0xff);   // 現在ng、この提案後はok

おわり

この記事のMarkdownソース