Back to C++
2026-04-139 min read

function contract specifiers (C++)

Learn function contract specifiers (C++) step by step with clear examples and exercises.

Why This Matters

Function contract specifiers are a significant addition to C++ that enables developers to specify the expected behavior of functions in terms of preconditions, postconditions, and invariants. These specifications help in writing robust code by ensuring that the function behaves as intended under certain conditions. In this lesson, we will delve deeper into function contract specifiers, their syntax, and how to use them effectively for writing reliable and maintainable code.

Why This Matters

Function contract specifications play a crucial role in software development by promoting clear communication of function behavior, encouraging modular design, and catching potential bugs during the development process itself. They are particularly valuable when working on large projects with multiple developers, as they provide a clear documentation of the function's intended behavior.

Prerequisites

To fully understand function contract specifiers, you should have a strong foundation in C++ programming concepts such as functions, classes, and object-oriented programming. Familiarity with the Standard Template Library (STL) is beneficial but not mandatory.

Core Concept

Preconditions

Preconditions are conditions that must be true before a function is called. If these conditions are not met, the function may behave unexpectedly or even crash. Preconditions can be specified using the requires clause followed by a condition in parentheses.

void myFunction(int arr[], int size) requires (size > 0);

In this example, myFunction requires that the size of the array is greater than zero before it can be called. If the size is not greater than zero, the compiler will issue an error.

Postconditions

Postconditions are conditions that should hold true after a function has been executed successfully. Postconditions can help in verifying that the function behaved as intended and can also serve as documentation for other developers. Postconditions can be specified using the ensures clause followed by a condition in parentheses.

void myFunction(int arr[], int size) ensures (std::is_sorted(arr, arr + size));

In this example, myFunction ensures that the array is sorted after it has been executed successfully. If the array is not sorted, the compiler will issue an error.

Invariants

Invariants are conditions that should hold true at any point during a function's execution. Invariants can help in verifying that the function maintains its internal state correctly and can also serve as documentation for other developers. Invariants can be specified using the noexcept clause followed by a condition in parentheses.

void myFunction() noexcept(std::is_sorted(arr, arr + size));

In this example, myFunction ensures that the array is sorted at any point during its execution. If the array is not sorted, the compiler will issue an error.

Exception Specifications

Exception specifications allow developers to specify which exceptions a function can throw under normal and abnormal conditions. They are specified using the throw() clause or by listing the types of exceptions that can be thrown.

void myFunction() throws(std::exception);

In this example, myFunction specifies that it can throw instances of std::exception and no other exceptions.

Worked Example

Let's consider a more complex example of a function that sorts an array using quicksort. We will use preconditions, postconditions, and exception specifications to ensure that the function behaves as expected.

#include <algorithm>
#include <vector>
#include <stdexcept>

void quickSort(std::vector<int>& arr, int low, int high) requires (low <= high);
void quickSort(std::vector<int>& arr, int low, int high) ensures (std::is_sorted(arr.begin() + low, arr.end()));
void quickSort(std::vector<int>& arr, int low, int high) throws(std::exception);

void swap(std::vector<int>& arr, int i, int j) requires (i >= 0 && i < arr.size() && j >= 0 && j < arr.size());
void swap(std::vector<int>& arr, int i, int j) ensures (arr[i] == arr[j] before && arr[i] == arr[j] after && std::find(arr.begin(), arr.end(), arr[i]) != arr.end() && std::find(arr.begin(), arr.end(), arr[j]) != arr.end());

void quickSort(std::vector<int>& arr, int low, int high) {
if (low < high) {
int pivotIndex = partition(arr, low, high);
quickSort(arr, low, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, high);
}
}

int partition(std::vector<int>& arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; ++j) {
if (arr[j] <= pivot) {
++i;
std::swap(arr[i], arr[j]);
}
}
std::swap(arr[i + 1], arr[high]);
return i + 1;
}

In this example, quickSort requires that the indices are valid for the array. It also ensures that the array is sorted after it has been executed successfully and that the function does not throw any exceptions other than std::exception. The swap function enforces similar preconditions and postconditions to maintain the integrity of the array during the sorting process.

Common Mistakes

  1. Forgetting to specify preconditions: If preconditions are not specified, the function may behave unexpectedly or even crash when called with invalid arguments.
  2. Overcomplicating postconditions: Postconditions should be simple and easy to understand. Avoid using complex expressions that make it difficult to verify the function's behavior.
  3. Ignoring invariants: Invariants are an important part of function contract specifications. Make sure to specify invariants for your functions to ensure their internal state is maintained correctly.
  4. Not testing with invalid inputs: It's essential to test your functions with invalid inputs to verify that the preconditions are enforced correctly and that the postconditions hold true even when the function fails.
  5. Not documenting contract specifications: Function contract specifications should be clearly documented in the function's documentation or comments to make it easy for other developers to understand the intended behavior of the function.
  6. Not handling exceptions appropriately: When using exception specifications, make sure to handle exceptions correctly within the function body and do not rethrow exceptions without proper error handling.
  7. Using outdated compilers: Function contract specifiers are a relatively new feature in C++ and may not be supported by all compilers. Make sure to use a compiler that supports C++26 or later to take advantage of these features.
  8. Misusing exception specifications: Exception specifications should be used judiciously, as they can make it more difficult to handle exceptions in some cases. Use them only when necessary and ensure that the specified exceptions are appropriate for the function's behavior.
  9. Not considering performance implications: While function contract specifiers can help improve code reliability, they may have performance implications. Be aware of these implications and optimize your code as needed to balance reliability and performance.

Practice Questions

  1. Write a function that takes two integers as arguments and returns their sum, with preconditions ensuring that both arguments are non-negative and postconditions ensuring that the result is also non-negative.
  2. Write a function that sorts an array of integers using merge sort, with preconditions ensuring that the array is not empty and postconditions ensuring that the array is sorted in ascending order.
  3. Write a function that calculates the factorial of a number, with preconditions ensuring that the number is non-negative and postconditions ensuring that the result is correct.
  4. Write a function that finds the maximum element in an array, with preconditions ensuring that the array is not empty and postconditions ensuring that the maximum element is returned correctly.
  5. Write a function that checks if a given number is prime, with preconditions ensuring that the number is non-negative and postconditions ensuring that the function returns true only for prime numbers.
  6. Write a function that finds all prime numbers in an array, with preconditions ensuring that the array contains only non-negative integers and postconditions ensuring that the function correctly identifies all prime numbers in the array.
  7. Write a function that calculates the greatest common divisor (GCD) of two integers, with preconditions ensuring that both arguments are non-negative and postconditions ensuring that the GCD is calculated correctly.
  8. Write a function that checks if a given string is a palindrome, with preconditions ensuring that the input string is not empty and postconditions ensuring that the function returns true only for palindromes.
  9. Write a function that finds the kth smallest element in an array, with preconditions ensuring that the array is sorted in ascending order and postconditions ensuring that the function correctly identifies the kth smallest element.
  10. Write a function that checks if a given binary tree is balanced, with preconditions ensuring that the input tree is not null and postconditions ensuring that the function returns true only for balanced trees.

FAQ

  1. Can I use function contract specifiers with non-member functions? Yes, function contract specifications can be used with both member and non-member functions.
  2. Do I need to specify preconditions, postconditions, and invariants for every function? It's not necessary to specify contract specifications for every function, but it's a good practice to do so for critical functions that are likely to be called frequently or have complex behavior.
  3. Can I use function contract specifiers with templates? Yes, function contract specifications can be used with templates, but you need to ensure that the template arguments satisfy the preconditions and postconditions of the template function.
  4. What happens if a function violates its postconditions or invariants? If a function violates its postconditions or invariants, the compiler will issue an error. However, Note that that the error may not be caught during compile-time and could lead to runtime errors.
  5. Can I use function contract specifiers with older versions of C++? Function contract specifications were introduced in C++26, so they are not available in older versions of the language.
  6. What is the relationship between function contract specifications and unit testing? Function contract specifications can complement unit testing by providing a more formal specification of a function's behavior. However, unit tests should still be used to verify the correctness of the function under various conditions and edge cases.
  7. Can I use function contract specifications with third-party libraries or APIs? Function contract specifications are primarily intended for use within your own codebase. When using third-party libraries or APIs, it's essential to follow their documentation and best practices for error handling and exception management.
  8. How can I enforce function contract specifications at runtime? Enforcing function contract specifications at runtime typically requires the use of a specialized tool or library that can check the specified conditions during execution and generate appropriate error messages if they are violated. Some popular tools include Contract++, Boost.Contract, and Google Test's Catch2.
  9. How do I handle exceptions when using function contract specifications? When using exception specifications, it's essential to ensure that the specified exceptions are handled appropriately within the function body. This may involve catching exceptions, performing error handling, and rethrowing exceptions if necessary. It's also important to consider the performance implications of exception handling and balance reliability with performance when designing your code.
  10. How can I learn more about function contract specifications and best practices for using them? To learn more about function contract specifications and best practices for using them, you can refer to the C++ Standard (C++26 Draft) and various online resources such as Modern C++ Design by Andreas Prochazka, A Tour of C++ by Bjarne Stroustrup, and various articles and tutorials on the web. Additionally, participating in online communities such as Stack Overflow and the C++ subreddit can help you connect with other developers who are experienced in using function contract specifications.
function contract specifiers (C++) | C++ | XQA Learn