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

文書の一覧

採択されたものはありません、全部で58本あります。

が、SG22(C/C++相互互換性に関する研究グループ)経由でCの提案が含まれているので、そこからC++に対してのものでないものを除くと48本になります。

N4880 PL22.16/WG21 agenda: 22 February 2021, Virtual Meeting

2021年2月22日 11:00 (北米時間)に行われるWG21本会議のアジェンダです。

C++23のための2回目の全体会議です。

N4881 WG21 virtual meetings: 2021-02, -06, and -10

今年のWG21全体会議の予定表。

↑の2月のものを除くと、6月と10月に予定されています。どちらもオンラインで行われることが決定しています。

N4882 WG21 admin telecon meetings: 2021-02, -05, and -09

全体会議に共通する大まかな進行表。

N4883 WG21 February 2021 admin telecon minutes

2021年2月8日に行われた、WG21各SG等の責任者によるミーティングの議事録。

P0401R6 Providing size feedback in the Allocator interface

アロケータが実際に確保したメモリのサイズをフィードバックすることのできるメモリ確保インターフェースを追加する提案。

以前の記事を参照

このリビジョンでの変更は、LWGのフィードバックを受けて提案する文言を改善したことです。

この提案はすでにLWGでのレビューが完了しており、そこではこの提案はC++23に導入する事に全会一致でのコンセンサスが取れています。そのため、次の全体会議(2021年6月)で全体投票にかけられ、問題がなければC++23に入る見込みです。

P0448R3 A strstream replacement using span as buffer

長い間非推奨のまま代替手段のなかったstd::strstreamの代替となるstd::spanによるspanstreamを追加する提案。

std::strstreamは事前に確保された固定長のバッファを受け取りそれを利用したストリームを構築できるものでしたが、同時に可変長の内部バッファを扱う機能も持っており(コンストラクタでスイッチする)、その結果.str()から返されるchar*の指すメモリの管理責任が不明瞭になっていました。また、可変長バッファを使用する場合は.freez(false)をしてからデストラクタを呼び出す必要があるのですがわかりづらく、忘れられることが多かったようです。

このように、使いづらくメモリリークの危険性を備えていることからC++98でstd::strstreamは非推奨とされました。ただし、固定長バッファからストリームを構築し、そのバッファを文字列として参照する、という機能は有用で完全に代替するものが無かったことから削除されずに今日まで残っています。

文字列ベースのストリームという機能はstd::stringstreamが代替として利用できますが、固定長バッファによるストリームを代替する機能はありませんでした。

この提案はstd::strstreamの機能の一つだった、事前に確保された固定サイズのバッファを用いたストリームをstd::spanを利用して実現するものです。

ヘッダ<spanstrem>に以下のものが追加されます。

  • std::basic_spanbuf
    • std::spanbuf
    • std::wspanbuf
  • std::basic_ispanstream
    • std::ispanstream
    • std::wispanstream
  • std::basic_ospanstream
    • std::ospanstream
    • std::wospanstream
  • std::basic_spanstream
    • std::spanstream
    • std::wspanstream
// 入力ストリームのサンプル

char input[] = "10 20 30";

std::ispanstream is{std::span<char>{input}};

int i;
is >> i;
ASSERT_EQUAL(10,i);

is >> i;
ASSERT_EQUAL(20,i);

is >> i;
ASSERT_EQUAL(30,i);

is >>i;
ASSERT(!is);
// 出力ストリームのサンプル

char output[30]{}; // zero-initialize array

ospanstream os{span<char>{output}};
os << 10 << 20 << 30;

auto const sp = os.span();

ASSERT_EQUAL(6, sp.size());
ASSERT_EQUAL("102030", std::string(sp.data(),sp.size()));
ASSERT_EQUAL(static_cast<void*>(output), sp.data()); // ポインタの比較
ASSERT_EQUAL("102030", output);

P0849R7 auto(x): decay-copy in the language

明示的にdecay-copyを行うための構文を追加する提案。

以前の記事を参照

このリビジョンでの変更は、decltype(auto(...))decay_tとの構文比較表の追加とLWG Issue 3491に関する文言を別のところで議論することにしたこと、および関連しそうな機能(P2255R0 A type trait to detect reference binding to temporaryP0847R6 Deducing this)との相互作用についての追記です。

この提案は、ライブラリパートについてLEWGからLWGへ転送され、そこでのレビューが完了次第CWGに送られ最後のレビューを迎えます。

P1018R8 C++ Language Evolution status 🦠 pandemic edition 🦠 2020/11–2021/01

EWG(コア言語への新機能追加についての作業部会)が2020/11–2021/01の間に議論した提案やIssueのリストや将来の計画、テレカンファレンスの状況などをまとめた文書。

前回と比べると、多くのIssueに関して議論をしていたようです。

P1072R7 basic_string::resize_and_overwrite

std:stringに領域(文字長)を拡張しつつその部分を利用可能にする為のメンバ関数resize_and_overwrite()を追加する提案。

以前の記事を参照

このリビジョンでの変更は、記述の修正がメインの様です。

P1322R3 Networking TS enhancement to enable custom I/O executors

Networking TSのI/Oオブジェクトをio_contextだけではなく、任意のExecutorによって構築できるようにする提案。

以前の記事を参照

このリビジョンでの変更は、AcceptableProtocol要件に入れ子::socket_for<Executor>を追加し、ip::tcp,ip::udpクラスがそれを備えるようにしたことなどの、提案する文言の調整です。

P1425R3 Iterators pair constructors for stack and queue

std::stackstd::queueに、イテレータペアを受け取るコンストラクタを追加する提案。

以前の記事を参照

このリビジョンでの変更は、アロケータ型も含めてテンプレートパラメータを推論するための推論補助を追加したことです。

この提案はLEWGからLWGへ送られ、LWGでのレビューの結果、まだ未公開のR4がC++23入りのコンセンサスを得たようです。おそらく次の全体会議(2021年6月)でC++23に採択されます。

P1659R2 starts_with and ends_with

P1659R3 starts_with and ends_with

任意の範囲に対して動作するstd::ranges::starts_with/std::ranges::ends_withの提案。

以前の記事を参照

R2およびR3での変更は、提案する文言の修正がメインです。

この提案はすでにLWGでのレビューを終えており、LWGでのC++23入りのコンセンサスを得ています。おそらく次の全体会議(2021年6月)でC++23に採択されます。

P1682R3 std::to_underlying

列挙型の値からその基底の整数型への変換を行うstd::to_underlyingの提案。

以前の記事を参照

このリビジョンでの変更は、機能テストマクロの修正など提案する文言の調整のみです。

この提案は2021年2月22日(米国時間)に行われた全体会議でC++23入りが承認されています。次のワーキングドラフトからC++23に反映されます。

P1885R5 Naming Text Encodings to Demystify Them

システムの文字エンコーディングを取得し、識別や出力が可能なライブラリを追加する提案。

以前の記事を参照

このリビジョンでの変更は以下のものです。

  • エンコーディング名としてchar*を返す動機の説明の追記
  • 文言の改善と、フリースタンディング関連の文言の削除
  • systemという言葉のenvironmentへの置き換え
  • 名前の照合に問題のある古いエンコーディングを除外

LEWGでの最初のレビューの結果、提案の方向性への支持とこの提案のために作業時間をかけることへのコンセンサスが得られています。とはいえ、まだLEWGでの議論は続きます。

P1951R1 Default Arguments for pair's Forwarding Constructor

std::pair<T1, T2>forwarding constructor(要素型に変換可能な値を転送して構築するコンストラクタ)のテンプレートパラメータにデフォルト引数としてT1, T2を指定する提案。

現在のstd::pairでは次のようなコードを書くと、見えない所にオーバーヘッドを埋め込むことになります。

// std::stringとstd::vector<std::string>の一時オブジェクトが作られ、コピーされる
std::pair<std::string, std::vector<std::string>> p("hello", {});

std::pair<T1, T2>について、上記コードのコンストラクタのオーバーロード解決では、次の2つのコンストラクタが考慮されることになります。

explicit(see below) constexpr pair(const T1& x, const T2& y);

template <class U, class V>
explicit(...) constexpr pair(U&& x, V&& y);

しかし、2つ目の候補は、第二引数が{}のみであることからVの推定ができないため候補から外されます。結果1つ目の候補が選ばれ、一時オブジェクトの作成とそこからのコピー構築が行われます。

この場合に2つ目のコンストラクタを選択しようとする場合、次のように書く必要があります。

std::pair<std::string, std::vector<std::string>> p("hello", std::vector<std::string>{});

しかしこの構文は冗長でユーザーフレンドリーではありません。

この提案は2つ目のコンストラクタ(forwarding constructor)のデフォルトテンプレートパラメータとしてT1, T2を入れておくことで、最初の例のように書いた場合でも常にforwarding constructorが選択されるようにするものです。

2つ目の形式のコンストラクタは次のように変更します。

template <class U = T1, class V = T2>
explicit(...) constexpr pair(U&& x, V&& y);

たったこれだけの事で、{}の初期化子の使用が意図通りになるようになります。std::pairの構築で{}を使用している既存のコードの振る舞いが変更され、一時オブジェクトのコピーからムーブまたは完全転送による構築に切り替わります。これは明らかに望まれる動作であり、それによって壊れるコードはないものと思われます。

この提案の内容とC++23導入を目指すことはLEWGにおいて合意が取れており、LWGに転送するためにLEWGでの投票を待っている所です。

P2047R1 An allocator-aware optional type

Allocator Awarestd::optionalである、std::pmr::optionalを追加する提案。

Allocator Awareなオブジェクトとは、次の要件を満たすものです。

  • 一度構築されると、そのアロケータは変更されない。
  • オブジェクトのコンストラクタはuses-allocator構築で指定される2つの方法のいずれかでアロケータを受け取る。
    • これによって、コンテナの要素となるときにコンテナのアロケータが伝播される。
  • Allocator Awareな型を要素としうるオブジェクトはそれ自体がAllocator Awareであり、受け取ったアロケータを要素に伝播させなければならない。

現在のstd::optionalAllocator Awareではなく、上記ルールのいずれも満たしません。有効値を破棄して無効状態となるとそこで使用されていたアロケータを忘れてしまい、uses-allocator構築のプロトコルに従ったアロケータを受け取るコンストラクタを持っていないため、コンテナで使用されたときに要素のオブジェクトに適切にアロケータが伝播しません。

結果、現在のstd::optionalはコンテナで使用するときにアロケータを適切に管理できず、例えばpmrコンテナの不変条件である全ての要素が同じアロケータを使用する、という条件を満たすことができません(これは未定義動作につながります)。

この提案のstd::pmr::optionalAllocator Awareな要素型を持つときに上記3つのルールに従うようにすることで、自身もAllocator Awareとなるものです。そのために、構築に使用されたアロケータを内部で保持しています。

std::pmr::optionalの基本設計は単純で、全てのコンストラクタでアロケータを受け取るようにした上で、受けたアロケータを使用して要素を構築するようにします。また、そのように渡されたアロケータを内部で保持し、有効値の再構築時に使用します。

ただし、対象とするアロケータはstd::pmr::polymorphic_allocatorのみです。これによってアロケータの型がstd::optionalの型に現れることを防止します(polymorphic_allocatorC++20での改修によってこのような型に依存しない運用が可能になっています)。

これは従来のstd::optionalを置き換えるものではなく、std::optionalの振る舞いを変更するものでもありません。また、std::pmr::optionalAllocator Awareでない型を保持する場合は従来のstd::optionalに切り替わります。すなわち、std::pmr::optionalAllocator Awareな型を要素とする場合にのみ、std::optionalと異なる振る舞いをします。

std::pmr::optionalジェネリックなコードで(特にコンテナの要素型として)使用して、Allocator Awareな型を要素とする場合に適切にアロケータが伝播されるようにするものです。そして、std::pmr::optionalstd::optionalの単なるエイリアスではなく別の型として実装されることになります。

なお、std::pmr::optionalAllocator Awareな型を要素とする場合でも、そのアロケータを使用して自身のためのストレージを確保するものではありません。要素のストレージは従来通り自身の内部にあります。

P2066R5 Suggested draft TS for C++ Extensions for Minimal Transactional Memory

現在のトランザクショナルメモリTS仕様の一部だけを、軽量トランザクショナルメモリとしてC++へ導入する提案。

以前の記事を参照

このリビジョンの変更点は、atomicブロックでのthrow式のうち、ブロック内で処理される例外は未定義動作ではないとされたこと、ほぼ全ての標準ライブラリ関数はatomicブロック内で使用可能とされたことです。

P2093R4 Formatted output

std::formatによるフォーマットを使用しながら出力できる新I/Oライブラリstd::printの提案。

前回の記事を参照

このリビジョンでの変更は、_isattyを使って処理の例示を行なっていた所をGetConsoleModeに置き換えた事です。

この提案はSG16での議論とレビューを終えて、LEWGに送られたようです。

P2128R3 Multidimensional subscript operator

多次元コンテナサポートのために添字演算子[])が複数の引数を取れるようにする提案。

前回の記事を参照

このリビジョンでの変更は、t[a][b]のような構文をt[a, b]と書き換えてoperator[]に委譲する機能についての議論を追加した事です。ただし、これはここでは提案されていません。

P2162R2 Inheriting from std::variant (resolving LWG3052)

std::variantを公開継承している型に対してもstd::visit()できるようにする提案。

以前の記事を参照 - P2162R0 Inheriting from std::variant (resolving LWG3052) - [C++]WG21月次提案文書を眺める(2020年5月) - P2162R1 Inheriting from std::variant (resolving LWG3052) - [C++]WG21月次提案文書を眺める(2020年8月)

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

この提案は2021年2月22日(米国時間)に行われた全体会議でC++23入りが承認されています。次のワーキングドラフトからC++23に反映されます。

P2164R4 views::enumerate

元のシーケンスの各要素にインデックスを紐付けた要素からなる新しいシーケンスを作成するRangeアダプタviews::enumrateの提案。

以前の記事を参照

このリビジョンでの変更は、views::enumrateの間接参照結果の型がindex/valueという名前のメンバを持つようにするために必要な事について追記された事です。

以前の提案では結果の型はシンプルな集成体で、views::enumrateイテレータvalue_typereferenceは同じ型とされていました。

struct result {
  count index;
  T value;
};

しかし、indirectly_readableコンセプトを満たすためにはvalue_typereferenceの間のcommon_referenceが必要であり、そのためにはそこそこ大きな追加の実装が必要となります。

このリビジョンではその実装の一部を示すとともに、std::pair/tupleを再現せずまた利用せず、かつ名前のついた(説明専用でない)新しい型を導入する方向性を提案しています。

また、以前の提案では上記result型のメンバはconstメンバでしたが、LEWGでのレビューで否定されたため、非constに修正されました。

P2195R2 Electronic Straw Polls

各委員会での投票が必要となる際に、メールまたは電子投票システムを用いて投票できるようにする提案。

以前の記事を参照

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

P2216R3 std::format improvements

std::formatの機能改善の提案。

以前の記事を参照

このリビジョンでの変更は、提案している文言の調整がメインです。

この提案はすでにC++20に逆適用されることがほぼ決まっています。

P2231R1 Missing constexpr in std::optional and std::variant

std::optionalstd::variantをさらにconstexpr対応させる提案。

以前の記事を参照

このリビジョンでの変更は、機能テストマクロを追加した事です。

この提案はLEWGでのレビューが終わる前にLWGでのレビューが完了しており、C++23に導入するコンセンサスが得られています。LEWGのレビュー完了を待って、全体会議での投票にかけられる予定です。

P2234R1 Consider a UB and IF-NDR Audit

C++標準のUB(undefined behavior)とIF-NDRill-formed no diagnostic required)について、委員会の小さなチームによって監査されるプロセスの提案。

以前の記事を参照

このリビジョンでの変更は、R0が何を目的としているか混乱を招いたようで、文書を全体的に再構成した事です。

P2242R1 Non-literal variables (and labels and gotos) in constexpr functions

constexpr関数において、コンパイル時に評価されなければgotoやラベル、非リテラル型の変数宣言を許可する提案。

以前の記事を参照

このリビジョンでの変更は、この提案は何かを新しくconstexpr関数で実行可能にしようとするものではなく、そのことを明記した事です。

この提案はEWGでのレビューでは反対意見なくCWGへ転送されるコンセンサスが得られています。このリビジョンのEWGでの投票を待って、CWGに送られる予定です。

P2249R0 Mixed comparisons for smart pointers

スマートポインターの比較演算子に生ポインタとの直接比較を追加する提案。

スマートポインタ型はリソースの所有権を表現する方法として広く認識されており、対して生ポインタはリソースにアクセスするために用いる所有権を保有しないものとして認識されつつあります。

そして、どちらも任意のリソース(オブジェクト)のアドレスを表現するという意味論を共有しています。

その一方で、現在の標準ライブラリにあるスマートポインタ型の比較演算子は自身と同じ型(テンプレートパラメータを除いて)との間の比較演算子しか備えていません。そのため、生ポインタとスマートポインタの間で比較をするためには、一旦スマートポインタから生ポインタを取り出さなければなりません。

std::shared_ptr<object> sptr1, sptr2;
object* rawptr;

// 2つのポインタが同じオブジェクトを指しているかを調べる。
if (sptr1 == sptr2) { ... }        // OK
if (sptr1 == rawptr) { ... }       // Error
if (sptr1.get() == rawptr) { ... } // OK

この事は同値比較演算子だけではなく、大小比較を行う関係演算子でも同様です。

スマートポインタと生ポインタの比較を行うケースは一般的に発生しているため、スマートポインタに生ポインタとの比較演算子を追加する事でこのような不整合を修正しようとする提案です。

この提案では、std::unique_ptrstd::shared_ptrに対して保持するポインタと比較可能な任意の型との比較を行う==<=>を追加する事で、生ポインタとの比較を実装しています。

// ManagerはObjectを所有し、利用者にはそのポインタを貸し与える
// クライアントは特定のObjectについて作業をしてもらうために、借りたポインタをManagerに渡す事で作業を依頼する
class Manager {

  std::vector<std::unique_ptr<Object>> objects;

public:
  // Objectのポインタを取得
  Object* get_object(std::size_t index) const {
    return objects[index].get();
  }

  // 指定したObjectを削除する
  void drop_object(Object* input) {
    // 直接比較できないので述語オブジェクトを作成しなければならない
    auto isEqual = [input](const std::unique_ptr<Object>& o) {
        return o.get() == input;
    };
    erase_if(objects, input);

    // この提案の後では、次の1行で事足りる
    erase(objects, input);
  }

  // Objectのインデックスを得る
  ssize_t index_for_object(Object* input) const {
    // 先ほどと同じ理由
    // このような述語オブジェクトは様々なところで重複して頻出する可能性がある
    // 一元化すればいいのだが、多くの場合その場で書いてそのままにしがち・・・
    auto isEqual = [input](const std::unique_ptr<Object>& o) {
        return o.get() == input;
    };
    auto it = std::ranges::find_if(objects, isEqual);
    // etc.

    // この提案の後では、次の1行で事足りる
    auto it = std::ranges::find(objects, input);
  }
};

P2280R1 Using unknown references in constant expressions

定数式での参照のコピーを許可する提案。

以前の記事を参照

このリビジョンでの変更は、EWGのレビューと投票の結果を記載した事と、提案する文言を改善した事(特にthisポインタの定数式での利用を明示的に許可したこと)です。

P2281R1 Clarifying range adaptor objects

range adaptor objectがその引数を安全に束縛し、自身の値カテゴリに応じて内部状態を適切にコピー/ムーブする事を明確化する提案。

以前の記事を参照

このリビジョンでの変更は、LWGからのフィードバックを提案する文言に反映した事です。

この提案はライブラリのIssue解決の多ものものであるのでLWGからレビューが開始されています。そこではC++23に向けて導入するコンセンサスが得られており、次の全体会議(2021年6月)で採択のための投票にかけられる予定です。

P2286R1 Formatting Ranges

任意の範囲を手軽に出力できる機能を追加する提案。

以前の記事を参照

このリビジョンでの変更は、実装定義としている範囲の出力フォーマットのオプションを提示した事です。何人かの方が筆者の方にフォーマットを実装定義としていることは受け入れられないと伝えているようです。

P2287R1 Designated-initializers for base classes

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

以前の記事を参照

このリビジョンでの変更は、R0で提案していた基底クラスを指定する構文を拡張して、{}を取り除いた形の基底クラスのメンバを直接指定した初期化を許可するようにしたことです。

struct A {
  int a;
};

struct B : A {
  int b;
};

int main() {
  // R0で提案した構文
  B b1{:A = {.a = 1}, b = 2};
  B b2{:A{.a = 1}, b = 2};
  B b3{:A{1}, .b{2}};

  // R1で追加された形式
  B b4{.a = 1, .b = 2};
  B b5{.a{1}, .b{2}};
}

P2289R0 2021 Winter Library Evolution Polls

2021年の冬(1月から3月にかけて)に予定されている、LEWGでの全体投票の予定表。

以下の9つの提案が投票にかけられる予定です。

基本的にはLEWGでの作業を完了してLWG(CWG)へ転送することを確認するための投票です。

P2290R0 Delimited escape sequences

文字・文字列定数中の8進・16進エスケープシーケンスおよびユニバーサル文字名について、その区切りが明確になるような形式を追加する提案。

現在文字列中のエスケープシーケンスには、ユニバーサル文字名(\uxx... or \Uxx...)、8進エスケープシーケンス(\ooo)、16進エスケープシーケンス(\xnn...)の3種類があります。8進エスケープシーケンスは3文字制限がありますが、16進エスケープシーケンスには長さの制限はありません。そして、どちらもエスケープシーケンス中に受け付けられない文字が出てきたらそこでエスケープシーケンスを終了するようになっています。

これによって、次のような問題が発生します。

"\17";      // 8進エスケープシーケンス、"0x0f"と等価
"\18";      // 8進エスケープシーケンスと文字、"0x01 8"の2文字
"\xabc";    // 1文字
"\xab" "c"; // 2文字

つまりどれも、エスケープシーケンスの終端(あるいは区切り)が明確ではありません。一番最後の例の様な回避策はありますが分かりづらく、この問題をよく知らない人から見ると余計なことをしているようにしか見えません。

また、ユニバーサル文字名は16進数字4桁もしくは8桁のどちらかになりますが、ユニコードのコードポイントの範囲が[0, 0x10FFFF]に制限されているため、有効なコードポイントは5桁以下の16進数字列によって書くことができます。そして、5桁のユニコード文字を書く場合は\U0001F1F8のように冗長な0が必要になってしまいます。

この提案ではこれらの問題の解決のために、\u{}, \o{}, \x{}の形の新しいエスケープシーケンス構文を提案しています。それぞれ{}の中に任意の長さの数字列(8 or 16進)を書けること以外は既存の対応するエスケープシーケンスと同様の扱いとなります。そして、この形式のエスケープシーケンスは現在コンパイルエラーとなる(\oの形式は予約されている)ために既存のコードを壊すこともありません。

なお、エスケープシーケンスの置換は文字列リテラルの連結の前に行われるため、"\x{4" "2}"のような形での使用はコンパイルエラーとすることを提案しています。

この提案の後では、先程の問題のエスケープシーケンスを次のように書くことができるようになります。

"\o{18}";     // 8進エスケープシーケンスに8は現れてはいけないのでコンパイルエラー
"\x{ab}c";    // 2文字
"\u{1F1F8}";  // 5桁のユニバーサル文字名 

この提案はC++に対してのものですがCに対しても同じ問題がある事から、WG14(C標準化委員会)に周知のためにSG22にも提出されています。

この部分の9割は以下の方のご指摘により成り立っています。

P2291R0 Add Constexpr Modifiers to Functions to_chars and from_chars for Integral Types in Header

std::to_chars, std::from_charsを整数変換に関してconstexprにする提案。

現在のC++にはコンパイル時に使用可能な文字列と数値の相互変換のためのものはありません。そのため、コンパイル時に文字列変換を行うには在野のライブラリに頼るほかありませんでした。

標準ライブラリには様々な実行時文字列変換法があり、中でもstd::to_chars, std::from_charsロケールに依存せず、例外を投げず、動的メモリ確保も行わないなど、コンパイル時の文字列と数値の相互変換に最適な候補です。そのため、この2つの関数を整数変換に限ってconstexpr対応することを目指す提案です。

constexpr std::optional<int> to_int(std::string_view s) {
  int value;
  
  if (auto [p, err] = std::from_chars(s.begin(), s.end(), value); err == std::errc{}) {
    return value;
  } else {
    return std::nullopt;
  }
}

static_assert(to_int("42") == 42);
static_assert(to_int("foo") == std::nullopt);

C++20でstd::stringコンパイル時に構築できるようになったため、この提案と合わせるとstd::formatcosntexprにすることができるようになります。

筆者の方は、MSVC STLの実装をconstexpr対応させることで実装テストを行い、その結果大きな変更や問題がなかったことを確認しています。

浮動小数点数変換のconstexpr対応を提案していないのは、実装が難しいためにサポートが進んでおらず(MSVCとGCC11が実装完了)、提案するのは時期尚早という判断のようです。

P2295R0 Correct UTF-8 handling during phase 1 of translation

C++コンパイラが少なくともUTF-8をサポートするようにする提案。

現在のC++コンパイラが入力ソースファイルのエンコードとしてサポートしている文字コード(ソースファイル文字集合)は実装定義であり、必ずしもUTF-8をサポートしていません。それによって、UTF=8ソースファイルで書かれたmain関数だけのシンプルなプログラムですら、移植性を保証できません。

C++コンパイラがどのような文字コードを入力としてサポートするにせよUTF-8はサポートすることを義務付けることで、ソースコード移植性の問題を改善し、またユニコード関連の機能やその処理方法を(規格中でも)適切に指定し、幅広く使用できるようにしようとする提案です。

現在の主要な実装は全て、細部の差異はあれどUTF-8をサポートしているため、この提案の実装に必要なのはそれをデフォルトにすることだけです。この提案は、ユーザーのUTF-8ソースコードが適切にでコードされていることを保証する一方で、実装者はより寛大なオプションを提供できるようにするものです。例えば、UTF-8ソースファイルであることをどう決定するか(BOMの考慮)や、UTF-8として無効なコードポイントのシーケンスをどう扱うかなどは実装定義となります。

P2297R0 Wording improvements for encodings and character sets

規格の文章の~ encoding~ character setという言葉の使い方を改善し、何が何を指しているのかを一貫させる提案。

これによって、ユニバーサル文字名の扱いに関する3件のIssueが解決されます。

P2301R0 Add a pmr alias for std::stacktrace

std::basic_stacktracestd::pmrエイリアスstd::polymorphic_allocatorを用いるエイリアス)を追加する提案。

std::basic_stacktraceはテンプレートパラメータにアロケータ型を取り、std::allocatorがあてがわれたエイリアスstd::stacktraceとして定義されています。そして、std::stacktraceAllocatorAwareContainerの要件を満たしており、std::polymorphic_allocatorを用いることに障害はありません。

他のコンテナなど、アロケータモデルをサポートしデフォルトでstd::allocatorを使用するものについてstd::pmrエイリアスが用意されているように、std::basic_stacktraceにもstd::pmr::stacktraceを用意すべき、という提案です。

P2302R0 Prefer std::ranges::contains over std::basic_string_view::contains

新しいアルゴリズムとしてstd::ranges::containsを追加して、std::basic_string_view/std::basic_string.contains()メンバ関数を削除する提案。

C++23に向けたP1679R3の採択によって、std::string/string_viewにはある文字列が含まれているかを判定するための.contains()メンバ関数を備えています。

筆者の方は、このような操作の必要性は同意するがこの操作はより一般の範囲(range)に適用可能なアルゴリズムであり、メンバ関数ではなく<algorithm>に配置される非メンバアルゴリズムとして追加すべき、と述べています。

それによって、ある範囲に別の範囲が含まれているかどうか、あるいは述語を満たす部分が含まれているかどうかを調べる形に一般化でき、また他のrangeアルゴリズムに従った形で幅広い型について利用できるようになります.

std::ranges::containsの使用イメージ。

// 文字の範囲と文字を受ける
if (std::ranges::contains(haystack, 'o')) {
  // meow
}

// 文字範囲のイテレータペアと文字を受ける
if (std::ranges::contains(haystack.begin(), haystack.end(), 'c')) {
  // purr
}

// 探索する範囲のイテレータペアと探索したい範囲のイテレータペアを受ける
if (std::ranges::contains(haystack.begin(), haystack.end(), long_needle.begin(), long_needle.end())) {
  // hiss
}

// 探索する範囲と探索したい範囲を受ける
if (std::ranges::contains(haystack, long_needle)) {
  // hiss again
}

// 探索する範囲と述語を受ける
if (std::ranges::contains(haystack, long_needle, bind_back(std::modulo(), 4))) {
  // double purr
}

P2314R0 Character sets and encodings

規格文書中の ~ character setという言葉を明確に定義し直す提案。

そしてこれらの用語を用いて文言を書き直すことで、翻訳フェーズ1でユニバーサル文字名が形成されなくなり、全てのユニコード文字入力はコンパイル全体で保持されるようになります。それにより、プリプロセッサにおける文字列化演算子#)の動作が変更されます。

C++20 この提案
#define S(x) # x
const char * s1 = S(Kテカppe);      // "K\\u00f6ppe"
const char * s2 = S(K\u00f6ppe); // "K\\u00f6ppe"
#define S(x) # x
const char * s1 = S(Kテカppe);     // "Kテカppe"
const char * s2 = S(K\u00f6ppe); // "Kテカppe"

ただし、既存の実装は全てこれを実装している(エスケープされたユニバーサル文字名を返す実装はない)ために問題にはならないようです。

また、これらの変更によって現在使用されているbasic / extended source character setという言葉は使われなくなります。

P2315R0 C++ Standard Library Issues to be moved in Virtual Plenary, Feb. 2021

標準ライブラリのIsuueのうち2021年02月のオンライン全体会議の際に投票にかけられるもののリスト。

ここにあるものは投票でコンセンサスが得られればLWG Isuueとして規格に反映されることになります。

これを書いている時点で投票は完了しており、その結果ここに記載されているIssueは全て標準に適用されることになりました。

P2316R0 Consistent character literal encoding

#ifプリプロセッシングディレクティブの条件式において、文字リテラルC++の式の意味論と同等に扱えるようにする提案。

#if 'A' == '\x41'
//...
#endif

if ('A' == 0x41){}

現在の仕様では、この2つの条件式は同じ結果となる保証がありません。

#ifの条件式において文字リテラルは対応する数値に変換され処理されますが、文字リテラルをどのように解釈するか(どのエンコーディングで読み取るか)は実装定義であり、C++の式上でのそれと一致するかどうかも実装定義とされます。

筆者の方がvcpkgを用いてコードベースを調査したところ、このような#ifでの文字リテラルの比較はその環境でのナロー文字エンコーディングを取得するために使用されているようです。

sqliteより

#if 'A' == '\301'
# define SQLITE_EBCDIC 1
#else
# define SQLITE_ASCII 1
#endif

主要なコンパイラは全て期待通りに動作し、#ifでの文字リテラルをナロー文字エンコーディングにおいて対応する数値として扱うようです。

C++で文字エンコーディングを検出するより良い方法は提案中ですが現状では手段がなく、この振る舞いを標準化して上記の様なコードがユーザーの期待通りに振舞うことを保証しようとする提案です。

P2317R0 C++ - An Invisible foundation of everything

C++とは何か?(原則と理想や使用についてなど)というよく聞かれる質問に対する返答をまとめた文書。

Bjarne Stroustrup先生がC++とは何か?とかC++はまだどこでも使われてるの?などよく聞かれるらしく、その簡単な答えとその詳細を知ることのできる文書へのリンクをまとめた文書です。WG21メンバーに向けて書かれたものではないですが、WG21メンバにも役立つ可能性があるためWG21に提出された様です。

目次

  1. 目的と手段
    • C++の設計の高レベルでの目的とシステムにおけるその役割
  2. 使用
    • 基礎的な使用法に焦点を当てた、C++のいくつかの用例
  3. 進化
    • フィードバックに基づくC++開発のための進化戦略
  4. 保証、言語、ガイドライン
    • 進化、安定性、表現力、型・リソース安全を同時に達成するための戦略
    • ソフトウェア開発における人の役割の再認識
  5. 参考文献とリソース
    • C++のより深い理解につながる可能性のある参考文献への注釈付きリスト
  6. 付録
    • C++の主要なプロパティと機能の非常に簡単な概説

C++ヲタク必見です!

P2320R0 The Syntax of Static Reflection

静的リフレクションのための新しい構文の提案。

表現力・読みやすさ・柔軟さ・曖昧さがない・実装可能、の観点から提案中のものも含めた現在のリフレクションの構文を見直したものの提案です。

リフレクション

現在のリフレクションTSでは、名前からメタ情報を取得するのにreflexpr()という構文を使用しています。この提案では代わりに^を使用します。

// この提案
meta::info r1 = ˆint;   // reflects the type-id int
meta::info r2 = ˆx;     // reflects the id-expression x
meta::info r3 = ˆf(x);  // reflects the call f(x)

// N4856現在
meta::info r1 = reflexpr(int);   // reflects the type-id int
meta::info r2 = reflexpr(x);     // reflects the id-expression x
meta::info r3 = reflexpr(f(x));  // reflects the call f(x)

スプライシング

リフレクションによって取得したメタ情報から型名を取り出して利用することです。この提案では[: R :]の様な構文によって行います。

struct S { struct Inner { }; };
template<int N> struct X;
auto refl = ˆS;
auto tmpl = ˆX;

void f() {
  typename [:refl:] * x;  // OK: xはS*
  [:refl:] * x;           // error: typename必須
  [:refl:]::Inner i;      // OK
  typename [:refl:]{};    // OK: Sの一時オブジェクトをデフォルト構築
  using T = [:refl:];     // OK
  struct C : [:refl:] {}; // OK
  template [:tmpl:]<0>;   // OK
  [:tmpl:] < 0 > x;       // error: xと0の比較になる

  // N4856現在
  namespace reflect = std::experimental::reflect;
  using refl_t = reflexpr(S);
  using tmpl_t = reflexpr(X);

  reflect::get_reflected_type_t<refl_t> * x;
  typename reflect::get_reflected_type_t<refl_t>::Inner i;
  reflect::get_reflected_type_t<refl_t>{};
  using T = reflect::get_reflected_type_t<refl_t>;
  struct C : reflect::get_reflected_type_t<refl_t> {};
  // 残りの2例は対応するものがない(おそらく)
}

パックのスプライシング

リフレクションによって取得したメタ情報から型名のリストを取り出して利用することです。この提案では...[: R :]...の様な構文によって行います。

// 型エイリアス
using T = std::tuple<int, ...[:range_of_types:]..., bool>;
// 関数宣言
void f(... [:range_of_types:] ...args);

// 関数呼び出し
fn(0, 1, ...[:range:]...);  // OK: 通常引数(0, 1)の後に展開
fn(...[:range:]..., 0, 1);  // OK: 通常引数(0, 1)の前に展開
fn(...[:range:] * 2...);    // OK: rangeの要素に2をかけながら展開
fn(...[:r1:] * [:r2:]...);  // OK: ただし、r1とr2の長さは同じでなければならない

P2321R0 zip

<ranges>zip_view, adjacent_view, zip_transform_view, adjacent_transform_viewを追加する提案。

これら4つのziplikeなviewは基本的には複数の範囲を一つの範囲に変換する様に振る舞うものです。そのため、元のそれぞれの範囲の要素型(value_type)をEnとすると、間接参照の結果型(reference)はstd::pair<E1&, E2&>std::tuple<E1&, ..., En&>の様になるでしょう。

このstd::pairstd::tupleイテレータの間接参照の結果として返されるプロクシオブジェクトとして使用されるのに必要な性質を備えておらず、その議論が間に合わなかったためにC++20ではこれらのviewは採択されませんでした。

この提案は、std::pairstd::tupleの各種の問題を解決した上で、zip_view, adjacent_view, zip_transform_view, adjacent_transform_viewを追加するものです。

std:tupleへの出力

std::indirectly_writableコンセプトは、const_castを用いた制約式によって、イテレータの間接参照がprvalueを返すようなイテレータを弾く一方で、それが単にプロクシ参照である場合は許容する、という事をしています。これはstd::output_iteratorコンセプトを構成するものの一つです。

ziplikeなviewイテレータはその間接参照の結果としてstd::pair/tupleprvalueを返します。output_iteratorとして有効であるためには、それをconst化したもの(例えば、const std::tuple<...>&&なオブジェクト)に対して代入できなければなりません。

当然ながら、現在のstd::pair/tupleはそうなっておらず、プロクシイテレータの間接参照の結果型としては不足しています。

この提案では、std::pair/tupleに代入演算子constオーバーロードを追加し、std::pair/tupleconstでもその要素に代入が可能であるならばできるように変更します。

同時に、std::vector<bool>::referenceにも同様の理由から同じように代入演算子constオーバーロードを追加します。

std:tupleの読み取り

std::indirectly_readableコンセプトは、イテレータ型のvalue_type&referenceの間にcommon_referenceが存在していることを求めています。これはstd::input_iteratorコンセプトを構成するものの一つです。

ziplikeなviewイテレータにおけるvalue_type&referenceの間のcommon_referenceとは何でしょうか?

std::vector<int> vi = /* ... */;
std::vector<std::string> vs = /* ... */;

ranges::sort(views::zip(vi, vs));

例えばこの場合のzip_viewイテレータvalue_typestd::pair<int, std::string>referencestd::pair<int&, std::string&>となります。

std::pair<int&, std::string&>std::pair<int, std::string>へは変換できるため、common_referencestd::pair<int, std::string>となるでしょうか。参照ではありませんが、common_referenceに求められることは果たします。

しかし、zip対象のイテレータの要素型がコピー不可能な型になった場合、value_typereferenceはどちらをどちらにも変換できなくなるため、common_referenceは機能しなくなります。それによって、input_iteratorのモデルとなれなくなります。

この事はstd::tupleにも同じことが言えます。

この問題の解決のために、std::pair<T1, T2>に対してstd::pair<U1, U2>&, const std::pair<U1, U2>&&から構築するコンストラクタ、およびstd::tuple<Ts...>に対してstd::tuple<Us...>&, const std::pair<Us...>&&から構築するコンストラクタを追加します。

この2つのコンストラクタを追加するだけで、std::pair<std::unique_ptr<int>, std::string>からstd::pair<std::unique_ptr<int>&, std::string&>が構築できるようになります。そして、この型は参照のセマンティクスを持っています(std::tupleでも同様)。

またcommon_referenceに適合するために、std::tuplestd::pairに対してstd::basic_common_referencestd::common_typeの特殊化を追加します

これは、common_referenceとしてよりふさわしい型となります。

zip, zip_tranformvalue_type

zip_transformは与えられた範囲をどのように一つの範囲にzipするかを指定することのできるviewです。zipはデフォルトでstd::tuple/pairを用いてまとめ、その振る舞いを変更できません。対して、zip_transformはその部分を任意の関数を指定することでカスタマイズできます。

ここで問題になるのが、zip_transformvalue_typeがどうなるかという点です。

std::vector<int> vi = /* ... */;
std::vector<std::string> vs = /* ... */;

auto b = views::zip_transform([](auto&... r){
    return std::tie(r...);
  }, vi, vs);

このzip_transformzipと同じことをします。breferencestd::tuple<int&, std::string&>となり、これはzipのふるまいと一貫しています。しかし、この場合のvalue_typestd::tuple<int, std::string>としてしまう事は適切でしょうか?

zipの行う事と異なり、この場合にtupleの各要素が参照となっているのはユーザーが指定した変換の結果であり、別の変換ではこれは参照とならないかもしれません。従って、zip_transformvalue_typeは単にstd::remove_cvref_t<std::invoke_result_t<F&, std::range_reference_t<R>...>>のような型とするのが適切です。上記の例ではstd::tuple<int&, std::string&>となります。ただ、そうしてしまうとzipとの振る舞いの一貫性がなくなってしまいます。

この提案ではこの方針を採用し、zipzip_transformreference/value_typeの間の一貫性を失う事を許容することにしたようです。

これらの様な設計を選択し、C++23への導入に向けてziplikeな4つのviewの作業が開始されます。

なお、adjacent_viewとは1つの範囲のある要素についてそれに続くN - 1要素をひとまとめにしたものを要素とする範囲を生成するviewで、入力の範囲に対してその範囲の1つ目の要素を飛ばした範囲を生成して、その二つの範囲をzipしたようなviewです。adjacent_transform_viewadjacentする部分をカスタムできるものです。

std::vector<int> vi = {1, 2, 3, 4};
std::vector<std::string> vs = { "one", "two", "three", "four" };

for (auto& [n, s] : vi | std::views::zip(vs)) {
  std::cout << n << " : " << s << std::endl;
}
// 1 : one
// 2 : two
// 3 : three
// 4 : four

for (auto [n, m] : vi | std::views::adjacent<2>) {
  std::cout << n << " : " << m << std::endl;
}
// 1 : 2
// 2 : 3
// 3 : 4

P2322R0 ranges::fold

rangeアルゴリズムであるranges::foldの提案。

このranges::foldというのは関数型言語foldlと呼ばれているものに対応し、現在のC++標準ライブラリには数値に特化したイテレータ版として<numeric>std::accumulateとして存在しています。

std::ranges::accumlateP1813R0で提案されており、検討中のstd::ranges::accumlateは以下のようになっています。

template <input_range R, movable T, class Proj = identity,
          indirect_magma<const T*, projected<iterator_t<R>, Proj>, T*> BOp = ranges::plus>
constexpr accumulate_result<safe_iterator_t<R>, T>
    accumulate(R&& r, T init, BOp bop = {}, Proj proj = {});

これを良しとしないのは、特定の2項演算をデフォルトにするべきではない、戻り値型が複雑(単に結果だけが欲しい)、制約がきつすぎる(indirect_magmaコンセプトは大きすぎる)、等の理由です。

この操作は数値に限定されたものではないためより一般的な名前を付けて、より汎用的であるために過度な制約を課さないようにしたものがranges::foldであり、もはや数値のためのものではないため<algorithm>に追加することを提案しています。

提案ではさらに、ranges::foldのファミリとして、foldrに対応するranges::fold_rightと、範囲の最初の要素を初項として使用するranges::foldであるranges::fold_first、最後の要素を初項として使用するranges::fold_rightであるranges::fold_right_lastを同時に提案しています。

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

int sum1 = std::ranges::fold(vec, 0, std::ranges::plus{});
int sum2 = std::ranges::fold_first(vec, std::ranges::plus{});
// sum1 == sum2 == 15

std::vector<std::string> vec2 = {"aaa", "bbb", "ccc"};
std::string concat1 = std::ranges::fold_first(vec2, std::ranges::plus{});
// concat1 == "aaabbbccc"

std::string concat2 = std::ranges::fold_right(vec2, std::string{}, std::ranges::plus{});
std::string concat3 = std::ranges::fold_right_last(vec2, std::ranges::plus{});
// concat2 == concat3 == "cccbbbaaa"

P2324R0 Labels at the end of compound statements (C compatibility)

複合ステートメントcompound statement)の末尾にラベルを置けるようにする提案。

Cではこれが可能になっていますがC++では可能になっておらず、Cとの互換性向上のためにできるようにしようという提案です。この提案はSG22に提出されたものです。

複合ステートメントとはブロック({...})のことで、その末尾とは例えば関数の末尾の事です。

void foo(void)
{
first:  // C/C++共にok
  int x;

second: // C/C++共にok
  x = 1;

last:   // Cはok、C++はng
}

この提案はこの例のlastの様なラベルを置けるようにするものです。

P2325R0 Views should not be required to be default constructible

Viewとみなされる型にデフォルト構築可能性を要求しない様にする提案。

Viewを定義しているstd::ranges::viewコンセプトは、現在次の様に定義されています。

template <class T>
concept view =
    range<T> &&
    movable<T> &&
    default_initializable<T> &&
    enable_view<T>;

viewrangeであって少なくともムーブ可能である必要があり、viewは構文的な部分よりも意味論の部分でrangeと大きく異なるため、構文的にviewであるかどうかは明示的なオプトイン(enable_view)が必要です。

ただ、残ったdefault_initializableに関しては、C++20の<ranges>に至る議論からはviewがデフォルト構築可能である事のモチベーションは見つからず、range-v3のものを含めても本質的にデフォルトコンストラクタを必要とするviewはむしろ少数であり、default_initializableの要求はviewには不要のものに思えます。

また、デフォルト構築を要求されていることによって、関数を受け取るタイプのviewrange adaptor)は受け取った関数オブジェクトをstd::optionalsemiregular-box)に格納する必要があるため、実装を複雑化しviewのサイズを増加させています。

さらに、型がデフォルト構築可能であるという性質を持つことによって、そのような型をメンバとして持つクラスには、そのメンバが初期化されていないという追加の状態が発生します。それによって、ユーザーはそれをチェックする必要があり、またドキュメントなどにはその旨を記載する必要が出てきます。これは、現在のviewに当てはまっていることです。

デフォルト構築可能という要求がメリットをもたらさずむしろ害(型に無効状態を追加し、全てのrange adaptorにそのサイズ増加をもたらし、これが原因でviewコンセプトを満たせない場合のユーザの混乱など)をもたらすのであれば、それは取り除くべき、という提案です。

この提案では、viewコンセプトからdefault_initializableを取り除くと共に、weakly_incrementableコンセプトからも取り除くことで、input iterator/output iteratorへのデフォルト構築可能という要求を取り除くことを提案しています。

そして、一部のviewおよびイテレータ型からデフォルトコンストラクタを削除し、std::spanが常にviewとなるようにします。

多分2週間後くらい

この記事のMarkdownソース