C++ Concepts
Type constraints and template requirements made simple
🎯 What are C++ Concepts?
C++ Concepts define requirements for template parameters, making templates safer and error messages clearer. They specify what types can be used with templates, improving code readability and debugging experience.
// Simple concept example
#include <concepts>
template<typename T>
concept Addable = requires(T a, T b) {
a + b;
};
template<Addable T>
T add(T a, T b) {
return a + b;
}
Usage:
int result = add(5, 3); // Works - int supports +
// add("hello", "world"); // Error - clear message
Key Concept Features
Type Safety
Ensure types meet requirements
template<std::integral T>
void process(T value) { }
Clear Errors
Better compiler error messages
// Clear: "T must be integral"
// Instead of template gibberish
Requirements
Express what operations are needed
concept Printable = requires(T t) {
std::cout << t;
};
Readability
Self-documenting template code
template<Sortable Container>
void sort_data(Container& c);
🔹 Basic Concept Definition
The concept keyword in C++20 enables the definition of named compile‑time predicates for template parameters. Concepts specify requirements that types must satisfy, improving template readability and error messages. For example, a Sortable concept ensures types support comparison and swapping. This enhances code clarity and allows compilers to enforce constraints early, reducing cryptic template errors and facilitating better documentation of generic interfaces.
#include <concepts>
// Simple concept - checks if type has + operator
template<typename T>
concept Addable = requires(T a, T b) {
a + b; // Must support addition
};
// Using the concept
template<Addable T>
T sum(T x, T y) {
return x + y;
}
int main() {
int result1 = sum(10, 20); // âś… Works
double result2 = sum(1.5, 2.5); // âś… Works
// sum(std::vector{1}, std::vector{2}); // ❌ Error
return 0;
}
Output:
result1 = 30
result2 = 4.0
🔹 Standard Library Concepts
C++20’s standard library includes predefined concepts like std::integral and std::floating_point. These built‑in concepts cover common type requirements, reducing boilerplate. For instance, std::integral<int> evaluates to true, enabling templates to restrict parameters to integer families. Utilizing these concepts accelerates development and ensures consistency with the language’s type‑traits system.
#include <concepts>
#include <iostream>
// Using standard concepts
template<std::integral T>
void print_integer(T value) {
std::cout << "Integer: " << value << std::endl;
}
template<std::floating_point T>
void print_float(T value) {
std::cout << "Float: " << value << std::endl;
}
int main() {
print_integer(42); // âś… int is integral
print_float(3.14); // âś… double is floating_point
// print_integer(3.14); // ❌ Error - clear message
return 0;
}
Output:
Integer: 42
Float: 3.14
🔹 Complex Requirements
Concepts can combine multiple requirements using logical operators like && and ||. This allows expressing sophisticated constraints, such as a type being both copyable and comparable. Complex concepts enable precise API specifications, ensuring that templates only accept types meeting all necessary operations, which enhances safety and reduces runtime checks.
#include <concepts>
#include <iostream>
// Complex concept with multiple requirements
template<typename T>
concept Printable = requires(T t) {
std::cout << t; // Must be printable
{ t.size() } -> std::integral; // Must have size() returning integral
};
template<Printable T>
void print_with_size(const T& item) {
std::cout << "Item: " << item
<< " (size: " << item.size() << ")" << std::endl;
}
int main() {
std::string text = "Hello";
std::vector<int> numbers = {1, 2, 3};
print_with_size(text); // âś… Works
print_with_size(numbers); // âś… Works
// print_with_size(42); // ❌ Error - no size()
return 0;
}
Output:
Item: Hello (size: 5)
Item: [vector content] (size: 3)