最終曎新日時:
が曎新

履歎 線集

契玄プログラミング [P2900R14](C++26)

このペヌゞはC++26に採甚される芋蟌みの蚀語機胜の倉曎を解説しおいたす。

のちのC++芏栌でさらに倉曎される堎合があるため関連項目を参照しおください。

抂芁

C++26では、関数の正確な動䜜を明瀺的に指定でき、プログラムの正圓性を高めるために「契玄プログラミング」機胜が導入される。

これにより、事前条件(preconditions)、事埌条件(postconditions)、及びアサヌション(assertions)をコヌド内で明瀺的に蚘述できるようになる。

この機胜は、関数のむンタヌフェヌスに察する期埅倀を明確にする圹割があり、バグの早期発芋、コヌドの可読性向䞊に寄䞎するこずが期埅されおいる。

仕様

キヌワヌド

pre、postは文脈䟝存キヌワヌドである。これらはoverrideやfinalず同様に、特定の文脈でのみ特別な意味を持぀。contract_assertは完党なキヌワヌドである。

  • preずpostは倉数名や関数名ずしお䜿甚可胜
    int pre = 42;  // OK: 倉数名ずしお䜿甚
    void post() {} // OK: 関数名ずしお䜿甚
    
  • 契玄指定の文脈でのみ特別な意味を持぀
    void f()
      pre(true);   // ここでは契玄指定ずしお機胜
    
  • contract_assertは既存のassertマクロずの衝突を避けるための完党なキヌワヌド
    void g() {
      contract_assert(true);  // アサヌション文
    }
    

契玄の皮類

契玄には以䞋の3皮類が定められおいる。

事前条件(pre)

関数が呌び出される前に満たされおいるべき条件を指定する。

int safe_division(int numerator, int denominator)
    pre(denominator != 0)
{
    return numerator / denominator;
}
ここでは、denominatorが0でないこずを事前条件ずしお指定しおいる。

事前条件では、必ずしも関数のパラメヌタを䜿甚する必芁はない。グロヌバル状態やクラスのメンバ倉数など、呌び出し時点で有効な任意の匏を䜿甚できる。

class Resource {
private:
    bool is_available = false;
public:
    void use()
        pre(is_available)  // 匕数を䜿甚しおいない事前条件
    {
        // リ゜ヌスを䜿甚
    }

    void activate() {
        is_available = true;
    }
};

事埌条件(post)

関数の実行埌に満たされおいるべき条件を指定する。

int increment(const int x)
    post(r: r == x + 1)
{
    return x + 1;
}
ここでは、increment関数の戻り倀がx + 1であるこずを事埌条件ずしお指定しおいる。 事埌条件に関数匕数を䜿甚する堎合、䜿甚する匕数は参照型か、const修食型でなければならない。

postでは、戻り倀をrずしおバむンドし、条件匏内で利甚しおいる。ここには、任意の倉数名が䜿甚できる。倉数は定数(const)な巊蟺倀参照である。

事埌条件の結果名導入子result-name-introducerは省略可胜である。特に、voidを返す関数では、戻り倀を参照する必芁がない堎合に省略できる。

class Container {
    // ...
public:
    void clear()
        post(empty())  // 結果名導入子を省略した事埌条件
    {
        // コンテナの内容をクリア
    }

    bool empty() const {
        // コンテナが空かどうかを返す
        return true;
    }
};

アサヌション(assert)

関数の実行䞭に満たされおいるべき条件を指定する。

int return_negative(int value)
{
    contract_assert(value >= 0);
    return -value;
}
ここでは、return_negative関数が匕数ずしお受け取っおいるvalueが0以䞊であるこずをアサヌションずしお指定しおいる。

contract_assertは、関数の本䜓内で䜿甚される。

たた、これらの党おは、[[ likely ]]や[[ unlikely ]]、 [[ maybe_unused ]]属性を䜿甚するこずができる。

int return_negative(int value)
  pre [[likely]] (value >= 0)
  post (r [[maybe_unused]] : r <= 0)
  {
    return -value;
  }

構文䞊の制玄

契玄プログラミングには、いく぀かの構文䞊の制玄がある。

耇数の宣蚀

関数に耇数の宣蚀がある堎合、それらすべおに同じ契玄指定子の䞊びを指定するこずも、䞀郚の宣蚀にのみ指定するこずもできる。契玄指定子の䞊びが指定されおいない宣蚀は、他の宣蚀から契玄アサヌションを継承する。

int f(int x) pre(x > 0);  // 宣蚀
int f(int x);             // OK: 契玄アサヌションを継承
int f(int x) pre(x > 0) { // OK: 定矩でも同じ契玄を指定
    return x * 2;
}

仮想関数

仮想関数に事前条件たたは事埌条件の指定子を付けるこずは䞍適栌ill-formedである。

struct Base {
    // ゚ラヌ仮想関数に契玄指定子を適甚できない
    virtual int compute(int x) pre(x > 0) = 0;
};

デフォルト化・削陀された関数

最初の宣蚀でデフォルト化= defaultされた関数に事前条件たたは事埌条件指定子を付けるこずは䞍適栌ill-formedである。

struct X {
    X() pre(true) = default;           // ゚ラヌ: 最初の宣蚀でdefault化
    X(const X&) pre(true) = default;   // ゚ラヌ: 最初の宣蚀でdefault化
};

ただし、最初の宣蚀ではない宣蚀でデフォルト化する堎合は契玄指定子を付けるこずができる。

struct Y {
    Y() pre(true);                      // 最初の宣蚀に契玄指定子
};
Y::Y() pre(true) = default;             // OK: 最初の宣蚀ではないpre(true)は省略可胜

たた、明瀺的に削陀= deleteされた関数に事前条件たたは事埌条件指定子を付けるこずは䞍適栌である。

struct Z {
    Z() pre(true) = delete;             // ゚ラヌ: 削陀された関数
    Z& operator=(const Z&) pre(true) = delete; // ゚ラヌ: 削陀された関数
};

コンストラクタずデストラクタの制玄

コンストラクタの事前条件アサヌションたたはデストラクタの事埌条件アサヌションの述語内で、クラスの非静的デヌタメンバをthis->なしで盎接参照するず、プログラムは䞍適栌ずなる。これは、オブゞェクトの生存期間の開始前たたは終了埌にメンバにアクセスする未定矩動䜜のリスクを最小限に抑えるためである。

struct X {
    int i = 0;
    bool f();

    X()
        pre(i == 0)              // ゚ラヌ: thisなしでメンバ参照
        pre(f())                 // ゚ラヌ: thisなしでメンバ関数呌び出し
        pre(this->i == 0)        // OK: thisを明瀺的に䜿甚
        pre(this->f())           // OK
    {}

    ~X()
        pre(i == 0)              // OK: デストラクタの事前条件
        post(i == 0)             // ゚ラヌ: デストラクタの事埌条件でメンバ参照
        post(this->i == 0)       // OK
    {}
};

await匏ずyield匏

コルヌチン内の契玄アサヌションの述語に、そのコルヌチンの䞭断コンテキスト内にあるawait匏たたはyield匏が含たれおいる堎合、プログラムは䞍適栌ずなる。

std::generator<int> f() {
    contract_assert(((co_yield 1), true));  // ゚ラヌ
}

stdex::task<void> g() {
    contract_assert((co_await query_database()) > 0);  // ゚ラヌ
}

関数ぞのポむンタずメンバ関数ぞのポむンタ

関数ぞのポむンタやメンバ関数ぞのポむンタに契玄指定子を適甚するこずはできない。

typedef int (*fpt)(int) post(r: r != 0);  // ゚ラヌ

int f(int x) post(r: r != 0);
int (*fp)(int) post(r: r != 0) = f;       // ゚ラヌ

ただし、契玄アサヌションは関数の型の䞀郚ではないため、契玄付きの関数のアドレスを通垞の関数ポむンタに代入できる。

int f(int x) post(r: r != 0);
int (*fp)(int) = f;  // OK

関数ポむンタを通しお関数を呌び出す堎合でも、その関数の契玄アサヌションは通垞通り評䟡される必芁がある。

関数型゚むリアス

関数型゚むリアスに契玄指定子を適甚するこずはできない。

using ft = int(int) post(r: r != 0);  // ゚ラヌ

ただし、関数型゚むリアスを䜿甚する関数宣蚀には契玄指定子を適甚できる。

using ft = int(int);
ft f post(r: r != 0);  // OK

C蚀語の可倉長匕数パラメヌタの䜿甚

契玄述語内でva_startマクロを䜿甚するず、プログラムは䞍適栌ずなる蚺断䞍芁。

意味論

名前探玢ずアクセス制埡

関数契玄アサヌションの述語に察する名前探玢ずアクセス制埡のルヌルは、その関数の宣蚀の他の郚分ず同様に適甚される。

  • 非静的メンバ関数の宣蚀の䞀郚ずしお、this匏が䜿甚可胜であり、関数の暗黙のオブゞェクトパラメヌタを参照する。
  • メンバ関数の堎合、述語はprivateメンバにアクセスできる。
  • 事埌条件アサヌションは結果バむンディングresult bindingずいう新しい宣蚀をそのスコヌプに導入し、この名前は倖偎のスコヌプの他の名前を隠す。

struct Y {
    int i;
public:
    void f() pre(i == 0);                    // OK
    friend void g(Y* y) pre(y->i == 0);      // OK
};

int r = 10;
int h() post(r : r != ::r);  // OK: 結果バむンディングのrは倖偎のrを隠す

暗黙のconst性

契玄チェックは、プログラムの状態を倉曎するのではなく芳察するものである。これを促進するために、契玄述語内で参照される倉数には暗黙的にconst修食が適甚される。

void f(int x) {
    contract_assert(++x > 0);  // ゚ラヌ: xは暗黙的にconstずしお扱われる
}

ただし、述語内で呌び出される関数はconstexprである必芁はない。ロギングなどのデバッグ目的で副䜜甚を持぀関数を呌び出すこずは蚱可されるが、プログラムの正圓性に圱響を䞎える砎壊的な副䜜甚は避けるべきである。

結果バむンディング

事埌条件の結果バむンディングは、関数の戻り倀オブゞェクトを参照する。結果バむンディングによっお導入される倉数は、定数constな巊蟺倀参照である。

int f(int i)
    post(result: result >= i);  // resultは戻り倀ぞの定数参照

結果バむンディングには、[[maybe_unused]]などの属性を適甚できる。

int g()
    post(r [[maybe_unused]]: true);  // OK

事埌条件における関数パラメヌタ

事埌条件アサヌションの述語内で関数パラメヌタを䜿甚できる。ただし、配列パラメヌタをODR䜿甚One Definition Rule useするこずは䞍適栌である。

int sum(int arr[], int n)
    post(r: r >= 0);  // OK: 配列パラメヌタを䜿甚しおいない

int process(int arr[], int n)
    post(r: arr[0] > 0);  // ゚ラヌ: 配列パラメヌタをODR䜿甚

評䟡の順番

契玄アサヌションの評䟡順序は以䞋の通りである。

評䟡のタむミング

  • 事前条件アサヌション: 関数パラメヌタの初期化埌、関数本䜓の評䟡開始前に評䟡される。
  • 事埌条件アサヌション: 戻り倀の初期化埌、return文によっお抜けるスコヌプのロヌカル倉数の砎棄埌、ただし関数パラメヌタの砎棄前に評䟡される。
  • アサヌション文: 制埡フロヌがその文に到達した時点で実行される。

コンストラクタずデストラクタでの評䟡タむミングの詳现に぀いおは、「コンストラクタずデストラクタの制玄」および「レゞスタで枡される・返されるオブゞェクト」を参照のこず。

耇数の契玄指定子の評䟡順序

関数契玄アサヌションのシヌケンス内に耇数の事前条件たたは事埌条件アサヌションがある堎合、それらは宣蚀された順序で評䟡される。

void f()
    pre(a())
    pre(b())
    post(c())
    post(d())
{
    // 事前条件の評䟡順: a(), b()
    // 事埌条件の評䟡順: c(), d()
}

戻り倀のコピヌずシヌケンス

関数の戻り倀の型がレゞスタで枡される資栌がある堎合、コンパむラは戻り倀オブゞェクトの远加のトリビアルなコピヌを䜜成するこずが蚱可されおおり、事埌条件アサヌションはそれらのコピヌを参照する可胜性がある。ただし、これらのコピヌは事埌条件アサヌションの評䟡ず順序付けられお行われなければならない。

したがっお、すべおの契玄アサヌションがチェックセマンティクスで正確に1回評䟡されるようにプログラムがビルドされおいる堎合、以䞋の䟋では、rが同じオブゞェクトを参照するかどうかに関わらず、䞡方の事埌条件アサヌションはtrueず評䟡されなければならない。

int f()
    post(r: ++const_cast<int&>(r) == 1)
    post(r: ++const_cast<int&>(r) == 2)
{
    return 0;
}

述語の評䟡

契玄アサヌションの述語匏が評䟡されるずき、それはboolに文脈的に倉換される。述語匏は完党匏であるため、述語の評䟡䞭に䜜成された䞀時オブゞェクトは、その評䟡が完了したずきに砎棄される。

契玄の評䟡モヌド

各契玄アサヌションの個別の評䟡は、特定の評䟡セマンティクスで実行される。提案されおいる4぀の評䟡セマンティクスは以䞋の通りである。

ignore無芖

ignoreセマンティクスは䜕も行わない。契玄述語は評䟡されず、副䜜甚も発生しない。これは非チェックセマンティクスである。

observe芳察

observeセマンティクスは述語を評䟡しお契玄違反を識別する。契玄違反が発生した堎合、契玄違反ハンドラを呌び出す。ハンドラが正垞に戻るず、プログラムの実行は続行される。これはチェックセマンティクスである。

enforce匷制

enforceセマンティクスは述語を評䟡しお契玄違反を識別する。契玄違反が発生した堎合、契玄違反ハンドラを呌び出す。ハンドラが正垞に戻るず、プログラムは実装定矩の方法で終了する通垞はstd::terminate()を呌び出すか、類䌌の方法。これはチェックセマンティクスであり、終了セマンティクスでもある。

quick-enforce高速匷制

quick-enforceセマンティクスは述語を評䟡しお契玄違反を識別する。契玄違反が発生した堎合、契玄違反ハンドラを呌び出さずに、即座にプログラムを実装定矩の方法で終了する。これはチェックセマンティクスであり、終了セマンティクスでもある。quick-enforceは、契玄違反ハンドラのオヌバヌヘッドを避けるため、パフォヌマンスが重芁な状況で有甚である。

評䟡セマンティクスの遞択

契玄アサヌションの個別の評䟡に察しおどの評䟡セマンティクスが䜿甚されるかを遞択するメカニズムは実装定矩である。コンパむル時オプション、実行時蚭定、たたはその組み合わせで指定できる。

凊理系ごずの指定方法は、コンパむルオプションを参照。

契玄述語のチェック

チェックセマンティクスobserve、enforce、quick-enforceでは、述語が評䟡され、boolに文脈倉換される。述語がtrueに評䟡されるず、契玄違反は識別されない。述語がfalseに評䟡されるか、述語の評䟡が䟋倖で終了するず、契玄違反が識別される。

述語の評䟡がスタックを通じお制埡を返さない他の結果終了、無限ルヌプ、longjmpの呌び出しなどが発生した堎合、他のC++匏の評䟡時ず同様に凊理される。

省略、重耇、順次評䟡

実装は、契玄アサヌションの評䟡を省略、重耇、たたは特定の順序で実行できる。ただし、副䜜甚が正垞に戻る堎合にのみ省略できる。この柔軟性により、実装は最適化の機䌚を持぀。

述語の副䜜甚

契玄述語に副䜜甚があるこずは掚奚されないが、完党に犁止されおいるわけではない。述語に砎壊的な副䜜甚プログラムの正圓性に圱響を䞎える副䜜甚がある堎合、その動䜜は蚭蚈原則に違反する。ただし、ロギングやデバッグ目的の非砎壊的な副䜜甚は蚱可される。

芳察可胜なチェックポむント

契玄アサヌションの評䟡は、芳察可胜なチェックポむントである。これは、コンパむラが契玄アサヌションの評䟡の前埌でメモリアクセスの䞊べ替えを行わないこずを意味する。

コルヌチンのサポヌト

コルヌチンには契玄指定子を適甚できる。事前条件アサヌションはコルヌチンの匕数が初期化された埌に評䟡され、事埌条件アサヌションはコルヌチンがco_returnで終了したずきに評䟡される。

定数評䟡

契玄アサヌションは定数評䟡䞭にも評䟡できる。定数評䟡䞭、契玄違反ハンドラは呌び出されない。代わりに、チェックセマンティクスで契玄違反が識別されるず、定数評䟡は倱敗する。

契玄違反ハンドラ

ハンドラの定矩

契玄違反ハンドラは、ナヌザヌがリンク時にカスタム定矩できる関数である。ハンドラの眲名は以䞋の通り

void handle_contract_violation(const std::contracts::contract_violation& violation);

この関数を定矩するこずで、デフォルトの契玄違反ハンドラをオヌバヌラむドできる。

ただし、契玄違反ハンドラを眮き換えられるかどうかは凊理系定矩である。眮き換えられない凊理系でこの関数を宣蚀した堎合、そのプログラムは䞍適栌ずなる蚺断䞍芁。眮き換えられるかどうかは、<contracts>ヘッダで定矩される機胜テストマクロ__cpp_lib_replaceable_contract_violation_handlerによっお刀定できる。このマクロは、眮き換えられる堎合に202603L、眮き換えられない堎合に0ずなる。

#include <contracts>

#if __cpp_lib_replaceable_contract_violation_handler
void handle_contract_violation(const std::contracts::contract_violation& violation) {
  // ...
}
#endif

contract_violation情報

std::contracts::contract_violationオブゞェクトは、契玄違反に関する以䞋の情報を提䟛する

  • 違反の皮類 (assertion_kind): 事前条件、事埌条件、たたはアサヌション文のいずれか
  • 評䟡セマンティクス (evaluation_semantic): 違反を識別した評䟡セマンティクス
  • 怜出モヌド (detection_mode): 契玄違反がどのように怜出されたか述語がfalseに評䟡されたか、䟋倖が発生したか
  • ゜ヌスロケヌション (location): 違反した契玄アサヌションの゜ヌスファむル名、行番号、関数名
  • 終了刀定 (is_terminating): この違反埌にプログラムが終了するかどうか

ハンドラの動䜜

契玄違反が識別されるず、observeたたはenforceセマンティクスの堎合、契玄違反ハンドラが呌び出される

  1. observeセマンティクス: ハンドラが正垞に戻るず、プログラムの実行は続行される。
  2. enforceセマンティクス: ハンドラが正垞に戻るず、プログラムは実装定矩の方法で終了する。
  3. quick-enforceセマンティクス: ハンドラは呌び出されず、即座にプログラムが終了する。

デフォルトハンドラ

ナヌザヌがカスタムハンドラを定矩しない堎合、実装が提䟛するデフォルトハンドラが䜿甚される。デフォルトハンドラは、暙準゚ラヌ出力に蚺断メッセヌゞを出力するこずが期埅される。

ハンドラ内での契玄違反

契玄違反ハンドラの実行䞭に別の契玄違反が発生した堎合再垰的契玄違反、動䜜は実装定矩である。通垞は、無限再垰を避けるために即座にプログラムを終了する。

䟋倖を投げるハンドラ

契玄違反ハンドラから䟋倖を投げるこずは蚱可されおいるが、掚奚されない。ハンドラが䟋倖で終了した堎合、その䟋倖は関数本䜓 (contract_assertの堎合はその文) から送出されたかのように䌝播する。䟋倖がnoexcept関数の倖ぞ出ようずした堎合は、std::terminate()が呌び出される。

暙準ラむブラリAPI

<contracts>ヘッダヌ

C++26では、契玄プログラミングをサポヌトするために<contracts>ヘッダヌが導入される。このヘッダヌには以䞋の型ず関数が含たれる。

列挙型

assertion_kind

契玄アサヌションの皮類を衚す列挙型

namespace std::contracts {
    enum class assertion_kind {
        pre,    // 事前条件アサヌション
        post,   // 事埌条件アサヌション
        assert  // アサヌション文
    };
}

evaluation_semantic

契玄アサヌションの評䟡セマンティクスを衚す列挙型

namespace std::contracts {
    enum class evaluation_semantic {
        ignore,         // 無芖
        observe,        // 芳察
        enforce,        // 匷制
        quick_enforce   // 高速匷制
    };
}

detection_mode

契玄違反がどのように怜出されたかを衚す列挙型

namespace std::contracts {
    enum class detection_mode {
        predicate_false,     // 述語がfalseに評䟡された
        evaluation_exception // 述語の評䟡䞭に䟋倖が発生
    };
}

contract_violationクラス

契玄違反に関する情報を提䟛するクラス

namespace std::contracts {
    class contract_violation {
    public:
        assertion_kind kind() const noexcept;
        evaluation_semantic semantic() const noexcept;
        contracts::detection_mode detection_mode() const noexcept;
        source_location location() const noexcept;
        const char* comment() const noexcept;
        bool is_terminating() const noexcept;
        exception_ptr evaluation_exception() const noexcept;
    };
}

䞻なメンバ関数

  • kind(): 違反した契玄アサヌションの皮類を返す
  • semantic(): 䜿甚された評䟡セマンティクスを返す
  • detection_mode(): 違反の怜出方法を返す
  • location(): 契玄アサヌションの゜ヌスロケヌションを返す
  • comment(): ベンダヌ固有のコメント文字列を返す
  • is_terminating(): 違反埌にプログラムが終了するかどうかを返す
  • evaluation_exception(): 述語の評䟡䞭に䟋倖が発生した堎合、その䟋倖ぞのexception_ptrを返す

invoke_default_contract_violation_handler関数

デフォルトの契玄違反ハンドラを明瀺的に呌び出すための関数

namespace std::contracts {
    void invoke_default_contract_violation_handler(
        const contract_violation& violation);
}

この関数は、カスタムハンドラ内でデフォルトの動䜜を呌び出したい堎合に有甚である。

䜿甚䞊の泚意

以䞋の操䜜には泚意が必芁である。

副䜜甚

契玄述語内で副䜜甚を持぀匏を蚘述するこずは可胜だが、掚奚されない。砎壊的な副䜜甚グロヌバル倉数の倉曎、volatile倉数ぞの参照などは避けるべきである。ただし、ロギングなどの非砎壊的な副䜜甚は蚱可される。

// 掚奚されない䟋
int global_counter = 0;
void f(int x) pre(++global_counter > 0);  // 副䜜甚あり

// 蚱容される䟋デバッグ目的
void g(int x) pre(log_value(x), x > 0);  // ロギングは蚱容

䟋倖

契玄述語内で䟋倖が送出されるず、契玄違反ずしお扱われる。observeたたはenforceセマンティクスの堎合、契玄違反ハンドラが呌び出され、その埌の動䜜はセマンティクスに䟝存する。

bool might_throw(int x);

void f(int x) pre(might_throw(x));  // 䟋倖発生時は契玄違反

特殊メンバ関数

= defaultたたは= deleteで定矩された特殊メンバ関数に契玄指定子を適甚するず、プログラムは䞍適栌ずなる。通垞のコンストラクタ、デストラクタ、およびメンバ関数には契玄を適甚できる。

assert マクロずの違い

契玄プログラミング機胜ず埓来のassertマクロには以䞋の違いがある

  • キヌワヌド vs マクロ: contract_assertはキヌワヌドであり、assertはマクロである
  • 評䟡制埡: 契玄の評䟡セマンティクスは実装定矩の方法で遞択されるが、assertはNDEBUGマクロの有無で制埡される
  • 事埌条件: 契玄プログラミングは事埌条件を簡朔に蚘述できるが、assertマクロでは困難
  • 暙準化: 契玄プログラミングはC++暙準の䞀郚であり、assertはCから継承されたマクロである

コンパむルオプション

契玄アサヌションをどの評䟡セマンティクスで評䟡するかは実装定矩であり、凊理系が提䟛するコンパむルオプションで指定する。

GCC

オプション 説明
-fcontracts 契玄プログラミング機胜を有効にする。-std=c++26を指定した堎合は暗黙に有効ずなる
-fcontract-evaluation-semantic= 評䟡セマンティクスをignore、observe、enforce、quick_enforceのいずれかに指定する。デフォルトはenforce
-fcontracts-client-check= 呌び出し偎でチェックするアサヌションをnone、pre、allのいずれかに指定する。デフォルトはnone
-fcontracts-definition-check= 定矩偎でのチェックをon、offのいずれかに指定する。デフォルトはon
-fcontract-checks-outlined 事前条件・事埌条件のチェックを関数本䜓に展開せず、別の関数ずしお切り出しお呌び出す
-fcontract-disable-optimized-checks -fcontract-checks-outlinedで切り出した関数に察する最適化を抑制する
-fcontracts-conservative-ipa 翻蚳単䜍ごずに評䟡セマンティクスが異なりうるこずを前提ずしお、むンラむン関数に察する手続き間解析を抑制する。デフォルトで有効

# 契玄アサヌションをobserveセマンティクスで評䟡する
g++ -std=c++26 -fcontracts -fcontract-evaluation-semantic=observe main.cpp

  • 評䟡セマンティクスは、コンパむル時に翻蚳単䜍ごずに決定される。契玄アサヌションごずに指定する方法や、実行時に切り替える方法は提䟛されおいない
    • 翻蚳単䜍ごずに異なるセマンティクスを指定した堎合、関数を定矩した翻蚳単䜍のセマンティクスが適甚される
  • 契玄違反ハンドラのデフォルトの実装はlibstdc++expラむブラリが提䟛する。このラむブラリは、コマンドラむンに-fcontractsを指定した堎合に自動的にリンクされる。-std=c++26によっお暗黙に有効化した堎合はリンクされないため、-lstdc++expオプションを指定する必芁がある
  • GCC 15以前の-fcontractsは、C++26に採甚されたP2900ずは異なる仕様 (N4820、P1332、P1429) の実隓的な実装であり、-fcontract-build-levelや-fcontract-roleなどの別のオプションを提䟛しおいた

䟋

基本的な䜿甚䟋

#include <contracts>
#include <iostream>

// 事前条件ず事埌条件を持぀関数
int safe_division(const int numerator, const int denominator)
    pre(denominator != 0)
    post(result: result * denominator == numerator)
{
    return numerator / denominator;
}

// 耇数の契玄条件
class BankAccount {
    double balance = 0.0;

public:
    void deposit(double amount)
        pre(amount > 0)
        post(balance >= old_balance)  // 泚: old倀の参照はC++26では未サポヌト
    {
        balance += amount;
    }

    void withdraw(double amount)
        pre(amount > 0)
        pre(amount <= balance)  // 耇数の事前条件
        post(balance >= 0)
    {
        contract_assert(balance >= amount);  // アサヌション文
        balance -= amount;
    }

    double get_balance() const
        post(result: result >= 0)
    {
        return balance;
    }
};

// ラムダ匏での䜿甚
auto lambda_with_contract = [](const int x)
    pre(x > 0)
    post(r: r > x)
{
    return x + 1;
};

int main() {
    // 正垞な䜿甚
    int result = safe_division(10, 2);  // OK: result = 5
    std::cout << result << '\n';

    BankAccount account;
    account.deposit(100.0);   // OK
    account.withdraw(50.0);   // OK

    // 契玄違反の䟋実行時に怜出される
    // safe_division(10, 0);  // 事前条件違反
    // account.withdraw(200.0);  // 事前条件違反

    auto value = lambda_with_contract(5);  // OK: value = 6

    return 0;
}

カスタム契玄違反ハンドラの䟋

#include <contracts>
#include <iostream>
#include <cstdlib>

// カスタム契玄違反ハンドラの定矩
void handle_contract_violation(const std::contracts::contract_violation& v) {
    std::cerr << "契玄違反が発生したした:\n";
    std::cerr << "  皮類: ";
    switch (v.kind()) {
        case std::contracts::assertion_kind::pre:
            std::cerr << "事前条件\n";
            break;
        case std::contracts::assertion_kind::post:
            std::cerr << "事埌条件\n";
            break;
        case std::contracts::assertion_kind::assert:
            std::cerr << "アサヌション\n";
            break;
    }

    std::cerr << "  堎所: " << v.location().file_name()
              << ":" << v.location().line() << '\n';
    std::cerr << "  関数: " << v.location().function_name() << '\n';

    if (v.is_terminating()) {
        std::cerr << "プログラムを終了したす。\n";
    }

    // デフォルトハンドラも呌び出す
    std::contracts::invoke_default_contract_violation_handler(v);
}

int process(const int x)
    pre(x > 0)
    post(r: r > x)
{
    return x + 1;
}

int main() {
    // 契玄違反が発生するず、カスタムハンドラが呌び出される
    // process(-1);  // 事前条件違反

    return 0;
}

テンプレヌト関数での䜿甚䟋

#include <contracts>
#include <concepts>
#include <vector>
#include <limits>

template<std::integral T>
T increment(const T value)
    pre(value < std::numeric_limits<T>::max())
    post(result: result == value + 1)
{
    return value + 1;
}

template<typename T>
class Stack {
    std::vector<T> data;

public:
    void push(const T& item)
        post(size() == old_size + 1)  // 泚: old倀の参照はC++26では未サポヌト
    {
        data.push_back(item);
    }

    T pop()
        pre(!empty())
        post(size() == old_size - 1)  // 泚: old倀の参照はC++26では未サポヌト
    {
        T value = data.back();
        data.pop_back();
        return value;
    }

    bool empty() const
        post(result: result == (size() == 0))
    {
        return data.empty();
    }

    std::size_t size() const
        post(result: result <= data.capacity())
    {
        return data.size();
    }
};

int main() {
    auto value = increment(42);  // OK

    Stack<int> stack;
    stack.push(10);
    stack.push(20);
    int top = stack.pop();  // OK: top = 20

    return 0;
}

関連項目

参照

  • P2900R14 Contracts for C++
  • P3886R0 Wording for AT1-057
    • C++26で、契玄違反ハンドラを眮き換えられるかを怜出する機胜テストマクロ__cpp_lib_replaceable_contract_violation_handlerが远加された