名前空間
変種
操作

属性: expects, ensures, assert (C++20)

提供: cppreference.com
< cpp‎ | language‎ | attributes
 
 
C++言語
一般的なトピック
フロー制御
条件付き実行文
繰り返し文 (ループ)
ジャンプ文
関数
関数宣言
ラムダ関数宣言
inline 指定子
例外指定 (C++20未満)
noexcept 指定子 (C++11)
例外
名前空間
指定子
decltype (C++11)
auto (C++11)
alignas (C++11)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点
文字 - 文字列 - nullptr (C++11)
ユーザ定義 (C++11)
ユーティリティ
属性 (C++11)
typedef 宣言
型エイリアス宣言 (C++11)
キャスト
暗黙の変換 - 明示的な変換
static_cast - dynamic_cast
const_cast - reinterpret_cast
メモリ確保
クラス
クラス固有の関数特性
特別なメンバ関数
テンプレート
その他
 
 
属性
(C++14)
(C++17)
(C++20)(C++20)
expectsensuresassert
(C++20)(C++20)(C++20)
 

関数に対する事前条件、事後条件、およびアサーションを指定します。

目次

[編集] 構文

[[ expects contract-level(オプション) : expression ]] (1) (C++20以上)
[[ ensures contract-level(オプション) identifier(オプション) : expression ]] (2) (C++20以上)
[[ assert contract-level(オプション) : expression ]] (3) (C++20以上)
contract-level - defaultaudit、 または axiom のいずれか。 デフォルトは default です。
identifier - 関数の戻り値を表すために選ばれた識別子。 contract-levelidentifier の間で何らかの曖昧性が生じた場合は contract-level が優先されます。
expression - 契約の述語を指定する文脈的に bool に変換可能な式。 最上段の演算子が代入またはコンマ演算子であってはなりません。

[編集] 説明

1) 事前条件、すなわち、関数に入る際の、その引数またはその他のオブジェクトの状態またはその両方に対する、関数の期待を定義します。 この属性は関数宣言の関数型に適用できます。 事前条件はその関数の本体 (コンストラクタのメンバ初期化子リストを含む) の評価を開始する直前にその述語を評価することによってチェックされます。 同じ関数の複数の事前条件は書かれている順にチェックされます。
2) 事後条件、すなわち、関数から出る際の、戻り値またはオブジェクトの状態またはその両方に対する、関数が保証するべき条件を定義します。 identifier (もし存在すれば) はその関数の結果の glvalue または結果の prvalue のオブジェクト (適切な方) を表します。 事後条件は関数の呼び出し元に制御を戻す直前 (ローカル変数および一時オブジェクトの生存期間が終了した後) にその述語を評価することによってチェックされます。 同じ関数の複数の事後条件は書かれている順にチェックされます。
3) アサーション、すなわち、関数の本体内のそれが現れた場所において満たされるべき条件を定義します。 これは空文に適用できます。 アサーションはその適用先の空文の評価の一部としてその述語を評価することによってチェックされます。

契約属性内の expression (文脈的に bool に変換される) はその述語と呼ばれます。 述語の評価は、生存期間がその評価内で始まり終わる非 volatile オブジェクトの変更以外、いかなる副作用も持ってはなりません。 そうでなければ動作は未定義です。 述語の評価が例外によって終了した場合は std::terminate が呼ばれます。

定数式の評価中は、チェックされる契約の述語のみが評価されます。 他のすべての文脈において、チェックされない契約の述語が評価されるかどうかは未規定です。 それが false に評価されるであろう場合、動作は未定義です。

[編集] 契約条件

事前条件と事後条件はまとめて契約条件と呼ばれます。 これらの属性は関数宣言の関数型に適用できます。

int f(int i) [[expects: i > 0]] [[ensures audit x: x < 1]]; 
 
int (*fp)(int i) [[expects: i > 0]]; // error: not a function declaration

関数の最初の宣言はその関数のすべての契約条件 (もしあれば) を指定しなければなりません。 以後の再宣言は契約条件を何も指定しないか同じ契約条件のリストを指定するかのいずれかでなければなりません。 対応する条件が常に同じ値に評価されるかどうか診断することは要求されません。 同じ関数が2つの異なる翻訳単位で宣言される場合、契約条件のリストは同じでなければなりません。 診断は要求されません。

2つの契約条件のリストは、それらが同じ契約条件を同じ順序で含む場合、同じです。 2つの契約条件は、それらが同じ種類の契約条件であり、同じ contract-level と同じ述語を持つ場合、同じです。 2つの述語は、関数およびテンプレートの引数および戻り値の識別子 (もしあれば) のリネームを除き、それらがもし関数定義内に現れたならば one-definiton rule を満たすであろう場合、同じです。

int f(int i) [[expects: i > 0]];
int f(int);                       // OK, redeclaration
int f(int j) [[expects: j > 0]];  // OK, redeclaration
int f(int k) [[expects: k > 1]];  // ill-formed
int f(int l) [[expects: 0 < l]];  // ill-formed, no diagnostic required

friend 宣言が翻訳単位内のその関数の最初の宣言であり、契約条件を持つ場合、その宣言は定義でなければならず、その翻訳単位内のその関数の唯一の宣言でなければなりません。

struct C {
   bool ok() const;
   friend void f(const C& c) [[ensures: c.ok()]]; // error, not a definition
   friend void g(C c) [[expects: c.ok()]] { } // OK
};
void g(C c); // error

契約条件の述語は、その適用先の関数の本体内の最初の式文として現れたかのような場合と同じ意味論上の制約を持ちます。

事後条件がその述語において引数を ODR 使用し、関数の本体がその引数の値を直接的または間接的に変更する場合、動作は未定義です。

int f(int x) [[ensures r: r == x]]
{
  return ++x; // undefined behavior
}
int g(int* p) [[ensures: p != nullptr]]
{
  *p = 42; // OK, p is not modified
}
 
bool meow(const int&) { return true; }
 
void h(int x) [[ensures: meow(x)]] 
{
  ++x;  // undefined behavior
}
 
void i(int& x) [[ensures: meow(x)]]
{
  ++x;  // OK; the "value" of a reference is its referent and cannot be modified
}

推定戻り値型を持つテンプレート化された関数の場合、戻り値は追加の制約なしに事後条件で表しても構いません (ただし戻り値の名前は依存型を持つものとして扱われます)。 推定戻り値型を持つ非テンプレート化された関数の場合、宣言で戻り値を表すことは禁止されています (が、定義では許されています)。

auto h(int x) [[ensures res: true]]; // error: return value with deduced type
                                     // on a non-template function declaration

[編集] ビルドレベルおよび違反の処理

プログラムは3つのビルドレベルのいずれかで翻訳される可能性があります。

  • off: 契約のチェックは行われません。
  • default (ビルドレベルが選択されない場合のデフォルト): contract-leveldefault の契約に対してチェックが行われます。
  • audit: contract-leveldefault または audit の契約に対してチェックが行われます。

ビルドレベル選択のための仕組みは処理系定義です。 異なるビルドレベルで翻訳された翻訳単位の結合は条件付きでサポートされます。

プログラムの違反ハンドラは処理系定義の方法によって指定される void (const std::contract_violation &) (オプションで noexcept) 型の関数です。 これはチェックされる契約の述語が false に評価されたときに呼ばれます。

  • 事前条件に違反した場合、 std::contract_violation 引数に反映されるソースの位置は処理系定義です。
  • 事後条件に違反した場合、 std::contract_violation 引数に反映されるソースの位置は関数定義のソースの位置です。
  • アサーションに違反した場合、 std::contract_violation 引数に反映されるソースの位置はアサーションの適用先の文のソースの位置です。

それ以外の場合、違反ハンドラに渡される std::contract_violation 引数の値は処理系定義です。

違反ハンドラが例外を投げることによって終了し、例外を投げない例外指定を持つ関数の呼び出しに対する契約に違反した場合、 std::terminate が呼ばれます。

 
void f(int x) noexcept [[expects: x > 0]];
void g() {
    f(0); // terminate if the violation handler throws
}

プログラムは2つの違反継続モードのいずれかで翻訳される可能性があります。

  • off (継続モードが選択されない場合のデフォルト): 違反ハンドラの実行完了後、 std::terminate が呼ばれます。
  • on: 違反ハンドラの実行完了後、実行は普通に継続されます。

処理系はビルドレベルを取得、設定、変更、または違反ハンドラを設定、変更するプログラム的な方法を提供しないことが推奨されます。