{"article":{"slug":"c-26-contracts-introduction-to-preconditions-and-postconditions","title":"C++26 Contracts: Introduction to Preconditions and Postconditions","subtitle":null,"summary":"C++ Stories introduces C++26 contracts: pre() and post() on function declarations and contract_assert() inside functions, why they beat assert, the ignore/observe/enforce/quick-enforce evaluation semantics, compiling examples with GCC, const parameters in postconditions, avoiding side effects in predicates, predicate cost, and why contracts are not a replacement for input validation.","content_type":"tutorial","language":"en","canonical_url":"https://www.cppstories.com/2026/cpp26_contracts/","author":{"name":"Bartlomiej Filipek","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"C++ Stories","url":"https://www.cppstories.com/","listing_slug":null,"listing":null},"topics":[{"name":"C++","slug":"c","url":"https://listedarticles.com/topics/c"},{"name":"Programming Languages","slug":"programming-languages","url":"https://listedarticles.com/topics/programming-languages"},{"name":"Software Safety","slug":"software-safety","url":"https://listedarticles.com/topics/software-safety"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":2052,"reading_minutes":9,"published_at":"2026-10-11T00:00:00.000Z","added_at":"2026-10-15T14:18:54.113Z","updated_at":"2026-10-15T14:18:54.113Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/c-26-contracts-introduction-to-preconditions-and-postconditions","markdown_url":"https://listedarticles.com/articles/c-26-contracts-introduction-to-preconditions-and-postconditions.md","example":false,"citation":"Bartlomiej Filipek, C++ Stories. \"C++26 Contracts: Introduction to Preconditions and Postconditions.\" 11 Oct 2026. https://www.cppstories.com/2026/cpp26_contracts/ (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://www.cppstories.com/2026/cpp26_contracts/"},"body_markdown":"In the article about [Standard Library hardening](https://www.cppstories.com/2026/hardening-experiments/), we looked at checks for invalid Standard Library container access and other broken preconditions. I mentioned that those requirements are expressed in terms of “contracts”. But what about our code?\n\nIn this post, we’ll explore contracts from C++26. We’ll start with a simple `cassert`, move the requirement into the function itself, and see how to check results. We’ll also look at declarations and definitions, compiler options, and a few details that can surprise you.\n\n## Starting with an Assertion\n\nLet’s start with a small example:\n\nA header file first:\n\n```\n// foo.h\n#include <vector>\nint get_value(const std::vector<int>& values, std::size_t index);\n```\n\nAnd the implementation:\n\n```\n// foo.cpp\n#include \"foo.h\" // our function declaration...\n#include <cassert>\n\nint get_value(const std::vector<int>& values, std::size_t index) {\n    assert(index < values.size());\n    return values[index];\n}\n```\n\nThe function expects a valid index. If the condition is false, `assert` prints a diagnostic message and terminates the program. However, this only happens when assertions are enabled. Defining `NDEBUG` disables the check and basically removes the check from the code.\n\nWhat are the issues with this simple approach? The requirement lives inside the implementation. Somebody reading only the declaration sees a vector and an index, without the relationship between them.\n\n## Adding a Precondition\n\nWith C++26 contracts, we can move that requirement from an assertion inside the function body to the function itself:\n\n```\nint get_value(const std::vector<int>& values, std::size_t index)\n    pre(index < values.size())\n{\n    return values[index];\n}\n```\n\nNotice the new `pre(...)` syntax after the parameter list. It introduces a *precondition assertion*: a condition expected to hold when entering the function.\n\nWhat’s more, we can add this precondition to the declaration:\n\n```\nint get_value(const std::vector<int>& values, std::size_t index)\n    pre(index < values.size());\n```\n\nThe feature was introduced through [P2900, Contracts for C++](https://wg21.link//p2900).\n\nLet’s understand how it works and how it differs from `assert`.\n\n### Why Use Contracts Instead of `assert`?\n\nAt first, `pre(index < values.size())` might look like another way to write `assert(index < values.size())`. Both express a condition that should be true. So what’s the benefit of using contracts?\n\nThere are a few important differences:\n\n- Requirements in declarations: We can put `pre` and `post` directly in the function declaration. This makes the requirements visible to anyone reading the API, without looking at the implementation.\n- More flexible checking: Traditional `assert` is enabled or disabled through `NDEBUG`. Contracts define several evaluation semantics, including ignoring a check, reporting a violation and continuing, or terminating the program. The compiler implementation determines which semantics are available and how they can be selected.\n- Preconditions and postconditions: Contracts distinguish between what the caller must provide and what the function promises to return. With `assert`, we would have to place checks manually inside the function body, including checks before different return statements.\n\nThis also makes contracts useful for documenting APIs, even when runtime checking is disabled.\n\n## A Complete Precondition Example\n\nHere’s a complete example (no header file for simplicity for now):\n\n```\n#include <print>\n#include <vector>\n\nint get_value(const std::vector<int>& values, std::size_t index)\n    pre(index < values.size())\n{\n    return values[index];\n}\n\nint main() {\n    std::vector<int> values { 10, 20, 30 };\n\n    std::println(\"{}\", get_value(values, 1));\n    // get_value(values, 10); // violates the precondition\n}\n```\n\nThe valid call prints:\n\n```\n20\n```\n\nAnd here’s the version for you to play with the problematic call uncommented:\n\nSee at [Compiler Explorer](https://godbolt.org/z/v9c4hr4va)\n\nOn GCC, I’m getting:\n\n```\nProgram returned: 134\nProgram stdout\n20\nProgram stderr\ncontract violation in function int get_value(const std::vector<int>&, std::size_t) at /app/example.cpp:6: index < values.size()\n[assertion_kind: pre, semantic: enforce, mode: predicate_false, terminating: yes]\nterminate called without an active exception\nProgram terminated with signal SIGABRT (6)\n```\n\n## Contracts on Declarations and Definitions\n\nFor clarity, contracts can appear on a declaration, in a definition, or both:\n\n```\nint get_value(const std::vector<int>& values, std::size_t index)\n    pre(index < values.size());\n```\n\nThe definition can then omit the contract specifier:\n\n```\nint get_value(const std::vector<int>& values, std::size_t index)\n{\n    return values[index];\n}\n```\n\nThe contract assertions are established by the function’s first declaration. A later declaration or definition may omit the contract specifiers, as above, or repeat the same sequence.\n\nA later declaration cannot introduce a different set of preconditions or postconditions.\n\nIn practice, if a function is declared in a header, that first declaration is the natural place for its preconditions and postconditions. If you’re reading the API, it’s probably best to see all preconditions without looking inside the implementation.\n\nWhat’s more, contracts are associated with the function, but they are not part of the function type. Overload resolution, for example, does not distinguish functions based on their contracts.\n\n## Contract Evaluation Semantics\n\nC++26 defines four evaluation semantics:\n\n| Semantic | What happens at runtime? |\n| --- | --- |\n| Ignore | The predicate is not evaluated. |\n| Observe | A violation invokes the handler; execution continues if it returns normally. |\n| Enforce | A violation invokes the handler; execution terminates if it returns normally. |\n| Quick-enforce | A violation terminates execution without invoking the handler. |\n\nThe implementation determines which semantic applies. C++26 has no portable syntax for forcing an individual ordinary contract assertion to use one specific policy. These rules appear in the [contract evaluation specification](https://eel.is/c++draft/basic.contract.eval).\n\nWith the ignore semantic, no runtime check is performed. With observe, a violation is reported, but execution continues if the handler returns normally. In our example, both policies can allow the program to reach `values[index]` with an invalid index, resulting in undefined behavior.\n\nThus, adding `pre(...)` expresses the requirement, while the selected policy determines how checking responds.\n\n## Compiling with GCC\n\nAs of October 2026, only GCC 16 supports Contracts. We can use it with special compile flags:\n\n```\ng++ -std=c++26 -fcontracts \\\n    -fcontract-evaluation-semantic=enforce example.cpp\n```\n\nWith that policy, the invalid call invokes the violation handler. If the handler returns normally, execution terminates before the function body performs the invalid access. GCC’s default handler emits diagnostic information. See the [GCC options documentation - contracts](https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect-Options.html#index-fcontracts).\n\nGCC also allows us to replace the default contract-violation handler. For example, we can log a custom message when a violation is detected.\n\nThe handler receives a `std::contracts::contract_violation` object, which provides information about the failed contract, including the predicate, location, and evaluation semantic.\n\n```\n#include <contracts>\n#include <print>\n\nvoid handle_contract_violation(\n    const std::contracts::contract_violation& violation)\n{\n    std::println(stderr, \"Contract violated: {}\",\n                 violation.comment());\n}\n```\n\nSee an example [@Compiler Explorer](https://godbolt.org/z/YT941W6vx)\n\nNotice that replacing the handler doesn’t change what happens after it returns. With `observe`, execution continues. With `enforce`, the program terminates.\n\nNot every implementation is required to support replacing the handler, so this feature is implementation-defined.\n\n## Adding a Postcondition\n\nSo far, we’ve described what the caller must provide. We can also describe what a function promises when it returns normally.\n\nSee this example:\n\n```\nint clamp_value(int value, const int low, const int high)\n    pre(low <= high)\n    post(result: result >= low && result <= high)\n{\n    if (value < low)\n        return low;\n    if (value > high)\n        return high;\n    return value;\n}\n```\n\nThere are two useful pieces here:\n\n- `pre(low <= high)` checks that the bounds form a valid interval.\n- `post(result: ...)` checks that the returned value lies inside that interval.\n\nThe name `result` is ours to choose. It refers to the function’s result within that postcondition.\n\nWhat’s convenient is that the same postcondition covers every normal return path. We don’t have to repeat an assertion before each `return`.\n\nHere’s an example where I deliberately violated the postcondition:\n\n```\n#include <print>\n\nint clamp_value(int value, const int low, const int high)\n    pre(low <= high)\n    post(result: result >= low && result <= high)\n{\n    if (value < low)\n        return low-10; // <<\n    if (value > high)\n        return high;\n    return value;\n}\n\nint main() {\n    std::print(\"{}\", clamp_value(10, 20, 30));\n}\n```\n\nSee [@Compiler Explorer](https://godbolt.org/z/c9asf75M5)\n\nAnd on GCC I’m getting:\n\n```\ncontract violation in function int clamp_value(int, int, int) at /app/example.cpp:5: result >= low && result <= high\n[assertion_kind: post, semantic: enforce, mode: predicate_false, terminating: yes]\nterminate called without an active exception\nProgram terminated with signal SIGABRT (6)\n```\n\nThe precondition is fine, but the postcondition is broken.\n\n### Const Parameters in Postconditions\n\nThere’s also a detail in the parameter list: `low` and `high` are `const`.\n\nYou will get the following error if you try making them non-const:\n\n```\nsource>:5:28: error: a value parameter used in a postcondition must be const\n    5 |     post(result: result >= low && result <= high)\n```\n\nWhen a postcondition uses a parameter passed by value, that parameter must have `const` type on every declaration. Our comparisons use both bounds this way. The parameter `value` is only used in the body, so this requirement doesn’t apply to it.\n\nPostconditions apply to normal exit; they do not check the result when the function body exits through an exception. See the [function contract rules](https://eel.is/c++draft/dcl.contract).\n\n## Assertions Inside a Function\n\nFor conditions inside an implementation, we have `contract_assert`:\n\n```\n#include <string_view>\n\nstd::size_t count_spaces(std::string_view text) {\n    std::size_t count = 0;\n\n    for (char ch : text) {\n        if (ch == ' ')\n            ++count;\n    }\n\n    contract_assert(count <= text.size());\n    return count;\n}\n```\n\nThe condition is simple, but it expresses a useful property: the number of spaces cannot exceed the number of characters.\n\nIn a larger algorithm, such a check could describe a relationship between counters, an intermediate result, or the state after processing a block of data.\n\nTogether, `pre`, `post`, and `contract_assert` cover function entry, normal exit, and points inside a function.\n\n## Avoiding Mutation in Predicates\n\nOne thing to watch during migration is mutation inside predicates:\n\n```\nvoid process(int count)\n    pre(++count > 0) // error\n{\n}\n```\n\nSee [@Compiler Explorer](https://godbolt.org/z/9j4TMbfzn)\n\nAn error from GCC:\n\n```\nerror: increment of read-only location '(const int)count'\n    2 |     pre(++count > 0) // error\n```\n\nContract predicates use special constification rules. In this example, count is treated as const inside the predicate, so `++count` is rejected by the compiler.\n\nHowever, this does not make predicates completely free of side effects. For example, a predicate can still call a function that modifies global state. Since contract evaluation may be ignored, and side effects are not guaranteed even under checking semantics, predicates should not modify program state.\n\n## Contracts Are Not Input Validation\n\nThere’s another practical distinction: input validation.\n\nSuppose an index comes from a configuration file. If an invalid value requires an error message, that handling belongs in ordinary control flow:\n\n```\nif (index >= values.size()) {\n    std::println(\"Invalid index: {}\", index);\n    return;\n}\n\nstd::println(\"{}\", get_value(values, index));\n```\n\nThe validation handles an expected failure at the application boundary. The precondition documents the internal helper’s requirements.\n\nP2900 explicitly discusses this idea in:\n\n> Principle 12: Contract Assertions Are Not Flow Control:\n> While a contract assertion provides an algorithm to validate correctness, nothing about a contract assertion guarantees any particular runtime behavior associated with that syntactic construct.\n\nAnd more:\n\n> Importantly, this aspect of Contracts is why contract assertions must not be used for error handling and input validation: If a function has in-contract requirements to report certain events as errors, that handling must be done with standard C++ control statements that are not optional, never with contract assertions.\n\n## Considering Predicate Costs\n\nFinally, one note about performance implications.\n\nOur index comparison is cheap. A condition that verifies whether an entire collection is sorted needs to examine its elements. An expensive check may be useful during testing, but you should measure its effect on representative workloads.\n\n## Summary\n\nC++26 contracts give us three ways to express assumptions and guarantees directly in the language:\n\n- `pre(...)` describes conditions expected to hold when a function is entered,\n- `post(...)` describes conditions expected to hold when a function returns normally,\n- `contract_assert(...)` checks properties at specific points inside a function.\n\nPreconditions and postconditions are associated with the function through its declarations. In particular, the first declaration establishes the contract, which makes declarations in header files a natural place to document an API’s requirements.\n\nA contract assertion does not necessarily mean that a runtime check will always be performed. The selected evaluation semantic can ignore, observe, enforce, or quick-enforce the assertion. Code therefore shouldn’t depend on contract predicates for required application logic or side effects.\n\nContracts also aren’t a replacement for normal input validation. Use regular control flow for errors that your program expects and needs to handle.\n\n## References\n\n- [P2900, Contracts for C++](https://wg21.link//p2900)\n- [Contract assertions (since C++26) - cppreference.com - C++ Reference](https://cppreference.dev/w/cpp/language/contracts)\n- [How C++26 Contracts Improve Correctness Without Sacrificing Performance | Citadel Securities](https://www.citadelsecurities.com/careers/career-perspectives/how-cpp26-contracts-improve-correctness-without-sacrificing-performance/)\n- [How C++26 contracts work | Train IT](https://train-it.eu/blog/how-cpp26-contracts-work/)\n- [Test Driving C++26 Contracts with GCC 16.1 | Fabian Koehler](https://fkoehler.me/posts/2026/05/test-driving-cpp26-contracts-with-gcc-16-1/)\n","body_html":"<p>In the article about <a href=\"https://www.cppstories.com/2026/hardening-experiments/\" rel=\"nofollow ugc noopener\">Standard Library hardening</a>, we looked at checks for invalid Standard Library container access and other broken preconditions. I mentioned that those requirements are expressed in terms of “contracts”. But what about our code?</p>\n<p>In this post, we’ll explore contracts from C++26. We’ll start with a simple <code>cassert</code>, move the requirement into the function itself, and see how to check results. We’ll also look at declarations and definitions, compiler options, and a few details that can surprise you.</p>\n<h2 id=\"starting-with-an-assertion\">Starting with an Assertion</h2>\n<p>Let’s start with a small example:</p>\n<p>A header file first:</p>\n<pre><code>// foo.h\n#include &lt;vector&gt;\nint get_value(const std::vector&lt;int&gt;&amp; values, std::size_t index);</code></pre>\n<p>And the implementation:</p>\n<pre><code>// foo.cpp\n#include &quot;foo.h&quot; // our function declaration...\n#include &lt;cassert&gt;\n\nint get_value(const std::vector&lt;int&gt;&amp; values, std::size_t index) {\n    assert(index &lt; values.size());\n    return values[index];\n}</code></pre>\n<p>The function expects a valid index. If the condition is false, <code>assert</code> prints a diagnostic message and terminates the program. However, this only happens when assertions are enabled. Defining <code>NDEBUG</code> disables the check and basically removes the check from the code.</p>\n<p>What are the issues with this simple approach? The requirement lives inside the implementation. Somebody reading only the declaration sees a vector and an index, without the relationship between them.</p>\n<h2 id=\"adding-a-precondition\">Adding a Precondition</h2>\n<p>With C++26 contracts, we can move that requirement from an assertion inside the function body to the function itself:</p>\n<pre><code>int get_value(const std::vector&lt;int&gt;&amp; values, std::size_t index)\n    pre(index &lt; values.size())\n{\n    return values[index];\n}</code></pre>\n<p>Notice the new <code>pre(...)</code> syntax after the parameter list. It introduces a <em>precondition assertion</em>: a condition expected to hold when entering the function.</p>\n<p>What’s more, we can add this precondition to the declaration:</p>\n<pre><code>int get_value(const std::vector&lt;int&gt;&amp; values, std::size_t index)\n    pre(index &lt; values.size());</code></pre>\n<p>The feature was introduced through <a href=\"https://wg21.link//p2900\" rel=\"nofollow ugc noopener\">P2900, Contracts for C++</a>.</p>\n<p>Let’s understand how it works and how it differs from <code>assert</code>.</p>\n<h3 id=\"why-use-contracts-instead-of-assert\">Why Use Contracts Instead of <code>assert</code>?</h3>\n<p>At first, <code>pre(index &lt; values.size())</code> might look like another way to write <code>assert(index &lt; values.size())</code>. Both express a condition that should be true. So what’s the benefit of using contracts?</p>\n<p>There are a few important differences:</p>\n<ul><li>Requirements in declarations: We can put <code>pre</code> and <code>post</code> directly in the function declaration. This makes the requirements visible to anyone reading the API, without looking at the implementation.</li><li>More flexible checking: Traditional <code>assert</code> is enabled or disabled through <code>NDEBUG</code>. Contracts define several evaluation semantics, including ignoring a check, reporting a violation and continuing, or terminating the program. The compiler implementation determines which semantics are available and how they can be selected.</li><li>Preconditions and postconditions: Contracts distinguish between what the caller must provide and what the function promises to return. With <code>assert</code>, we would have to place checks manually inside the function body, including checks before different return statements.</li></ul>\n<p>This also makes contracts useful for documenting APIs, even when runtime checking is disabled.</p>\n<h2 id=\"a-complete-precondition-example\">A Complete Precondition Example</h2>\n<p>Here’s a complete example (no header file for simplicity for now):</p>\n<pre><code>#include &lt;print&gt;\n#include &lt;vector&gt;\n\nint get_value(const std::vector&lt;int&gt;&amp; values, std::size_t index)\n    pre(index &lt; values.size())\n{\n    return values[index];\n}\n\nint main() {\n    std::vector&lt;int&gt; values { 10, 20, 30 };\n\n    std::println(&quot;{}&quot;, get_value(values, 1));\n    // get_value(values, 10); // violates the precondition\n}</code></pre>\n<p>The valid call prints:</p>\n<pre><code>20</code></pre>\n<p>And here’s the version for you to play with the problematic call uncommented:</p>\n<p>See at <a href=\"https://godbolt.org/z/v9c4hr4va\" rel=\"nofollow ugc noopener\">Compiler Explorer</a></p>\n<p>On GCC, I’m getting:</p>\n<pre><code>Program returned: 134\nProgram stdout\n20\nProgram stderr\ncontract violation in function int get_value(const std::vector&lt;int&gt;&amp;, std::size_t) at /app/example.cpp:6: index &lt; values.size()\n[assertion_kind: pre, semantic: enforce, mode: predicate_false, terminating: yes]\nterminate called without an active exception\nProgram terminated with signal SIGABRT (6)</code></pre>\n<h2 id=\"contracts-on-declarations-and-definitions\">Contracts on Declarations and Definitions</h2>\n<p>For clarity, contracts can appear on a declaration, in a definition, or both:</p>\n<pre><code>int get_value(const std::vector&lt;int&gt;&amp; values, std::size_t index)\n    pre(index &lt; values.size());</code></pre>\n<p>The definition can then omit the contract specifier:</p>\n<pre><code>int get_value(const std::vector&lt;int&gt;&amp; values, std::size_t index)\n{\n    return values[index];\n}</code></pre>\n<p>The contract assertions are established by the function’s first declaration. A later declaration or definition may omit the contract specifiers, as above, or repeat the same sequence.</p>\n<p>A later declaration cannot introduce a different set of preconditions or postconditions.</p>\n<p>In practice, if a function is declared in a header, that first declaration is the natural place for its preconditions and postconditions. If you’re reading the API, it’s probably best to see all preconditions without looking inside the implementation.</p>\n<p>What’s more, contracts are associated with the function, but they are not part of the function type. Overload resolution, for example, does not distinguish functions based on their contracts.</p>\n<h2 id=\"contract-evaluation-semantics\">Contract Evaluation Semantics</h2>\n<p>C++26 defines four evaluation semantics:</p>\n<div class=\"table-wrap\"><table><thead><tr><th>Semantic</th><th>What happens at runtime?</th></tr></thead><tbody><tr><td>Ignore</td><td>The predicate is not evaluated.</td></tr><tr><td>Observe</td><td>A violation invokes the handler; execution continues if it returns normally.</td></tr><tr><td>Enforce</td><td>A violation invokes the handler; execution terminates if it returns normally.</td></tr><tr><td>Quick-enforce</td><td>A violation terminates execution without invoking the handler.</td></tr></tbody></table></div>\n<p>The implementation determines which semantic applies. C++26 has no portable syntax for forcing an individual ordinary contract assertion to use one specific policy. These rules appear in the <a href=\"https://eel.is/c++draft/basic.contract.eval\" rel=\"nofollow ugc noopener\">contract evaluation specification</a>.</p>\n<p>With the ignore semantic, no runtime check is performed. With observe, a violation is reported, but execution continues if the handler returns normally. In our example, both policies can allow the program to reach <code>values[index]</code> with an invalid index, resulting in undefined behavior.</p>\n<p>Thus, adding <code>pre(...)</code> expresses the requirement, while the selected policy determines how checking responds.</p>\n<h2 id=\"compiling-with-gcc\">Compiling with GCC</h2>\n<p>As of October 2026, only GCC 16 supports Contracts. We can use it with special compile flags:</p>\n<pre><code>g++ -std=c++26 -fcontracts \\\n    -fcontract-evaluation-semantic=enforce example.cpp</code></pre>\n<p>With that policy, the invalid call invokes the violation handler. If the handler returns normally, execution terminates before the function body performs the invalid access. GCC’s default handler emits diagnostic information. See the <a href=\"https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect-Options.html#index-fcontracts\" rel=\"nofollow ugc noopener\">GCC options documentation - contracts</a>.</p>\n<p>GCC also allows us to replace the default contract-violation handler. For example, we can log a custom message when a violation is detected.</p>\n<p>The handler receives a <code>std::contracts::contract_violation</code> object, which provides information about the failed contract, including the predicate, location, and evaluation semantic.</p>\n<pre><code>#include &lt;contracts&gt;\n#include &lt;print&gt;\n\nvoid handle_contract_violation(\n    const std::contracts::contract_violation&amp; violation)\n{\n    std::println(stderr, &quot;Contract violated: {}&quot;,\n                 violation.comment());\n}</code></pre>\n<p>See an example <a href=\"https://godbolt.org/z/YT941W6vx\" rel=\"nofollow ugc noopener\">@Compiler Explorer</a></p>\n<p>Notice that replacing the handler doesn’t change what happens after it returns. With <code>observe</code>, execution continues. With <code>enforce</code>, the program terminates.</p>\n<p>Not every implementation is required to support replacing the handler, so this feature is implementation-defined.</p>\n<h2 id=\"adding-a-postcondition\">Adding a Postcondition</h2>\n<p>So far, we’ve described what the caller must provide. We can also describe what a function promises when it returns normally.</p>\n<p>See this example:</p>\n<pre><code>int clamp_value(int value, const int low, const int high)\n    pre(low &lt;= high)\n    post(result: result &gt;= low &amp;&amp; result &lt;= high)\n{\n    if (value &lt; low)\n        return low;\n    if (value &gt; high)\n        return high;\n    return value;\n}</code></pre>\n<p>There are two useful pieces here:</p>\n<ul><li><code>pre(low &lt;= high)</code> checks that the bounds form a valid interval.</li><li><code>post(result: ...)</code> checks that the returned value lies inside that interval.</li></ul>\n<p>The name <code>result</code> is ours to choose. It refers to the function’s result within that postcondition.</p>\n<p>What’s convenient is that the same postcondition covers every normal return path. We don’t have to repeat an assertion before each <code>return</code>.</p>\n<p>Here’s an example where I deliberately violated the postcondition:</p>\n<pre><code>#include &lt;print&gt;\n\nint clamp_value(int value, const int low, const int high)\n    pre(low &lt;= high)\n    post(result: result &gt;= low &amp;&amp; result &lt;= high)\n{\n    if (value &lt; low)\n        return low-10; // &lt;&lt;\n    if (value &gt; high)\n        return high;\n    return value;\n}\n\nint main() {\n    std::print(&quot;{}&quot;, clamp_value(10, 20, 30));\n}</code></pre>\n<p>See <a href=\"https://godbolt.org/z/c9asf75M5\" rel=\"nofollow ugc noopener\">@Compiler Explorer</a></p>\n<p>And on GCC I’m getting:</p>\n<pre><code>contract violation in function int clamp_value(int, int, int) at /app/example.cpp:5: result &gt;= low &amp;&amp; result &lt;= high\n[assertion_kind: post, semantic: enforce, mode: predicate_false, terminating: yes]\nterminate called without an active exception\nProgram terminated with signal SIGABRT (6)</code></pre>\n<p>The precondition is fine, but the postcondition is broken.</p>\n<h3 id=\"const-parameters-in-postconditions\">Const Parameters in Postconditions</h3>\n<p>There’s also a detail in the parameter list: <code>low</code> and <code>high</code> are <code>const</code>.</p>\n<p>You will get the following error if you try making them non-const:</p>\n<pre><code>source&gt;:5:28: error: a value parameter used in a postcondition must be const\n    5 |     post(result: result &gt;= low &amp;&amp; result &lt;= high)</code></pre>\n<p>When a postcondition uses a parameter passed by value, that parameter must have <code>const</code> type on every declaration. Our comparisons use both bounds this way. The parameter <code>value</code> is only used in the body, so this requirement doesn’t apply to it.</p>\n<p>Postconditions apply to normal exit; they do not check the result when the function body exits through an exception. See the <a href=\"https://eel.is/c++draft/dcl.contract\" rel=\"nofollow ugc noopener\">function contract rules</a>.</p>\n<h2 id=\"assertions-inside-a-function\">Assertions Inside a Function</h2>\n<p>For conditions inside an implementation, we have <code>contract_assert</code>:</p>\n<pre><code>#include &lt;string_view&gt;\n\nstd::size_t count_spaces(std::string_view text) {\n    std::size_t count = 0;\n\n    for (char ch : text) {\n        if (ch == &#39; &#39;)\n            ++count;\n    }\n\n    contract_assert(count &lt;= text.size());\n    return count;\n}</code></pre>\n<p>The condition is simple, but it expresses a useful property: the number of spaces cannot exceed the number of characters.</p>\n<p>In a larger algorithm, such a check could describe a relationship between counters, an intermediate result, or the state after processing a block of data.</p>\n<p>Together, <code>pre</code>, <code>post</code>, and <code>contract_assert</code> cover function entry, normal exit, and points inside a function.</p>\n<h2 id=\"avoiding-mutation-in-predicates\">Avoiding Mutation in Predicates</h2>\n<p>One thing to watch during migration is mutation inside predicates:</p>\n<pre><code>void process(int count)\n    pre(++count &gt; 0) // error\n{\n}</code></pre>\n<p>See <a href=\"https://godbolt.org/z/9j4TMbfzn\" rel=\"nofollow ugc noopener\">@Compiler Explorer</a></p>\n<p>An error from GCC:</p>\n<pre><code>error: increment of read-only location &#39;(const int)count&#39;\n    2 |     pre(++count &gt; 0) // error</code></pre>\n<p>Contract predicates use special constification rules. In this example, count is treated as const inside the predicate, so <code>++count</code> is rejected by the compiler.</p>\n<p>However, this does not make predicates completely free of side effects. For example, a predicate can still call a function that modifies global state. Since contract evaluation may be ignored, and side effects are not guaranteed even under checking semantics, predicates should not modify program state.</p>\n<h2 id=\"contracts-are-not-input-validation\">Contracts Are Not Input Validation</h2>\n<p>There’s another practical distinction: input validation.</p>\n<p>Suppose an index comes from a configuration file. If an invalid value requires an error message, that handling belongs in ordinary control flow:</p>\n<pre><code>if (index &gt;= values.size()) {\n    std::println(&quot;Invalid index: {}&quot;, index);\n    return;\n}\n\nstd::println(&quot;{}&quot;, get_value(values, index));</code></pre>\n<p>The validation handles an expected failure at the application boundary. The precondition documents the internal helper’s requirements.</p>\n<p>P2900 explicitly discusses this idea in:</p>\n<blockquote><p>Principle 12: Contract Assertions Are Not Flow Control:\nWhile a contract assertion provides an algorithm to validate correctness, nothing about a contract assertion guarantees any particular runtime behavior associated with that syntactic construct.</p></blockquote>\n<p>And more:</p>\n<blockquote><p>Importantly, this aspect of Contracts is why contract assertions must not be used for error handling and input validation: If a function has in-contract requirements to report certain events as errors, that handling must be done with standard C++ control statements that are not optional, never with contract assertions.</p></blockquote>\n<h2 id=\"considering-predicate-costs\">Considering Predicate Costs</h2>\n<p>Finally, one note about performance implications.</p>\n<p>Our index comparison is cheap. A condition that verifies whether an entire collection is sorted needs to examine its elements. An expensive check may be useful during testing, but you should measure its effect on representative workloads.</p>\n<h2 id=\"summary\">Summary</h2>\n<p>C++26 contracts give us three ways to express assumptions and guarantees directly in the language:</p>\n<ul><li><code>pre(...)</code> describes conditions expected to hold when a function is entered,</li><li><code>post(...)</code> describes conditions expected to hold when a function returns normally,</li><li><code>contract_assert(...)</code> checks properties at specific points inside a function.</li></ul>\n<p>Preconditions and postconditions are associated with the function through its declarations. In particular, the first declaration establishes the contract, which makes declarations in header files a natural place to document an API’s requirements.</p>\n<p>A contract assertion does not necessarily mean that a runtime check will always be performed. The selected evaluation semantic can ignore, observe, enforce, or quick-enforce the assertion. Code therefore shouldn’t depend on contract predicates for required application logic or side effects.</p>\n<p>Contracts also aren’t a replacement for normal input validation. Use regular control flow for errors that your program expects and needs to handle.</p>\n<h2 id=\"references\">References</h2>\n<ul><li><a href=\"https://wg21.link//p2900\" rel=\"nofollow ugc noopener\">P2900, Contracts for C++</a></li><li><a href=\"https://cppreference.dev/w/cpp/language/contracts\" rel=\"nofollow ugc noopener\">Contract assertions (since C++26) - cppreference.com - C++ Reference</a></li><li><a href=\"https://www.citadelsecurities.com/careers/career-perspectives/how-cpp26-contracts-improve-correctness-without-sacrificing-performance/\" rel=\"nofollow ugc noopener\">How C++26 Contracts Improve Correctness Without Sacrificing Performance | Citadel Securities</a></li><li><a href=\"https://train-it.eu/blog/how-cpp26-contracts-work/\" rel=\"nofollow ugc noopener\">How C++26 contracts work | Train IT</a></li><li><a href=\"https://fkoehler.me/posts/2026/05/test-driving-cpp26-contracts-with-gcc-16-1/\" rel=\"nofollow ugc noopener\">Test Driving C++26 Contracts with GCC 16.1 | Fabian Koehler</a></li></ul>","headings":[{"level":2,"text":"Starting with an Assertion","id":"starting-with-an-assertion"},{"level":2,"text":"Adding a Precondition","id":"adding-a-precondition"},{"level":3,"text":"Why Use Contracts Instead of assert?","id":"why-use-contracts-instead-of-assert"},{"level":2,"text":"A Complete Precondition Example","id":"a-complete-precondition-example"},{"level":2,"text":"Contracts on Declarations and Definitions","id":"contracts-on-declarations-and-definitions"},{"level":2,"text":"Contract Evaluation Semantics","id":"contract-evaluation-semantics"},{"level":2,"text":"Compiling with GCC","id":"compiling-with-gcc"},{"level":2,"text":"Adding a Postcondition","id":"adding-a-postcondition"},{"level":3,"text":"Const Parameters in Postconditions","id":"const-parameters-in-postconditions"},{"level":2,"text":"Assertions Inside a Function","id":"assertions-inside-a-function"},{"level":2,"text":"Avoiding Mutation in Predicates","id":"avoiding-mutation-in-predicates"},{"level":2,"text":"Contracts Are Not Input Validation","id":"contracts-are-not-input-validation"},{"level":2,"text":"Considering Predicate Costs","id":"considering-predicate-costs"},{"level":2,"text":"Summary","id":"summary"},{"level":2,"text":"References","id":"references"}]}}