Concept in C++
C++20 introduced concept as a major language feature that allows developers to specify constraints on template parameters.
Table of Contents
1. Intro
2. Concept Syntax
3. Requires Expression
4. Error Messages
5. Practices
1. Intro
Before C++20, template constraints are achieved by using SFINAE
, which lacks readability and error messages. concept
provides clear template constraints that are easy to read and understand.
1 | // SFINAE way |
When T
is a type of integral
, then the expression std::enable_if_t<std::is_integral_v<T>, void>
will be evaluated to be true
. The function signature is valid. Otherwise, the function will be discarded, leading to compile error of “no matching function”.
To achieve the same thing using concept
.
1 | template <typename T> |
2. Concept Syntax
We will define a concept
named Addable
which checks whether two objects of type T
can be added together using the +
operator. Here are some common usages of how to apply this concept
.
1 | // Define a concept |
We can also use logical operators to combine multiple constraints.
1 | template <typename T> |
1 | template <typename T> |
3. Requires Expression
The requires
expression is a new language construct in C++20 that allows you to specify requirements within concept definitions. The basic syntax for requires
is:
1 | requires (parameter-list) { |
Where parameter-list
is optional and requirement-seq
can be one of the followings:
- simple requirement
- type requirement
- compound requirement
- nested requirement
*Simple requirement requires that the expressions must be valid.
1 | template<typename T> |
Type requirement* requires that a type member exists and is valid.
1 | template<typename T> |
Compound requirement
Basic syntax:
1 | requires { |
A compound requirement does two things:
- Checks if the expression is valid.
- Checks whether the result type of the expression matches a given type.
1 | template<typename T> |
Nested requirement is a situation where a requires
clause is embedded inside another requires
clause. It performs additional constraints check.
1 | template<typename T> |
4. Error Messages
Unlike SFINAE (which only output: “no matching function found”), with concept
, we can have a more clear error message. In case if we accidentally use a wrong data type:
1 | template<std::signed_integral T> |
We will see a error message like this:
1 | Compiler returned: 1 |
5. Practices
Here’s a practical real-world example to illustrate the use of concept
. Imagine we are developing an e-commerce platform for selling products. We can simply classify products into two categories: physical products and digital products. All products share some common attributes, such as a name, price, and unique ID. However, each product type also has its own specific characteristics—for instance, physical products may include properties like weight and dimensions, while digital products might have attributes such as file size and download link.
1 | // Basic concept: What every product in our store must have |
References
Concept in C++