C++20 introduced a powerful feature called Concepts that gives programmers a way to specify precise requirements for template parameters. Concepts act as compile‑time predicates that constrain types, improving code clarity, error diagnostics, and enabling more expressive generic programming.
1. What are Concepts?
A concept is essentially a boolean expression that can be evaluated at compile time. It describes a set of operations and properties that a type must satisfy. For example, the standard library defines the std::integral concept to represent all integral types:
template<class T>
concept integral = std::is_integral_v <T>;
2. Syntax Basics
A concept is declared with the concept keyword:
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as <T>;
};
The requires clause lists expressions that must be valid for the concept to hold. The trailing `-> std::same_as
` is a *return type constraint* ensuring the expression yields a type convertible to `T`.
**3. Using Concepts in Function Templates**
“`cpp
template
T sum(T a, T b) {
return a + b;
}
“`
If you try to instantiate `sum` with a type that does not satisfy `Addable`, the compiler will produce a clear error pointing to the violation.
**4. Combining Concepts**
You can compose multiple concepts using logical operators:
“`cpp
template
concept Arithmetic = std::integral
|| std::floating_point;
template
T product(T a, T b) {
return a * b;
}
“`
**5. Standard Library Concepts**
C++20 provides a rich set of concepts in `
`: `same_as`, `derived_from`, `default_initializable`, `copy_constructible`, `move_constructible`, `equality_comparable`, etc. They are used extensively in the STL:
“`cpp
template
requires std::input_iterator
void process(I first, I last);
“`
**6. Writing Your Own Concepts**
When designing libraries, defining meaningful concepts can dramatically improve user experience:
“`cpp
template
concept Serializer = requires(T t, std::ostream& os) {
{ t.serialize(os) } -> std::same_as
;
};
“`
Now functions requiring a serializable type can enforce this constraint:
“`cpp
template
void save(const T& obj, const std::string& filename) {
std::ofstream out(filename);
obj.serialize(out);
}
“`
**7. Benefits**
– **Early error detection**: The compiler reports constraint violations at the point of instantiation.
– **Improved diagnostics**: Standard library errors become more readable.
– **Self‑documenting code**: Concepts describe intent directly in the signature.
– **Better IDE support**: Tools can infer template constraints, aiding autocomplete.
**8. Caveats**
– **Compilation time**: Excessive or overly complex concepts can increase template instantiation time.
– **Compatibility**: Not all compilers fully support C++20 concepts yet, so consider fallbacks or `-std=c++2a` flags.
**9. Practical Example: A Generic Binary Search**
“`cpp
#include
#include
#include
template
struct Node {
Key key;
// other members…
};
template
requires std::ranges::range
&&
std::sortable
auto binary_search(const Container& data, const auto& key) {
auto it = std::lower_bound(data.begin(), data.end(), key,
[](const auto& a, const auto& b){ return a.key key == key)
return it;
return data.end();
}
int main() {
std::vector<node> vec{{1}, {3}, {5}, {7}};
auto it = binary_search(vec, Node
{5});
if (it != vec.end())
std::cout << "Found key: " <key << '\n';
}
“`
Here, the `binary_search` function uses concepts to constrain the container type to something that is a range and sortable. The lambda inside `lower_bound` also demonstrates how concepts can be combined with other standard facilities.
**10. Summary**
Concepts bring compile‑time contracts to C++ templates, enabling more expressive, safer, and maintainable generic code. As the language evolves, adopting concepts early in your projects will position you to write clearer APIs and benefit from the growing ecosystem of libraries that embrace this feature.</node