C++ の言語規則と名前付き要件

最終更新: 2023-09-23 12:23:05 +0800 +0800
原文は英語で書かれています。あなたはAI翻訳版を読んでいます。

C++ の言語規則は、プログラムが適格であるか、およびその実行が何を意味するかを決定します。ライブラリの名前付き要件とコンセプトは、ジェネリックコードに契約を追加します。似た用語によって重要な違いが隠れる場合があります。コンパイラーは式と型を検査できますが、型が約束するすべてのセマンティクス、計算量、有効性、または等価性を証明することはできません。

定義と One Definition Rule #

odr-use されるすべての非 inline 関数または変数には、プログラム内に 1 つの定義が必要です。互換性がある場合は宣言を繰り返せるため、前方宣言が可能です。

cpp
class Node;

struct Node {
    Node* next = nullptr;
};

型、テンプレート、inline 関数、および inline 変数は、それぞれの整合性要件を満たす場合に限り、複数の翻訳単位に定義を持てます。ヘッダーガードは 1 つの翻訳単位内での重複インクルードを防ぎますが、翻訳単位間で異なる定義を修復するものではありません。ODR 違反があってもプログラムのコンパイルとリンクに成功する場合がありますが、その動作は信頼できません。

名前付き要件とコンセプト #

C++20 より前は、標準ライブラリのジェネリック契約が LegacyInputIterator などの名前付き要件として表現されていました。C++20 のコンセプトは、その構文的要素の多くを制約として表現します。

cpp
#include <concepts>
#include <iterator>

template<std::input_iterator Iterator>
void consume(Iterator first, Iterator last);

制約付き宣言は、必要な式と関連する型関係を満たさない型を拒否します。制約の充足は、意味的な振る舞いを確立しません。型は ++i、*i、i == j を適格にしながら、複数回走査、等価性の保持、または明記された計算量の上限に違反する可能性があります。同じ境界は一様乱数ビット生成器にも当てはまります。型チェックでは、その出力が一様に分布することを実証できません。

ジェネリック契約の記述 #

ジェネリック API は階層に分けて指定します。

  1. 必要な式と関連型を、標準コンセプトまたはプロジェクトのコンセプトを用いて記載する。
  2. 操作の意味、等価性を保持する式、および呼び出し後も値が有効かどうかという意味的不変条件を記載する。
  3. 計算量、イテレーターの無効化、所有権、および並行性の保証を記載する。
  4. 実行時の呼び出し側が守るか確認する必要がある事前条件と事後条件を記載する。

たとえば、キー述語として使用する等価関係は、アルゴリズムが受け入れる値に対して同値関係でなければなりません。その述語とともに使用するハッシュは、等しいキーに対して同じ結果を生成する必要があります。どちらの性質も requires 式では証明できません。

有効性も実行時の性質です。イテレーター型が構文的に std::input_iterator を満たしていても、特定のイテレーターがコンテナ操作によって無効化されている場合があります。型の要件と、既存のイテレーター、ポインター、または参照を無効化する操作の両方を文書化してください。

コンパイル時指定子には個別の契約がある #

constexpr は、呼び出しとオペランドが該当する規則を満たす場合に、エンティティまたは関数が定数評価に参加することを許可します。consteval は、即時関数の呼び出しに定数評価を要求します。constinit は静的またはスレッド記憶域期間を持つ変数に適用され、定数初期化を要求しますが、その変数を不変にしたり定数式で使用可能にしたりはしません。

cpp
constexpr int square(int value) { return value * value; }
consteval int checked_port(int value) { return value > 0 ? value : throw "port"; }
constinit int process_generation = 1;

これらの指定子は、交換可能な「コンパイル時」アノテーションとしてではなく、明記された保証のために使用してください。const オブジェクトは実行時に初期化できます。constinit オブジェクトは、const も指定されていない限り、後から変更できます。

実行時の振る舞いも契約の一部 #

適格なテンプレートのインスタンス化が、正しいプログラムを意味するわけではありません。割り当てが失敗する、比較が例外を送出する、コールバックが再入可能性の前提に違反する、範囲が空である、といった可能性があります。型システムで表現できない振る舞いを記載し、無効な入力または外部状態がプログラムへ入る境界でテストしてください。

計算量も同様に契約です。定数時間のルックアップを線形走査に置き換えても、必要な式はすべて維持できますが、その上限に依存するアルゴリズムを破壊する可能性があります。名前付き要件、コンセプト、および API 文書は相互補完するものとして扱ってください。制約は有用な診断を生成し、意味的な文書はジェネリックコードが依存できる振る舞いを定義します。

コメント

GitHub で閲覧・コメントする(投稿にはログインが必要です)