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

文書の一覧

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

N4908 Working Draft, C++ Extensions for Library Fundamentals, Version 3

次期標準ライブラリ機能候補の実装経験を得るためのTSである、Library Fundamental TS v3の文書。

N4909 Editor's Report: C++ Extensions for Library Fundamentals, Version 3

↑の変更点を記した文書。

この版での変更は、LWG Issue 3649を反映したことです。

N4910 Working Draft, Standard for Programming Language C++

C++23ワーキングドラフト第6弾。

N4911 Editors’ Report - Programming Languages - C++

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

2月の会議で採択された提案とコア言語/ライブラリのIssue解決が適用されています。

P0009R16 MDSPAN

多次元配列に対するstd::spanである、mdspanの提案。

以前の記事を参照

このリビジョンでの変更は、LWGのフィードバックを受けての文言の修正です。

P0957R6 Proxy: A Polymorphic Programming Library

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

以前の記事を参照

このリビジョンでの変更は、ODR違反防止のためにstd::dispatch宣言の構文を再設計したこと、それに伴って、std::dispatch関連の文言を修正したことです。

以前の提案では、std::dispatchはテンプレート引数として1つめに関数型、2つめにそれに合うCallable(ディスパッチ処理)をNTTPで受けていました。このリビジョンでは、2つめのパラメータを無くして、std::dispatchの実装クラスのメンバ関数operator())としてディスパッチ処理を記述するようにしました。

// 以前の提案
struct Draw : std::dispatch<
    void(), [](const auto& self) { self.Draw(); }> {};
struct Area : std::dispatch<
    double(), [](const auto& self) { return self.Area(); }> {};

// この提案
struct Draw : std::dispatch<void()> {
  template <class T>
  void operator()(const T& self) { return self.Draw(); }
};
struct Area : std::dispatch<double()> {
  template <class T>
  double operator()(const T& self) { return self.Area(); }
};

ラムダ式がNTTPに渡されている時でもそのクロージャ型は宣言ごとに固有の型をもつため、ヘッダに宣言されたstd::dispatchの特殊化を複数の翻訳単位から参照するとODR違反を起こします。この変更はおそらくこれを防止するためのものです。

/// foo.h
template <auto>
struct foo { };

// 2つ以上の翻訳単位から参照されると未定義動作(ODR違反)
extern foo<+[]() {}> x;
inline foo<+[]() {}> y;

/// tu1.cpp
#include "foo.h"

/// tu2.cpp
#include "foo.h"

P1083R5 Move resource_adaptor from Library TS to the C++ WP

pmr::resource_adaptorをLibrary Foundermental TSからワーキングドラフトへ移動する提案。

以前の記事を参照

このリビジョンでの変更は、aligned_object_storage<T>Tはオブジェクト型でなければならないことを規定したこと、aligned_object_storage<T>TがCV修飾される可能性を考慮するようにしたこと、Library Foundermental TSの参照をv2からv3へ更新したことです。

この提案はLEWGのレビューをひとまず終えて、LWGに送られるための投票待ちをしています。

P1684R1 mdarray: An Owning Multidimensional Array Analog of mdspan

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

std::mdspanP0009)は別に確保された領域を適切に参照することで、所有権を保持せずに多次元配列を使用するものです。ユースケースによっては(小さい固定サイズの配列を使用する場合など)、多次元配列を所有した状態で同様に使用したい場合があります。それによって、std::mdspanでは回避できない間接参照のコストを削減でき、サイズが小さい場合は参照局所性の向上や最適化によるレジスタへの配置などを期待できます。この提案のmdarrayはその目的のための多次元配列クラスです。

// mdspanを使用する場合

void make_random_rotation(mdspan<float, std::extents<3, 3>> output);

void apply_rotation(mdspan<float, std::extents<3, 3>>, mdspan<float, 3>);

void random_rotate(mdspan<float, std::extents<dynamic_extent, 3>> points) {
  float buffer[9] = { };
  auto rotation = mdspan<float, 3, 3>(buffer);
  make_random_rotation(rotation);

  for(int i = 0; i < points.extent(0); ++i) {
    apply_rotation(rotation, subspan(points, i, std::all));
  }
}
// mdarrayを使用する場合

mdarray<float, std::extents<3, 3>> make_random_rotation();

void apply_rotation(mdarray<float, std::extents<3, 3>>&, const mdspan<float, std::extents<3>>&);

void random_rotate(mdspan<float, std::extents<dynamic_extent, 3>> points) {
  auto rotation = make_random_rotation();

  for(int i = 0; i < points.extent(0); ++i) {
    apply_rotation(rotation, subspan(points, i, std::all));
  }
}

この提案によるmdarrayは、mdspanとそれが参照するメモリ領域(を保持するコンテナ)を1つのクラスにまとめたような設計になっています。特に、そのインターフェースはmdspanと共通しており、mdspanの利用に慣れたユーザーがほぼそのままmdarrayを利用できるようになっています。

// mdarrayのクラス定義の一部例
template<class ElementType,
         class Extents,
         class LayoutPolicy = layout_right,
         class Container = ...>
class mdarray {
  ...

  using mapping_type = typename layout_type::template mapping<Extents>;

  ...
  
private:
  container_type ctr_;
  mapping_type map_;
};

mdarrayはその内部領域を保持するコンテナ型を一番最後のテンプレートパラメータで変更することができます。デフォルトでは、Extentsの一部がdynamic_extentであればstd::vector<ElementType>Extentsが全て静的であればstd::array<ElementType, N>NExtentsの値の積)となります。手動で指定する場合、Containercontiguous container要件を満たしている必要があります。

LayoutPolicyのデフォルトは、1次元の配列を多次元配列として(行優先で)アクセスするときのインデックス変換を行うものです。その変更は、たとえばその要素へのアクセスにアトミックアクセスが必要である場合などに使用することができます。

ただし、mdarrayは配列を所有するものであるため、mdspanとは異なりコンテナセマンティクスを持ちます。特に、mdarraydeep constnessを持っており、mdarray事態のconst性は(そのconstメンバ関数を通して)各要素まで伝播されます。これはmdspanとは異なり、std::vectorなどとのコンテナと共通する性質です。

P1708R6 Simple Statistical Functions

標準ライブラリにいくつかの統計関数を追加する提案。

以前の記事を参照

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

  • stats_result_tの削除
  • Accumulator objectを並列実装が可能となるように改善
  • stat_accumweighted_stat_accumの削除
  • より多くのデータ型を許可するために一部の制約を緩和
  • Projectionの削除(必要なら、views::transformによって行えるため)
  • 多くの名前に変更

などです。

P1839R4 Accessing Object Representations

reinterpret_cast<char*>によるオブジェクト表現へのアクセスを未定義動作とならないようにする提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言内のオブジェクト表現のNについて曖昧な使用を修正したことです。

P2264R3 Make assert() macro user friendly for C and C++

assertマクロをC++の構文に馴染むように置き換える提案。

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

  • Macos Mojaveでシステムヘッダとしてのassertの利用経験から、assert.hのを修正
  • WG14がこの提案を採択し場合、なにがCにとって必要かを示すために、C++の文言を明示化した

などです。

WG14は、C23に向けてこの提案を採択しています。C++に向けてはC++26を目指して作業しています。

P2290R3 Delimited escape sequences

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

前回の記事を参照

このリビジョンでの変更は、conditional-escape sequenceのリストから\oを取り除いたことです。

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

P2465R3 Standard Library Modules std and std.compat

標準ライブラリモジュールについて最小のサポートをC++23に追加する提案

以前の記事を参照

このリビジョンでの変更は、CWGのフィードバックを受けての文言の修正です。

この提案はすでにLWG/CWGでのレビューを終えており、次の全体会議で投票にかけられることが決まっています。

P2510R1 Formatting pointers

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

以前の記事を参照

このリビジョンでの変更は、LEWGのフィードバックを受けての文言の修正です。

P2511R1 Beyond operator(): NTTP callables in type-erased call wrappers

std::move_only_fuctionを、呼び出し可能なNTTP値を型消去するCallable Wrapperへ拡張する提案。

以前の記事を参照

このリビジョンでの変更は、寄せられたコメントへの返答、std::functionのための文言を追加した事などです。

このリビジョンでは、std::functionに対してもnontype_tからの構築を行うコンストラクタが追加されました。

P2521R2 Contract support - Working Paper

C++に最小の契約プログラミングサポートを追加する提案。

以前の記事を参照

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

  • 事前条件と事後条件の評価順序について提案を追加
    • 引数初期化 -> 事前条件評価 -> 関数本体の評価 -> ローカル変数のデストラクタ実行 -> 事後条件評価、の順を提案
  • 事後条件で使用される非参照の関数引数はconstでなければならない、とした
  • 関数の仮引数の代わりに、関数の実引数の値を事後条件で使用したとしても、事後条件で非参照引数を利用する際の問題を解決できないことに関する説明を追記
  • 副作用の除去を提案する動機を更新

などです。

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

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

std::print(及びfmt::print)では、文字化け防止のためにコンソール出力時にストリームバッファをバイパスして書き込んでおり、その際に環境ネイティブのユニコードAPIを使用する場合があります。

P2093のレビューでは、std::printをその基礎となるストリーム(出力先ストリーム、デフォルトではstdout)と同期させることが移行のために有益だろうという意見が出たようです。この文書は、その提案についての報告書です。

printf("first\n");
std::print("second\n");

この単純なコードは、次のように出力されることが期待されます。

first
second

しかし実際には、この順番は入れ替わる可能性があります。なぜなら、printfはバッファリングするのに対して、std::printはそれを行わないためです。

これを期待通りに出力するためには、std::printが書き込み動作をする前にストリームバッファをflushする必要があります。ただし、それをすると小さくない追加のコストがかかります。そして、{fmt}でもRustのコンソール出力でも、現在そのような同期は行われていません。

この文書は何かを提案するものではなく、問題の周知や別の解決策・対案を得るためのものです。

P2540R1 Empty Product for certain Views

viewの空積は空のタプルになるべきという提案。

以前の記事を参照

このリビジョンでの変更は、提案する文言を追加したことです(多分)。

この提案はLEWGのレビューをひとまず終えて、LWGに送られるための投票待ちをしています。

P2553R1 Make mdspan size_type controllable

P0009で提案中のstd::mdspanについて、そのsize_typeを制御可能とする提案。

以前の記事を参照

このリビジョンでの変更は、size_typeを符号付き整数型に制限しないようにしたこと、それに伴ってextentsのコンストラクタに事前条件(0以上の値であること)を追加したこと、size_typeの変換が縮小変換となる場合にexplicitになるようにしたことなどです。

この提案はLEWGのレビューをひとまず終えて、LWGに送られるための投票待ちをしています。

P2555R1 Naming improvements for std::execution

P2300で提案されている、senderファクトリ及びsenderアダプタの一部のものについて、名前を改善する提案。

以前の記事を参照

このリビジョンでの変更は、文字化けを修正したことです。

P2558R0 Add @, $, and ` to the basic character set

@ $ `の3種類の文字をソースコードの基本文字集合に追加する提案。

これらの文字はAsciiに含まれているものですが、ソースコードの基本文字集合basic character set)には含まれていませんでした。基本文字集合C/C++の基本的な文法を記述するために使用される文字の集合であり、そこに追加しておけば将来的な構文拡張の際に使用可能となります。

この3つの文字は、C++では基本文字集合にこそ含まれていなかったものの、1バイト文字として翻訳文字集合Translation character setソースコードを記述するのに使用可能な文字の集合。C++23で導入された概念)には含まれていました。Cには翻訳文字集合の概念はなく、この3つは基本文字集合に含まれていませんでしたが、C23でこの3つはCの基本文字集合に追加されました。CはC++よりさらに保守的(後方互換に敏感)ですが、この3つの文字の将来的な利用に障害がないことを確認した上で基本文字集合に追加しています。

C++ではCほどの重要性はないものの、将来的な構文のために使用可能にしておくことは有益であるとして、C23での決定を踏襲してC++でも@ $ `を基本文字集合へ追加しておこうとする提案です。

P2560R0 Comparing value- and type-based reflection

値ベースのリフレクションと型ベースのリフレクションの比較スライド。

現在のリフレクション機能の候補には型ベースのものと値ベースの2種類があります。型ベースはリフレクションの結果(メタオブジェクト、メタ情報を格納するもの)が型で得られるのに対して、値ベースはリフレクションの結果が値(コンパイル時の値)で得られます。型ベースはTMPとの親和性に、値ベースはconstexpr処理との親和性に優れています。

Reflection TSは現在型ベースのリフレクションで設計されていますが、リフレクションの方向性としては値ベースのものにしていこうとしているようです。

このスライドはその両者を比較するもので、ユーザビリティでは型ベースが優れており、コンパイル時間への影響は値ベースの方が小さいことを報告しています。その結論としては、値ベースリフレクションにはコンパイル時間の利点しかなく、それだけを重視して値ベースのリフレクションを採用するのか?という疑問を投げかけています。

P2562R0 constexpr Stable Sorting

std::stable_sortとそのファミリをconstexpr対応する提案。

この提案の対象は次のものです。

  • std::stable_sort
  • std::stable_partition
  • std::inplace merge
  • これらのranges

これらのものはどれも安定ソート(同値なものの相対順序が保たれるソート)によって並べ替えやマージを行うものですが、rangesのものも含めてどれもconstexprではありません。その原因は、入力範囲の長さによって追加のメモリ領域を使用するかしないかを動的に分岐する実装になっているためです。

このことは規格によっても指定されていて、例えばstd::stable_sortなら「計算量はN log2(N)(回の比較)、ただし追加のメモリが使用できる場合はN log N」のように規定されています。追加のメモリが使用できる場合がいつなのかは指定されていませんが、実装は入力範囲が一定の長さを超える場合にその場でメモリを確保して使用します。

この追加のストレージを使用する経路が、stable_sortとそのファミリのconstexpr対応を妨げていたようです。ただし、追加のメモリを使用しない経路の実装は現在でもconstexpr対応が可能であり、こちらの経路はそのままconstexpr指定することができます。

このような実行経路による定数実行可能性の問題は、std::is_constant_evaluated()によって解決されます。これによって、定数式で実行可能なパスの選択を静的に行うことができるため、std::stable_sortとそのファミリをconstexpr対応させることができます。

また、C++20からは動的メモリ確保が定数式で行えるようになり、std::vectorを定数式で使用可能となります。C++23からは同様にstd::unique_ptrも定数式で使用可能となるため、これらを用いた実装ならばすべてのパスでconstexpr対応させることが可能です。

P2564R0 consteval needs to propagate up

consteval関数をconstexpr関数の中で呼び出すことができない問題を軽減する提案。

値ベースのリフレクション(P1240)では、std::meta::infoという単一の型によってリフレクションの結果を表現することが提案されています。そして、多くのところではstd::meta::infoのシーケンス(std::vector<std::meta::info>など)を扱うことになることが予想されます。すると、自然に既存のアルゴリズム<algorithm>)を使用したくなります。

namespace std::meta {
  // std::meta::infoの仮の実装とする
  struct info {
    int value;
  };

  // std::meta::infoがある条件を満たしているかを調べる関数の一例
  consteval auto is_invalid(info i) -> bool {
    // 偶数を許可しない
    return i.value % 2 == 0;
  }
}

// std::meta::infoのシーケンス
constexpr std::meta::info types[] = {1, 3, 5};

std::meta::infoの実装は現在ないので、このような仮想的な実装で実験をしてみます(ここでの問題にはこれで十分です)。ここで注意するのは、std::meta::is_invalid()consteval関数であることです。

// NG
// is_invalid()の呼出(none_of内)は即時関数コンテキストではない
static_assert(std::ranges::none_of(
  types,
  std::meta::is_invalid
));

// NG
static_assert(std::ranges::none_of(
  types,
  [](std::meta::info i) {
    return std::meta::is_invalid(i);  // is_invalid()の呼出は即時呼出となるが、iが定数式ではないためNG
  }
));

// NG
// is_invalid()をラップするラムダの呼出(none_of内)は即時関数コンテキストではない
static_assert(std::ranges::none_of(
  types,
  [](std::meta::info i) consteval {
      return std::meta::is_invalid(i);
  }
));

// NG
// 即時関数コンテキストではないところで、consteval関数の関数ポインタを取れない
static_assert(std::ranges::none_of(
  types,
  +[](std::meta::info i) consteval {
      return std::meta::is_invalid(i);
  }
));

次に、アルゴリズム(ここではnone_of)の結果を直接static_assertに渡す代わりに、consteval関数の内部で使用してみます。

// OK(ただし言語ルール的には)
// 標準ライブラリのルールでは、規定されていない限りそのアドレスをとることができない
consteval auto all_valid() -> bool {
  return std::ranges::none_of(
    types,
    std::meta::is_invalid
  );
}
static_assert(all_valid());

// NG
consteval auto all_valid() -> bool {
  return std::ranges::none_of(
    types,
    [](std::meta::info i) {
      return std::meta::is_invalid(i);  // iは定数式ではない
    }
  );
}
static_assert(all_valid());

// NG
// is_invalid()をラップするラムダの呼出(none_of内)は即時関数コンテキストではない
consteval auto all_valid() -> bool {
  return std::ranges::none_of(
    types,
    [](std::meta::info i) consteval {
      return std::meta::is_invalid(i);
    }
  );
}
static_assert(all_valid());

// OK
consteval auto all_valid() -> bool {
  return std::ranges::none_of(
    types,
    +[](std::meta::info i) consteval {
        return std::meta::is_invalid(i);
    }
  );
}
static_assert(all_valid());

一番最後の方法(consteval関数の中で、consteval関数をconstevalラムダでラップして、関数ポインタを取得する)だけが、完全に合法的にconsteval関数を既存のアルゴリズムで使用する方法です。

この問題の原因は、consteval関数を呼び出すことができるのは即時関数コンテキスト(immediate function context)と呼ばれる文脈の中だけであることから起きています。特に、他の関数内でconsteval関数を呼び出そうとすると、その関数もまたconstevalでなければなりません。そうするとconsteval関数は呼び出せなくなる気がしますが、呼出が完全に定数式であれば(引数が定数式であれば)どこの関数内でも呼び出すことができ、その場合のことを即時呼出(immediate invocation)と言います。

consteval f(int i) {
  return i;
}

constexpr void g(int i) {
  // g()の内部は即時関数コンテキストではない

  f(0); // ok、即時呼出
  f(i); // ng、iは定数式ではない
}

consteval void h(int i) {
  // h()の内部は即時関数コンテキスト

  f(0); // ok
  f(i); // ok
}

std::ranges::none_ofをはじめとする標準アルゴリズムは、constexpr指定されているものはあってもconsteval指定されているものはありません。そのため、関数を受け取って何かをするアルゴリズムの場合、その内部が即時関数コンテキストではないことからconsteval関数を受け取って実行することができません。

これと同様の問題はstd::is_constant_evaluated()でも問題となっていて、C++23ではその解決のためにif constevalが導入されました(P1938R3)。if constevaltrueブロック(定数式で実行されるべきブロック)を即時関数コンテキストとすることで、定数式でしか実行されないtrueブロックにおいてconsteval関数を自然に呼び出せるようにします。これを利用すると、この問題の解決を図れそうです。

// OK
static_assert(std::ranges::none_of(
  types,
  [](std::meta::info i) {
    if consteval {
      return std::meta::is_invalid(i);  // ここは即時関数コンテキスト
    }
  }));

// NG
// none_of内は即時関数コンテキストではない
static_assert(std::ranges::none_of(
  types,
  [](std::meta::info i) consteval {
    if consteval {
      return std::meta::is_invalid(i);
    }
  }));

それでもなお、アルゴリズムが直接呼び出す関数はconstevalであってはいけません。そのため、非constevalラムダによるラップが必要です。根本的な解決を図るには、ライブラリ関数の変更が必要です。

template <ranges::input_range R, class Pred>
constexpr auto my::none_of(R&& r, Pred pred) -> bool {
  auto first = ranges::begin(r);
  auto last = ranges::end(r);
  for (; first != last; ++first) {
    if consteval {
      // 即時関数コンテキスト
      if (pred(*first)) {
        return false;
      }
    } else {
      // 即時関数コンテキストではない
      if (pred(*first)) {
        return false;
      }
    }
  }
  return true;
}

if constevalの半目的外使用に目を瞑れば、このような変更でそのままpredconsteval関数を渡せるような気がします。ただしこれは実際には機能しません。なぜなら、predconsteval関数の場合、if constevalfalseブロックで結局同じ問題が発生するからです。

ライブラリ側で解決を図るには、もっと大元でif constevalで分岐しておくことで、定数式(即時関数)用の処理と実行時処理を分岐する必要があります。そして、その分岐先の処理(コード)は全く同じになるでしょう。しかし統一することはできません・・・

無論そのような醜い解決策は解決とはいえないため、この問題の解決には言語レベルでの対処が必要となります。

問題を単純化してみてみると

constexpr auto pred_bad(std::meta::info i) -> bool {
  return std::meta::is_invalid(i);    // NG、即時関数コンテキストではない
}

constexpr auto pred_good(std::meta::info i) -> bool {
  if consteval {
    return std::meta::is_invalid(i);  // OK、即時関数コンテキスト
  }

  // UB、実行時にリターンしない
}

pred_bad()は今日ではNGであり、consteval関数が確実にコンパイル時にのみ呼ばれるという性質を保証する1つの方法の結果です。pred_good()consteval関数の呼出がif constevalのブロック(即時関数コンテキスト)にのみ現れているため、問題ありません。ここで、pred_good()constexprとマークされていますが、実際には定数式でのみ呼び出すことができます(何もリターンしないため実行時にはUB)。従って、これも優れた解決策であるとはいえません。

pred_good()コンパイル時にのみ呼出可能なようにすることができれば問題は解決しますが、それは結局consteval関数に行き付き堂々巡りになります。

最良の方法はどうやら、pred_bad()が実行時に呼び出されないことを保証すればよさそうです。pred_bad()の問題点はconsteval関数が確実に呼ばれるのにconstexpr関数であることから実行時にも呼び出すことができうる点にあります。したがって、pred_bad()の呼出がコンパイル時にのみ行われることを保証できれば、pred_bad()がNGである必要は無くなります。

この提案は、constexpr関数がコンパイル時にのみ呼出可能である場合に、それらの関数を暗黙consteval関数とすることで上記の問題の解決を図るものです。そのために、次のようなルールを提案しています

  1. constexpr関数が即時関数コンテキスト外のconsteval関数呼出を含み、その呼出が定数式ではない場合、そのconstexpr関数は暗黙的にconsteval関数となる。
  2. 式が、1と同様のコンテキストで即時呼出ではないconsteval関数を呼び出す場合、そのコンテキストも暗黙的にそのコンテキストも暗黙的にconsteval関数(即時関数コンテキスト)となる。
  3. manifestly constant evaluatedコンテキスト(std::is_constant_evalueted()trueとなるコンテキスト)は即時関数コンテキストと見做される。

これらの変更によって、先ほどNGだったstd::meta::infoのシーケンスに対する標準アルゴリズムの呼出は、全てコンパイルが通り、(直接渡すものもラムダによるものも)意図通りに実行されるようになります。

P2565R0 Supporting User-Defined Attributes

ユーザー/ベンダー定義の属性について、全てのコンパイラでサポートされる(警告が出ない)属性構文の提案。

C++標準では、「規格で指定されていない属性は実装(コンパイラ)によってサポートされ、実装が認識できない属性については無視される」のように規定されており、非標準の属性をサポートしています。コンパイラは別のベンダーやユーザー定義の属性については無視することでサポートすべきですが、現在主要なコンパイラは属性名のタイプミス検出のために不明な属性について警告を発します。

// deprecatedのタイプミス
[[ edprecated("Broken. Use `bravo`.") ]]
double banana(double num);
// Clang 13.0.1
<source>:6:4: warning: unknown attribute 'edprecated' ignored
[-Wunknown-attributes]
[[ edprecated("Broken. Use `bravo`.") ]]
   ^~~~~~~~~~

// GCC 11.2
<source>:7:25: warning: 'edprecated' attribute directive ignored [-Wattributes]
  7 | double banana(double num);
    |

// MSVC v19.30
<source>(6): warning C5030: attribute 'edprecated' is not recognized

これはユーザー定義属性(何かしらの手段で実装したとする)に対しても同様です。

// deprecated属性の拡張、2つ目の引数に重要度を受け取る
[[ bespoke::deprecated("Broken. Use `charlie`.", "warn") ]]
double carrot(double num);
// Clang 13.0.1
<source>:9:4: warning: unknown attribute 'deprecated' ignored
[-Wunknown-attributes]
[[ bespoke::deprecated("Broken. Use `charlie`.", "warn") ]]
   ^~~~~~~~~~~~~~~~~~~>

// GCC 11.2
<source>:10:25: warning: 'bespoke::deprecated' scoped attribute directive ignored [-Wattributes]
  10 | double carrot(double num);
     |

// MSVC v19.30
<source>(9): warning C5030: attribute 'bespoke::deprecated' is not recognized

そして、ベンダー定義属性についても、各コンパイラが互いに同様の報告をします。

[[ clang::no_sanitize("undefined") ]]
[[ gnu::access(read_only, 1) ]]
[[ msvc::known_semantics ]]
double daikon(double num);
// Clang 13.0.1
<source>:13:4: warning: unknown attribute 'access' ignored [-Wunknown-attributes]
[[ gnu::access(read_only, 1) ]]
   ^~~~~~~~~~~
<source>:14:4: warning: unknown attribute 'known_semantics' ignored [-Wunknown-attributes]
[[ msvc::known_semantics ]]
   ^~~~~~~~~~~~~~~~~~~~~

// GCC 11.2
<source>:15:21: warning: 'clang::no_sanitize' scoped attribute directive ignored [-Wattributes]
  15 | int* daikon(int* num);
     | ^
<source>:15:21: warning: 'msvc::known_semantics' scoped attribute directive ignored [-Wattributes]
Compiler returned: 0

// MSVC v19.30
<source>(12): warning C5030: attribute 'clang::no_sanitize' is not recognized
<source>(13): warning C5030: attribute 'gnu::access' is not recognized

このことは、ベンダー定義であってもユーザー定義であっても、非標準の属性は実質的にサポート(警告もない完全な無視)されていないことを意味しています。この問題の回避のために、規模の大きなプロジェクトやライブラリでは、プリプロセッサとマクロによってコンパイラを検出し適切に非標準属性の表示を切り替えることが行われています。しかし、その方法は複雑であり、正しく書くのは難しいものがあります。

この提案は、現在のこの挙動(標準属性タイプミスの検出のための警告)を受け入れつつ、属性構文を拡張することによって、ユーザー/ベンダー定義の属性を全てのコンパイラでサポートされる(無視されるなら警告されない)ようにすることを提案するものです。例えば次のような構文を提案しています

// タイポではない意図的な属性であることを表す
[[ extern gnu::access(...) ]];

この提案ではどのような構文やメカニズムを採用するかについては確定しておらず、これ以外の代替案を募集するものでもあります。

P2568R0 Proposal of std::map::at_ptr

.at()を提供する標準コンテナに、.at_ptr()メンバ関数を追加する提案。

.at_ptr()はその名の通り、.at()して見つかった要素のポインタを返すものです。もし要素が見つからない場合はnullptrを返し、例外を投げません。

int main() {
  std::map<int, int> m = /*...*/;

  if (int* v = m.at_ptr(42)) {
    // 要素が見つかった場合の処理

  } else {
    // 要素が見つからなかった場合の処理
  }

}

このように、コンテナ内の特定要素を検索し存在すればそれに対して何かする、という処理はさまざまなプログラムやアルゴリズムで非常によく見られる処理です。しかし、.at()関数は要素が見つからない場合に例外を投げ、[]は要素が見つからない場合にデフォルト構築して返すなど使いづらい部分があり、要素の検索と引き当てを別々に書く必要がありました。そのため、そのような典型的なパターンを1まとめにしてこの.at_ptr()と同じことを行う関数がさまざまなライブラリやプロジェクトに存在しています。この提案は、そのような既存の慣行を標準コンテナのメンバ関数として標準化するものです。

この提案ではすでに.at()を持っているコンテナのみを対象としています

  • std::string
  • std::string_view
  • std::array
  • std::vector
  • std::map
  • std::unorderd_map
  • std::deque

また、std::optionalは参照型を保持することが(現在は)できないため、この用途に適しておらず、この提案ではポインタを返すことを提案しています。

P2569R0 *_HAS_SUBNORM==0 implies what?

*_HAS_SUBNORMを削除する提案。

*_HAS_SUBNORMとは、<float.h>/<cfloat>に定義されているマクロで、float, double, long double型における非正規化数のサポート状況を取得するためのものです。このマクロの値が1であれば非正規化数がサポートされており、-1の場合はその判定ができないことを表しています。

0の場合は非正規化数がサポートされていないことを表していますが、その場合の振る舞いは明確ではありません。例えば、fpclassify()に非正規化数を渡した場合の動作は不明瞭です。

また、ARM, x86, NVIDIAGPUなど、非正規化数の扱いに関する制御ビットを持つアーキテクチャが存在しています。その制御ビットの値によって、次のように非正規化数の扱いを変化させることができます

  • 非正規化数のオペランドをゼロとして扱う
  • 非正規化数の結果をゼロとして扱う(フラッシュ)

ただし、x86は丸めの後に非正規化数を検出し、ARMは丸めの前に検出するなどその振る舞いは実装によって変化しており、さらにはその制御ビットを実行時に変化させる実装が提供されていることもあります。つまりは、*_HAS_SUBNORMの値はコンパイル時定数ではなく、どちらかといえば実行時に変化する値です。

非正規化数はCの浮動小数点モデルの一部ではありますが必須の要素ではなく、IEC 60559を除いて標準仕様としてその動作を指定するものがありません。非正規化数をサポートする実装は様々に異なる方法で非正規化数を扱います。現在の*_HAS_SUBNORMは実装や利用の上で曖昧であり、これを解決する方法を見つけることができていません。したがって、この提案では将来の改定のためにも*_HAS_SUBNORMを削除することを提案しています。

ただし、非正規化数のフラッシュ(非正規化数の結果をゼロにする)は最適化などの点から有用であり広く使用されていることからC標準はそれをサポートし続ける必要があるため、フラッシュされた非正規化数の振る舞いを明確に規定することも同時に提案しています。

これはC標準に対するものですが、SG22を通してC++にも提案されています。

おわり

この記事のMarkdownソース