- cpp23[meta cpp]
このページはC++23に採用された言語機能の変更を解説しています。
のちのC++規格でさらに変更される場合があるため関連項目を参照してください。
C++では、スコープ([basic.scope])と名前ルックアップ([basic.lookup])に関する規格上の記述が、長年の機能追加によって分散・重複していた。 用語の定義も曖昧かつ不整合な状態となっており、特にモジュール導入後は「ルックアップは同一翻訳単位内の先行宣言に限られる」という暗黙の前提が成り立たなくなり、これらの章を整理する必要性が高まっていた。
P1787R6は、[basic.scope]と[basic.lookup]の章をまるごと書き直し、用語と規則を整備するための提案である。 変更の大部分は規格書の編集上の整理 (editorial reorganization) であり、新たに導入された言語機能はない。 ただしこの整理の過程で、長年解決されていなかった言語の不具合報告 (CWG issue) が63件、リフレクタ上で議論されていた問題が21件、まとめて解決される。 これに伴い、いくつかの観測可能な振る舞いの変更も含まれる。
P1787R6は、スコープと名前ルックアップを記述するための語彙を再構築している。 主な新用語や関係のある用語と、それに対応する従来用語を以下に示す。
| 新しい用語 | 概要 | 従来の用語との対応 |
|---|---|---|
| スコープ (scope) | 名前を識別するための領域 | 「declarative region」と「scope」の使い分けを廃止し、「scope」に一本化 |
| 直近スコープ (immediate scope) | プログラム上のある点を含む最小のスコープ | 暗黙的に使われていた概念を明示化 |
| 親スコープ (parent scope) | あるスコープを (それ自身を除いて) 含む最小のスコープ | 入れ子構造の記述を明確化 |
| 居住する (inhabit) | 宣言が、自身のローカスにおける直近スコープに属すること | 宣言の所在の表現を明確化 |
| ターゲットスコープ (target scope) | 宣言が居住するスコープ | 暗黙的に説明されていた概念を明示化 |
| ローカス (locus) | 宣言がプログラム中に導入される位置 | 「point of declaration」を置き換える |
| 結合する (bind) | 宣言が導入する名前を、ターゲットスコープ内でその宣言と結びつけること | 名前と宣言の関係を明示化 |
| 先行する (precede) | 点Pにおいて宣言Xが「Pに先行する」という関係。XのローカスがPより前にある場合、XがクラススコープにありPから到達可能 (reachable) な場合、Xが別の翻訳単位のモジュール経由でPから到達可能な場合 (export済みの宣言、もしくは同一モジュール内で内部リンケージを持たない宣言が対象。詳細な前提条件はP1787R6本文を参照) のいずれかを含む | モジュール導入後の到達可能性を含めて再定義 |
| 単一探索 (single search) | あるスコープSと点Pに対して、Pに先行しSに名前Nが結合されたすべての宣言を見つける基本操作 | 散在していたルックアップ規則の最小単位として整理 |
| ルックアップ文脈 (lookup context) | メンバ修飾名 (./->の右側の名前) では、関連オブジェクト式の型。それ以外の修飾名では、先行するnested-name-specifierが指し示す型・テンプレート・名前空間 |
修飾名ルックアップの起点を明示化 |
| 構成名 (component name) | unqualified-idを構成する根本の名前 (template-idの場合はそのテンプレート名)。テンプレート引数やdecltypeの中には再帰しない |
「型として現れる名前」という曖昧な表現を置き換える |
| 修飾名 (qualified name) / メンバ修飾名 (member-qualified name) | qualified-idやusing宣言子の終端の名前を「修飾名」、./->の右側の名前を「メンバ修飾名」と呼ぶ |
ルックアップの対象となる名前の種類を整理 |
| 指名可能 (nominable) | クラス・クラステンプレート・名前空間Eに対して、点Pにおいて宣言が「指名可能」とは: (1)宣言がPに先行し、(2)宣言がブロックスコープに居住しておらず、(3)宣言のターゲットスコープがEのスコープか、Eが名前空間のときはそのインライン名前空間集合の任意要素のスコープと一致する、こと | 修飾名ルックアップでの可視性を整理 |
| 対応する (correspond) | 2つの宣言が、同じ名前を (再) 導入するか、両方ともコンストラクタを宣言するか、両方ともデストラクタを宣言する関係 (一定の例外を除く) | 宣言マッチング規則を統一的に表現 |
| 潜在的に衝突する (potentially conflict) | 対応する宣言が、共有された名前を異なるエンティティに対して指す可能性があること | 宣言間の衝突条件を明確化 |
P1787R6の主目的は規格の整理であるが、その過程でCWG issueの解決を兼ねた、いくつかの観測可能な変更が含まれている。
非依存なusing宣言は、名前ルックアップの段階で「using宣言そのもの」ではなく「その参照先の宣言」に置き換えられて扱われるようになった。
これにより、usingディレクティブと組み合わせたときの曖昧さの判定が、規格上明確に規定される。
namespace F { float x; }
namespace A {
namespace I { int x; }
using namespace I;
using F::x;
void f() {
x = 0; // エラー: 曖昧 (Aの中のF::xと、using namespace I経由で見えるI::xが衝突)
A::x = 1; // OK: A内の修飾ルックアップではF::xのみが見つかる
}
}friend宣言の宣言子IDの中に現れる名前は、まず宣言子IDが指す名前空間の直接 (immediate) のスコープのみで探索される。
そのスコープに見つからなければ、通常の非修飾ルックアップにフォールバックする。
直接のスコープには、内部の (inline 含む) 入れ子の名前空間で導入された名前は含まれない。
using D = double;
namespace A {
inline namespace N { using C = char; }
using F = float;
void f(D);
void f(C);
void f(F);
}
namespace B {
class Y {
friend void A::f(D); // OK: Aの直接のスコープにDはないが、非修飾ルックアップで外側のD=doubleが見つかる
friend void A::f(F); // OK: Aの直接のスコープでF=floatが見つかる
friend void A::f(C); // エラー: CはAの内部の inline namespace Nに属しており、Aの直接のスコープには含まれない
};
}デストラクタ呼び出しの~XのXは、注入されたクラス名 (injected-class-name) として解決される必要がある。
他のクラスを経由するtypedef名などは候補として考慮されない。
struct A { using X = A; };
struct B : A { struct X {}; };
void f(B* b) {
b->A::~X(); // エラー: A::X (typedef名) はデストラクタ名のルックアップで考慮されない
}テンプレートパラメータスコープの親となるのは、別のテンプレートパラメータスコープのみであると規定された。 これにより、テンプレート定義内の非修飾ルックアップにおける探索順序が明確になる。 ただしCWG459 (基底クラスはテンプレートパラメータより先に探索される) の振る舞いは維持される。
依存型を含む変換関数ID (conversion-function-id) も依存名として扱われるよう、依存名の定義が拡張された (CWG1500・CWG1936の解決)。 これに伴い、依存ルックアップは型の置換後に行われることが明確化された。
別の翻訳単位の宣言であっても、exportされた宣言や、同一モジュール内で内部リンケージを持たない宣言であってモジュール経由で到達可能なものは、ルックアップの対象となることが規格上明示された。
これはモジュール機能の前提に合わせた整理である。
オーバーロード集合は、要素が1つしかない場合 (singleton) でも一貫した規則で構築されるようになった。
[basic.scope]および[basic.lookup]の問題は次のように整理できる:
- 「scope」の定義の二重性: 規格上、「scope」という語が異なる意味で2通り定義されており、暗黙のうちに名前ルックアップの規則を含んでいたが、[basic.lookup]の記述と完全には一致していなかった。
- 名前空間の取り扱いの不整合: グローバル名前空間を含む名前空間が、ある場所では1つのdeclarative regionとして、別の場所では複数のregionとして記述されていた。
- モジュール導入による前提の崩れ: 「ルックアップは同じ翻訳単位の中の、その時点までに現れた宣言だけを対象とする」という暗黙の前提が、モジュールの導入で成り立たなくなった。
<トークンの曖昧性: テンプレートIDか比較演算子かを判別するための規則が、ルックアップの記述と絡み合っていた。- 同一エンティティの判定: 同じ名前の複数の宣言が同一のエンティティを導入するか、別のエンティティを導入するかを一貫した規則で判別できなかった。
P1787R6は、これらの問題を解決するために、新しい用語を導入したうえで、[basic.scope]と[basic.lookup]の章を全面的に書き直す。 63件のCWG issueと21件のリフレクタ議論で報告された問題が、この整理によって一括で解決される。
ユーザーが書く通常のコードに与える影響は限定的だが、規格書を直接参照するC++ユーザー、コンパイラ実装者、ライブラリ実装者にとっては、本変更でC++の名前ルックアップ規則が初めて一貫した枠組みのもとに整理されたことになる。
[basic.scope]と[basic.lookup]の章は全面的に書き直されている。 編集上の主な変更点としては、以下が挙げられる:
- 「declarative region」の語を廃し、「scope」に統一する。
- 「point of declaration」の語を廃し、「locus」に置き換える。
- 「in scope」「global function」「global object」「overloaded function」といった曖昧な表現を削除する。
- 散在していたルックアップ規則を、単一探索 (single search) などの基本操作の組み合わせとして再定義する。
- ルックアップは常に「名前」に対する操作として定義し、id-expressionやtype-idに対する操作とは区別する。
- P1787R6 Declarations and where to find them
- CWG2370 friend declarations of namespace-scope functions
- CWG1500 Name lookup of dependent conversion function
- CWG1936 Dependent qualified-ids
- そのほか本提案で解決された計63件のCWG issue・21件のリフレクタ議論 (P1787R6本文を参照)