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

文書の一覧

全部で36本あります(SG22のWG14からのものは除きます)。

P0429R8 A Standard flat_map

キーの検索をstd::map比で高速に行える連想コンテナ、flat_mapの提案。

std::mapはノードベースの連想コンテナであり、個々の要素はノードと呼ばれる単位(key-valueペア及びアロケータ、親と子ノードへのポインタなどをまとめて格納しているもの)でメモリ上に存在しており、多くの場合はノード同士はメモリ上で連続せずに分散して存在しています。そのため、キャッシュ局所性が悪く、キーの検索(ノードの引き当て)のパフォーマンスが悪いことが問題となっていました。

flat_mapの基礎的なアイデアは、シーケンスコンテナ(std::vectorなどメモリ連続なコンテナ)上に二分木を構成して使用することで、各要素をメモリ上で連続させてキャッシュ局所性を向上させようとするものです。要は、更新時もソート済みであることが保証されるソート済std::vectorです。したがって、要素の更新はO(N)(ほぼ常に再配置が発生)、要素の検索はO(logN)(ソート済み配列上での二分探索)の計算量となります。

この提案のflat_mapではさらに、キーと値をそれぞれ別のシーケンスコンテナ(デフォルトはstd::vector)に保持することでキーのキャッシュ局所性をさらに向上させ、検索のパフォーマンス向上の最大化を図っています。そして、この提案のflat_mapはコンテナではなくコンテナアダプタとなり、イテレータはランダムアクセスイテレータ(ただし、C++17以前に対しては入力イテレータ)となります。

namespace std {
  // std::flat_mapの宣言例
  template <class Key, class T, class Compare = less<Key>,
            class KeyContainer = vector<Key>,
            class MappedContainer = vector<T>>
  class flat_map {

    ...

    struct containers {
      KeyContainer keys;
      MappedContainer values;
    };

    ...

  private:
    containers c; // exposition only
  };
}

また、flat_mapstd::mapとのインターフェース互換を意識しているため、ほぼ同じインターフェースによって使用可能となっています。

flat_mapはその構造上、要素の挿入(insert())や削除(remove())が遅く検索(find/operator[])及びイテレートが早い連想コンテナです。従って、適した用途は要素の更新よりも参照回数の方が多くなる場合であり、最大のパフォーマンスメリットを得るには最初に一度構築した後は検索しかしないような使い方をする必要があります。

また、flat_mapはノードベースコンテナではないため要素ごとにアロケータや親子ポインタなどを保持する必要がなく、std::mapに比べて要素あたりの空間コストを削減することができます。そのため、パフォーマンスメリットが得られない場合でも省メモリ目的で使用することもできます。

flat_mapC++20に向けて議論されていましたが、キーと値のコンテナを別々に持つことからそのイテレータのためにzip_viewが必要とされ、zip_viewはその値型(std::pair<T&, U&>/std::tuple<Ts&...>)のcommon_referenceの問題(std::pair<T&, U&><->std::pair<T, U>のような変換ができない)やswapの問題(参照pair/tupleconst-assignableにすると正しくswapされない)の解決のためにC++23に延期されたたため、flat_mapもそれを待たねばなりませんでした。今のところ、C++23に向けてレビューされています(現在LWGでレビュー中)。

P0957R7 Proxy: A Polymorphic Programming Library

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

以前の記事を参照

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

  • proxy::type(), proxy::cast()proxy::reflect()で置き換えた(静的リフレクションを意識して?)
  • bad_proxy_castを削除
  • proxy::operator=の例外指定を変更
  • BasicFacade(名前付き要件)の指定を変更

などです。

P1061R2 Structured Bindings can introduce a Pack

構造化束縛可能なオブジェクトをパラメータパックに変換可能にする提案。

std::tupleとパラメータパックは任意の異なるオブジェクトのシーケンスという点でよく似ています。現在、パラメータパックからstd::tupleへ変換することは簡単にできますが、その逆(std::tuple->パラメータパック)は少し面倒です。

template<typename... Ts>
void pack_to_tuple(Ts&&... ts) {
  std::tuple<Ts...> t(std::forward<Ts>(ts)...);
}

template<typename... Ts>
void tuple_to_pack(std::tuple<Ts...> t) {
  std::apply([](auto&&... elems) {
    // ここでtupleから変換したパックが得られる
  }, t);
}

これは単一のパックの単純な使用においてはそこまで複雑ではありませんが、特定のオーバーロードを解決したいとか、それによって戻り値を返したいとか、複数のtupleを扱いたいなどしてくると、急速に複雑化します。たとえば、2つのtuple内積を求めるようなコードの場合

// std::applyを使用した例
template <class P, class Q>
auto dot_product_apply(P p, Q q) {
    return std::apply([&](auto... p_elems){
        return std::apply([&](auto... q_elems){
            return (... + (p_elems * q_elems));
        }, q)
    }, p);
}

// std::index_sequenceを使用した例
template <size_t... Is, class P, class Q>
auto dot_product_idxseq(std::index_sequence<Is...>, P p, Q, q) {
    return (... + (std::get<Is>(p) * std::get<Is>(q)));
}

template <class P, class Q>
auto dot_product_idxseq(P p, Q q) {
    return dot_product_idxseq(
        std::make_index_sequence<std::tuple_size<P>::value>{},
        p, q);
}

どちらもコード自体の短さとは裏腹に、恐ろしいほどの複雑さが詰め込まれています(慣れてるとそう見えなくなってしまうのですが・・・)。そして、このコードはstd::tuple(あるいは互換インターフェースを備えた型)のみに制限されています。構造化束縛宣言で使用可能なタプルっぽく思える型をここに入れることはできません。

この提案はこれらの複雑さと非一貫性を取り払うために、構造化束縛宣言を拡張してパック導入ができるようにしようとするものです。これによって、std::tupleからパラメータパックへの変換が簡単になるとともに、それをタプルlikeな任意の型へと拡張することができます。

std::tuple<X, Y, Z> f();

auto [x,y,z] = f();          // OK today
auto [...xs] = f();          // proposed: xsは長さ3のパック、X,Y,Zのオブジェクトを含んでいる
auto [x, ...rest] = f();     // proposed: xはXのオブジェクト、restは長さ2のパック(Y,Z)
auto [x,y,z, ...rest] = f(); // proposed: restは空のパック
auto [x, ...rest, z] = f();  // proposed: xはXのオブジェクト、restは長さ1のパック(Y)、zはZのオブジェクト
auto [...a, ...b] = f();     // ill-formed: 複数パックへの展開は決定不可能

サンプルコード

std::applyの実装

現在 この提案
namespace detail {
    template <class F, class Tuple, std::size_t... I>
    constexpr decltype(auto) apply_impl(F &&f, Tuple &&t,
        std::index_sequence<I...>)
    {
        return std::invoke(std::forward<F>(f),
            std::get<I>(std::forward<Tuple>(t))...);
    }
}

template <class F, class Tuple>
constexpr decltype(auto) apply(F &&f, Tuple &&t)
{
    return detail::apply_impl(
        std::forward<F>(f), std::forward<Tuple>(t),
        std::make_index_sequence<std::tuple_size_v<
            std::decay_t<Tuple>>>{});
}
template <class F, class Tuple>
constexpr decltype(auto) apply(F &&f, Tuple &&t)
{
    auto&& [...elems] = t;
    return std::invoke(std::forward<F>(f),
        forward_like<Tuple, decltype(elems)>(elems)...);
}

std::applyを使用した2つのタプルの内積

現在 この提案
template <class P, class Q>
auto dot_product(P p, Q q) {
    return std::apply([&](auto... p_elems){
        return std::apply([&](auto... q_elems){
            return (... + (p_elems * q_elems));
        }, q)
    }, p);
}
template <class P, class Q>
auto dot_product(P p, Q q) {
    // no indirection!
    auto&& [...p_elems] = p;
    auto&& [...q_elems] = q;
    return (... + (p_elems * q_elems));
}

std::index_sequenceを使用した2つのタプルの内積std::index_sequenceにタプルインターフェースを追加したとする)

現在 この提案
template <size_t... Is, class P, class Q>
auto dot_product(std::index_sequence<Is...>, P p, Q, q) {
    return (... + (std::get<Is>(p) * std::get<Is>(q)));
}

template <class P, class Q>
auto dot_product(P p, Q q) {
    return dot_product(
        std::make_index_sequence<std::tuple_size_v<P>>{},
        p, q);
}
template <class P, class Q>
auto dot_product(P p, Q q) {
    // no helper function necessary!
    auto [...Is] = std::make_index_sequence<
        std::tuple_size_v<P>>{};
    return (... + (std::get<Is>(p) * std::get<Is>(q)));
}

この拡張による実装は簡潔であるだけでなく、これらのタプルに限定されていたコードを構造化束縛で利用可能な型に拡張します。たとえば、上記のstd::apply実装ならば、ユーザー定義型で利用可能となります。

struct Point {
    int x, y, z;
};

Point getPoint();
double calc(int, int, int);

double result = std::apply(calc, getPoint()); // 現在はng、この提案による実装ではok

ただし、これを実装すると。非テンプレートコンテキストにおいてもパラメータパックの出現を考慮しなければならなくなるため、実装の複雑さと通常コードへのコンパイル時間の増大等の影響が予想されます。

// 非テンプレートでのパック導入
auto sum_non_template(SomeConreteType tuple) {
    auto [...elems] = tuple;
    return (... + elems);
}

この提案では、この提案以外の提案(主にリフレクション関係)によって任意の場所にパックを導入するものがいくつかあるということと、この提案によるパックは構造化束縛宣言によってのみ導入され、展開される前に必ず宣言されていることから任意の場所で突然パック展開が出現することはない(ためにすべてのコンテキストでパック展開出現を考慮する必要が無い)、等の事から実装と他コードへの影響は大きくないとしています。

P1169R4 static operator()

関数呼び出し演算子operator())を、静的メンバ関数として定義できるようにする提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言の調整のみのようです。

この提案はCWGのレビューを終えていますが、ライブラリ部分についてLEWGでの投票待ちをしています。

P1222R3 A Standard flat_set

キーの検索がstd::set比で高速に行える連想コンテナ、flat_setの提案。

この提案のモチベーションやメリット、及び設計はほとんど先ほどのflat_mapと共通しています。flat_setはほぼ、常にソート済みであることが保証されるソート済みvectorであり、要素はメモリ上で連続して配置されています。それによって、検索やイテレート時のキャッシュ局所性が向上しそれらの操作を高速に行うことができ、ノードベースではないことから空間コストも削減することができます。

flat_mapの実装がキーと値のコンテナを別々に持つなど複雑だったのに対して、flat_setの場合はキーのコンテナ1本のラッパとなるのでかなり単純になります。flat_setflat_map同様にコンテナアダプタであり、そのイテレータはプロクシイテレータかつC++20イテレータとしてはランダムアクセスイテレータとなります(C++17イテレータとしては入力イテレータ)。

namespace std {

  // flat_setの宣言例
  template<class Key, class Compare = less<Key>, class Container = vector<Key>>
  class flat_set {
    
  private:
    container_type c; // exposition only
  };

}

この提案はおそらくflat_mapと足並みを揃えるために遅れており、現在はC++23に向けてLWGでレビュー中です。

P1223R4 find_last

指定された値をシーケンスの後ろから探索するfind_lastアルゴリズムの提案。

このリビジョンの変更はfind_lastファミリの戻り値としてsubrangeを返すようにしたことと、それに伴う提案全体の書き直しなどです。

以前の提案のfind_lastの戻り値は見つけた位置を指すイテレータのみでしたが、この提案では範囲の終端を指すイテレータを含めたsubrangeを返すように変更されました。これはLWGのレビューによるもので、そのAPIの変更の確認のためにSG9およびLEWGでレビューと投票を行っており、現在LEWGでの投票待ちです。

P1467R9 Extended floating-point types and standard names

C++コア言語/標準ライブラリに拡張浮動小数点型のサポートを追加する提案。

以前の記事を参照

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

この提案は、このリビジョンでもってCWG/LWGのレビューを終え、C++23に向けて次の全体会議で投票にかけられる予定です。

P1642R8 Freestanding Library: Easy [utilities], [ranges], and [iterators]

[utility]<ranges><iterator>から一部のものをフリースタンディングライブラリに追加する提案。

前回の記事を参照

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

  • move_only_functionout_ptrを含めないようにした
  • invoke_r, zip, zip_transform, adjacent, adjacent_transform, to_underlying, unreachable, views::chunk_by, views::chunk, views::slide, views::join_with, ranges::to及びP2387をフリースタンディングライブラリ機能として含めるようにした

などです。

この提案はC++23を目指してLWGのレビュー待ちをしています。

P1673R7 A free function linear algebra interface based on the BLAS

標準ライブラリに、BLASをベースとした密行列のための線形代数ライブラリを追加する提案。

以前の記事を参照

このリビジョンでの変更は、タイポの修正と提案する文言の調整などです。

P1674R1 Evolving a Standard C++ Linear Algebra Library from the BLAS

C++標準ライブラリに提案する線形代数ライブラリの設計に関して記述した文書。

これはP1673(1つ上)の設計について記述した文書でもあります。

BLASをベースに、それをC++のインターフェスによってラップし、C++のイディオムやコアガイドラインに沿うように抽象化していくとともに、その際に生じた問題やその解決について述べられています。

P1684R2 mdarray: An Owning Multidimensional Array Analog of mdspan

多次元配列クラスmdarrayの提案。

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

  • 全体的なtypoや指定漏れの修正
  • rangeコンストラクタの追加
  • デフォルトのコンテナ型をstd::vectorとした
  • 内部コンテナへのアクセス関数を削除(勝手にresize()等ができてしまうため)
  • mdspanからの変換コンストラクタを追加
  • mdarrayの領域へのmdspanを返す.view()メンバ関数の追加

などです。

P1967R5 #embed - a simple, scannable preprocessor-based resource acquisition method

コンパイル時(プリプロセス時)にバイナリデータをインクルードするためのプリプロセッシングディレクティブ#embedの提案。

以前の記事を参照

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

  • 実装者などからのフィードバックにより、構文の変更
  • 提案する文言の改善
  • WG14への提案とWG21への提案を分離した
  • __has_embedの変更
    • 空のリソースに対しては1ではなく2を返す
  • limitパラメータの引数について、最低でも1回はマクロ展開が行われるようにした

などです。

このリビジョンでは、emptyパラメータがis_emptyに変更されています。

// empty引数
// リソースが空の場合に指定されたpp-tokenのリストを展開する
constexpr const char x[] = {
#embed "empty_file.dat" \
    is_empty((char)-1)
};
// sizeof(x) == 1
// x[0] == -1 or 255

__has_embed<header-name>header-nameに指定されたリソースが使用可能であるかを問い合わせるもので、__has_includeに対応するものです。その結果は

  • 0 : リソースが見つからない、もしくは、指定された追加のパラメータが利用可能でない場合
  • 1 : リソースが存在し空ではなく、追加のパラメータが利用可能な場合
  • 2 : リソースが存在し空であり、追加のパラメータが利用可能な場合

となります。

また、これにより#embed#__has_embedの間でTOCTOU問題が発生しますが、同じことは#include__has_includeの間でも発生しており、それは現在コンパイラによって回避されています(一度読んだファイルをキャッシュすることで回避されている)。#embedも既存コンパイラのそうした実装に乗っかることで問題を回避できます。

P2071R2 Named universal character escapes

ユニバーサル文字名として、16進エスケープシーケンスの代わりにユニコードの規定する文字名を使用できるようにする提案。

以前の記事を参照

このリビジョンでの変更は、CWGからのフィードバックに基づく提案する文言の改善です。

この提案は既にCWGのレビューを終えており、C++23に向けて次の全体会議で投票にかけられる予定です。

P2093R14 Formatted output

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

以前の記事を参照

このリビジョンでの変更は、提案する文言の修正や改善です。

この提案はLWGでのレビューを終え、C++23に向けて次の全体会議で投票にかけられる予定です。

P2174R1 Compound Literals

C99から存在している複合リテラルcompound literal)をC++でもサポートする提案。

このリビジョンでの変更は、複合リテラルの生成するオブジェクトの値カテゴリを調整した事です。

以前の提案では複合リテラル(式)の値カテゴリはprvalueとすることを提案していました。この提案では複合リテラルで使用可能な型をtrivially destructibleな型のみに制限したうえで、複合リテラルの結果はそのスコープに導入される新しい変数を参照するlvalueとなるようになりました。

これによって、C++でも複合リテラルによって生成した匿名のバッファを安全に使用できるようになるなど、C言語での複合リテラルとほぼ同等に使用できるようになり、C言語への後方互換性が向上します。

char *ptr = strcat((char [100]){0}, "like this"); // ok、安全

P2198R5 Freestanding Feature-Test Macros and Implementation-Defined Extensions

フリースタンディング処理系でも使用可能なライブラリ機能について、機能テストマクロを追加する提案。

以前の記事を参照

このリビジョンでの変更は、__cpp_lib_bind_back, __cpp_lib_ranges_chunk, __cpp_lib_ranges_chunk_by, __cpp_lib_ranges_join_with, __cpp_lib_ranges_slide, __cpp_lib_ranges_to_container, __cpp_lib_reference_from_temporary, __cpp_lib_unreachableをフリースタンディングとして追加したことなどです。

この提案は再びC++23に向けて作業されており、現在はLEWGでの最終投票をパスしています。

P2266R3 Simpler implicit move

return文における暗黙のムーブを改善する提案。

以前の記事を参照

このリビジョンでの変更は、この提案の内容に関してAnnexCに追記した事などです。

この提案の採択によって、ローカル右辺値参照をそのまま返そうとするときの型が変更されます。

decltype(auto) f(int&& x) { return (x); }  // 戻り値型はint&&になる、以前はint&
int& g(int&& x) { return x; }  // ill-formedになる、以前はwell-formed

この提案CWGでのレビューを終え、次の全体会議で投票にかけられる予定です。

P2278R3 cbegin should always return a constant iterator

std::ranges::cbegin/cendを拡張して、常にconst_iteratorを返すようにする提案。

以前の記事を参照

このリビジョンでの変更は、views::all_constviews::as_constに名前を戻したことなどです。

この提案はLWGでのレビュー中です。

P2280R4 Using unknown references in constant expressions

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

以前の記事を参照

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

この提案は既にCWGでのレビューを終えており、次の全体会議で投票にかけられる予定です。

P2286R7 Formatting Ranges

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

以前の記事を参照

このリビジョンでの変更は、提案する文言の調整のみのようです。

P2300R5 std::execution

P0443R14のExecutor提案を置き換える、任意の実行コンテキストで任意の非同期処理を構成・実行するためのフレームワークおよび非同期処理モデルの提案。

以前の記事を参照

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

  • start_detachedは引数にvoidsenderを要求するようにした
  • recieverコンセプトはエラーチャネルにexception_ptrを使用しないようになった
  • sender_ofコンセプトとconnectカスタマイゼーションポイントでは、receiverがすべての完了を受信できる必要があることが要求されるようになった
  • get_completion_signaturescompletion_signaturesdependent_completion_signaturesのどちらかを返すようにした
  • make_completion_signaturesをよりジェネリックにした
  • receiver_adaptorは派生クラスの.get_env()メンバ関数を考慮するようになった
  • just, just_error, just_stopped, into_variantはカスタマイゼーションポイントととして再指定された

などです。

この提案は現在LWGに転送するためのLEWGでの最終投票待ちをしています。

P2302R4 std::ranges::contains

新しいアルゴリズムとしてstd::ranges::containsを追加する提案。

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

この提案はLWGのレビューを終えて、C++23に向けて次の全体会議で投票にかけられる予定です。

P2322R6 ranges::fold

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

以前の記事を参照

このリビジョンでの変更は、提案する文言の修正です。

この提案はLWGでのレビューを終えて、次の全体会議で投票にかけられる予定です。

P2400R3 Library Evolution Report: 2021-09-28 to 2022-01-25

2021年9月から2022年1月にかけての、LEWGでのミーティングについてのまとめ。

どれくらいミーティングを行ったのか、おおまかな機能単位についての進行状況、レビューを行った提案についての議論の状況などが記載されています。

P2408R5 Ranges iterators as inputs to non-Ranges algorithms

非Rangeアルゴリズムイテレータに対する名前付き要件を、イテレータコンセプトで置き換える提案。

以前の記事を参照

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

この提案はLWGでのレビューを終えて、次の全体会議で投票にかけられる予定です。

P2472R2 make function_ref more functional

function_refに適応的に型消去させるためのヘルパ関数make_function_ref()を追加する提案。

以前の記事を参照

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

  • 提案する文言の追加
  • function_refに、参照の代わりにポインタを受け取る3つ目のコンストラクタを追加
  • function_refの推論補助を追加

などです。

この提案はC++23に向けて作業されることになったようです。

P2505R2 Monadic Functions for std::expected

std::optionalのMonadic interfaceをstd::expectedにも導入する提案。

以前の記事を参照

このリビジョンでの変更は、CGG(libstdc++)での実装と例へのリンク追加と提案する文言の修正などです。

この提案はバグフィックスであるとしてC++23をターゲットにすることになったようです。

P2510R2 Formatting pointers

std::formatについて、ポインタ型のフォーマットを充実させる提案。

以前の記事を参照

このリビジョンでの変更は、例(比較表)におけるフォーマットエラーを修正したこと、参照実装へのリンクを追加した事です。

P2538R1 ADL-proof std::projected

C++20 Rangeアルゴリズムが不必要な型の完全性要求をしないようにする提案。

このリビジョンでの変更は、コンパイルエラーの例を追記したことなどです。

この提案はC++23に向けて、LEWGの最終投票待ちをしています。

P2539R1 Should the output of std::print to a terminal be synchronized with the underlying stream?

提案中のstd::printP2093)が出力するストリームについて、同じストリームに対する他の出力との同期を取るようにする提案。

このリビジョンでの変更は、例を追加したこと、提案する文言を追加した事などです。

P2542R1 views::concat

同じ要素型を持つ異なる型の範囲を連結するRangeファクトリ、views::concatの提案。

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

  • 元の範囲が!common_range && random_access_range && sized_rangeであるときにconcat_viewcommon_rangeとするのをやめた
  • concat可能な型を示す説明専用のコンセプトconcatableを追加
    • 主に入力範囲の値型の互換性に関する要求をまとめたもの

などです。

P2546R1 Debugging Support

標準ライブラリにデバッグサポートの為のユーティリティを追加する提案。

以前の記事を参照

このリビジョンでの変更は、std::breakpoint()の何もしない可能性がある実装についてのコメントを削除、SG15における投票結果を追記。

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

P2559R0 Plan for Concurrency Technical Specification Version 2

Concurrency TS v2発効に向けた作業計画書。

現在のConcurrency TS v2に向けては次の2つの提案が採択されています

次の提案は、Concurrency TS v2入りを目指してレビュー中です

予定では、2022年中にLEWGでのレビューを完了し、2023年後半に正式発効することを目指しています。

P2573R0 = delete("should have a reason");

関数のdelete指定にメッセージを付加できるようにする提案。

関数のdelete指定はC++11で導入され、特に、クラスの特殊メンバ関数の暗黙生成を抑制するために対応する宣言をプライベートで宣言だけしておくイディオムを置き換えることが目的でした。しかし、= delete;は特殊メンバ関数だけではなくあらゆる関数に拡張され、さらに強力な機能となりました。

導入から10年が経ち振り返ってみれば、この機能はライブラリ関数の誤使用を防止するC++11の重要な改善の1つだったと言ってよく、Andrew's C/C++ Token Count Dataset 2019.で調べてみるとその利用は4万件を超えており、標準ライブラリ内部でもより広いコミュニティ全体でも広く採用されています。

しかしその診断メッセージには問題があり、delete指定された関数が呼ばれた場合には単に削除されているとしか表示されず、なぜ削除されているのかがユーザからは不明瞭になっています。これと同様の問題は、[[deprecated]][[nodiscard]]でも報告され、これらはそれぞれC++14とC++20で理由を含むメッセージを指定できるように拡張されました。

この提案は、それらと同様に= deleteもメッセージを付加できるように拡張しようとするものです。

提案している構文は= delete("...");という形のもので、delete()に渡す文字列で理由を指定し、コンパイラはその関数が選択された場合のエラーメッセージにその文字列を出力します。

提案文書より、利用例

// フリー関数
void newapi();
void oldapi() = delete("This old API is outdated and already been removed. Please use newapi() instead.");

// 関数テンプレート
template<typename T>
struct A {/* ... */};

template<typename T>
A<T> factory(const T&) {/* process lvalue */}

template<typename T>
A<T> factory(const T&&) = delete("Using rvalue to construct A may result in dangling reference");

// メンバ関数
struct MoveOnly
{
    // ... (with move members defaulted or defined)
    MoveOnly(const MoveOnly&) = delete("Copy-construction is expensive; please use move construction instead.");
    MoveOnly& operator=(const MoveOnly&) = delete("Copy-assignment is expensive; please use move assignment instead.");
};

std::unique_ptrでの利用例

// [unique.ptr.single.general]
namespace std {
    template<class T, class D = default_delete<T>> class unique_ptr {
    public:
        // ...
        // disable copy from lvalue
        unique_ptr(const unique_ptr&) = delete(
            "unique_ptr<T> resembles unique ownership, so copy is not supported. Use move operations instead.");
        unique_ptr& operator=(const unique_ptr&) = delete(
            "unique_ptr<T> resembles unique ownership, so copy is not supported. Use move operations instead.");
    }
}

// [memory.syn]
namespace std {
    // ...
    template<class T>
        constexpr T* addressof(T& r) noexcept;
    template<class T>
        const T* addressof(const T&&) = delete("Cannot take address of rvalue.");

    // ...
    template<class T, class... Args> // T is not array
        constexpr unique_ptr<T> make_unique(Args&&... args);
    template<class T> // T is U[]
        constexpr unique_ptr<T> make_unique(size_t n);
    template<class T, class... Args> // T is U[N]
        unspecified make_unique(Args&&...) = delete(
            "make_unique<U[N]>(...) is not supported; perhaps you mean make_unique<U[]>(N) instead?");
}

// [basic.string.general]
namespace std {
    template<class charT, class traits = char_traits<charT>,
             class Allocator = allocator<charT>>
    class basic_string {
    public:
        // ...
        basic_string(nullptr_t) = delete("Construct a string from a null pointer is undefined behavior.");
}

これによって、ライブラリ開発者は今までの「あなたがやろうとしていることはわかるしそれは間違っている」というメッセージに加えて、「なぜ間違っているのか、推奨される方法を示すこともできる」という選択肢を手に入れることができ、ユーザーエクスペリエンスを改善することができます。

C++23の設計凍結時期は既に過ぎており、原則としてこの提案をC++23に含めることはできません。しかし、この提案の内容は既存機能の改善であり、=delete;のより広い採用と既存ライブラリの使いやすさの向上に不可欠なものであり後2年ほど待たせるべきでは無く、実装も簡単(clangのフォークで実装済み)である、と筆者の方は主張しておりC++23に向けて提案しています。

P2574R0 2022-05 Library Evolution Polls

2022年の5月に予定されている、LEWGでの全体投票の予定表。

次の提案が、LWGに進むための投票にかけられます。

最後の3つ以外はすべてC++23を目指しています。

P2577R0 C++ Modules Discovery in Prebuilt Library Releases

ビルド済みモジュールライブラリ配布のための規則についての提案。

これは、P2473R1の代替案となる提案です。P2473R1では、モジュール名とファイル名が対応していることを提案していましたがこの点についてコンセンサスを得られなかったようで、ライブラリ自体により近いメタデータファイルを読み取ることができるより高いレベルの抽象化が必要であるという指針が示されたようです。ただし、それは結局メタデータファイルの依存関係やビルドに必要なメタデータファイルを決定する方法などの問題を生みますが、これをどのように実現するかについてはまだコンセンサスを得られていないようです。

この提案は、ビルドシステムがビルド済みモジュールライブラリを使用するために、まず最初にそのライブラリにまつわるメタデータファイルを発見する方法についてのものです。

この提案では、今日のビルドシステムがビルド済みライブラリを利用する際に主にそのリンカ引数を取得するためにパッケージマネージャーと対話し、ビルド済みバイナリを正しく使用する方法をパッケージマネージャから取得していることに着目しています。

この実装そのものは完全に実装定義であり取得する情報(リンカ引数)の形式や意味も実装定義ですが、ビルドシステムがビルド済みライブラリを利用する際にそれらの情報を必要とし、取得しているという点は共通しています。また、これらの情報は実装定義の方法によってファイルに保存されることも共通しています。

この提案は、ビルド済みモジュールのメタデータファイルを決定論的に命名し、ビルドシステムはリンカ引数取得プロセスにおいて同時にそのメタデータファイルを取得し(リンカ引数からメタデータファイルへの変換を行うことでメタデータファイル名を得る)、それをパースすることでビルド済みモジュールライブラリの利用に必要な情報(バイナリの場所と名前、コンパイル時引数、依存関係など)を取得するというプロセス、及びそのメタデータファイルが現在のパッケージマネージャが配布するリンカ引数等の情報と同様に配布されることを提案しています。

具体的な配布形態や配布手段、メタデータがビルドシステムに伝達される手段などは実装定義となりますが、これは現在C++ビルド済みライブラリを使用するための要件でもあり、既に実装され広く使用されているはずです。

この提案によるアプローチのメリット

  • ODR違反の緩和
  • 既存のセマンティクスのみを使用する
    • この提案のほとんどの部分は現在の実装の上に構築可能であり、変更が必要なのはモジュールメタデータファイル周りのみ
    • モジュールメタデータファイルの依存関係を取り扱う必要がない

提案では、GNU/Linux環境(GNU Linkerを使用)を例にこれらのことの実装例を解説しています。この環境の場合、ライブラリはpkg-config名によって指定され、pkg-configファイルによってライブラリおよび依存ライブラリのリンカ引数が取得されます。

GNU/Linux環境の場合リンカ引数からモジュールメタデータファイルへの変換は簡単で、例えば取得されたライブラリファイル名(libfoo.a, libbar.so)の拡張子を変更してモジュールメタデータファイル(libfoo.meta-ixx-info, libbar.meta-ixx-info)を取得します。メタデータファイルがない場合はそのライブラリはモジュールを提供しないものと見做せばよく、メタデータファイルにはコンパイル時引数(コンパイラとリンカへの引数)およびモジュール依存関係グラフが含まれており(いる必要があり)、これを全て結合することでモジュールを正しく使用するために必要な情報の全てを取得することができます。

Windows環境におけるMT/MDオプションのように、的リンクと動的リンクでオプションが変化するような場合でも、現在のビルド設定からまずそのオプジョンを判別し、それに従って最初に探しに行くライブラリを変化させることで後の処理を共通化し、ビルド条件分岐を簡易に処理することができます。

おわり

この記事のMarkdownソース