mirror of
https://github.com/AnthonyCalandra/modern-cpp-features.git
synced 2025-12-18 10:34:35 +03:00
More work on concepts.
This commit is contained in:
98
CPP20.md
98
CPP20.md
@@ -12,48 +12,86 @@ C++20 includes the following new library features:
|
|||||||
## C++20 Language Features
|
## C++20 Language Features
|
||||||
|
|
||||||
### Concepts
|
### Concepts
|
||||||
_Concepts_ are named compile-time predicates which constrain template arguments. They take the following form:
|
_Concepts_ are named compile-time predicates which constrain types. They take the following form:
|
||||||
```
|
```
|
||||||
template < template-parameter-list >
|
template < template-parameter-list >
|
||||||
concept concept-name = constraint-expression;
|
concept concept-name = constraint-expression;
|
||||||
```
|
```
|
||||||
where `constraint-expression` evaluates to a constexpr Boolean. Example:
|
where `constraint-expression` evaluates to a constexpr Boolean. _Constraints_ should model semantic requirements, such as whether a type is a numeric or hashable. A compiler error results if a given type does not satisfy the concept it's bound by (i.e. `constraint-expression` returns `false`). Because constraints are evaluated at compile-time, they can provide more meaningful error messages and runtime safety.
|
||||||
```c++
|
```c++
|
||||||
// `MyConcept` is always satisfied.
|
// `T` is not limited by any constraints.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept MyConcept = true;
|
concept AlwaysSatisfied = true;
|
||||||
|
// Limit `T` to integrals.
|
||||||
// Three syntactic forms for constraints (same for lambdas):
|
|
||||||
template <MyConcept T>
|
|
||||||
void f(T);
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
requires MyConcept<T>
|
|
||||||
void f(T);
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void f(T) requires MyConcept<T>;
|
|
||||||
```
|
|
||||||
_Constraints_ should model semantic requirements, such as whether a type is a numeric or hashable. Because constraints are evaluated at compile-time, they can provide more meaningful error messages and runtime safety.
|
|
||||||
```c++
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept Integral = std::is_integral_v<T>;
|
concept Integral = std::is_integral_v<T>;
|
||||||
|
// Limit `T` to both the `Integral` constraint and signedness.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept SignedIntegral = Integral<T> && std::is_signed_v<T>;
|
concept SignedIntegral = Integral<T> && std::is_signed_v<T>;
|
||||||
|
// Limit `T` to both the `Integral` constraint and the negation of the `SignedIntegral` constraint.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;
|
concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;
|
||||||
```
|
```
|
||||||
|
There are a variety of syntactic forms for enforcing concepts:
|
||||||
|
```c++
|
||||||
|
// Forms for function parameters:
|
||||||
|
// `T` is a constrained type template parameter.
|
||||||
|
template <MyConcept T>
|
||||||
|
void f(T v);
|
||||||
|
|
||||||
|
// `T` is a constrained type template parameter.
|
||||||
|
template <typename T>
|
||||||
|
requires MyConcept<T>
|
||||||
|
void f(T v);
|
||||||
|
|
||||||
|
// `T` is a constrained type template parameter.
|
||||||
|
template <typename T>
|
||||||
|
void f(T v) requires MyConcept<T>;
|
||||||
|
|
||||||
|
// `v` is a constrained deduced parameter.
|
||||||
|
void f(MyConcept auto v);
|
||||||
|
|
||||||
|
// `v` is a constrained non-type template parameter.
|
||||||
|
template <MyConcept auto v>
|
||||||
|
void g();
|
||||||
|
|
||||||
|
// Forms for auto-deduced variables:
|
||||||
|
// `foo` is a constrained auto-deduced value.
|
||||||
|
MyConcept auto foo = ...;
|
||||||
|
|
||||||
|
// Forms for lambdas:
|
||||||
|
// `T` is a constrained type template parameter.
|
||||||
|
auto f = []<MyConcept T> (T v) {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
// `T` is a constrained type template parameter.
|
||||||
|
auto f = []<typename T> requires MyConcept<T> (T v) {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
// `T` is a constrained type template parameter.
|
||||||
|
auto f = []<typename T> (T v) requires MyConcept<T> {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
// `v` is a constrained deduced parameter.
|
||||||
|
auto f = [](MyConcept auto v) {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
// `v` is a constrained non-type template parameter.
|
||||||
|
auto g = []<MyConcept auto v> () {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
```
|
||||||
The `requires` keyword is used either to start a requires clause or a requires expression:
|
The `requires` keyword is used either to start a requires clause or a requires expression:
|
||||||
```c++
|
```c++
|
||||||
template <typename T>
|
template <typename T>
|
||||||
requires MyConcept<T> // requires clause
|
requires MyConcept<T> // `requires` clause.
|
||||||
void f(T);
|
void f(T);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept Callable = requires (T f) { f(); }; // requires expression
|
concept Callable = requires (T f) { f(); }; // `requires` expression.
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
requires requires (T x) { x + x; } // requires clause and expression on same line
|
requires requires (T x) { x + x; } // `requires` clause and expression on same line.
|
||||||
T add(T a, T b) {
|
T add(T a, T b) {
|
||||||
return a + b;
|
return a + b;
|
||||||
}
|
}
|
||||||
@@ -92,9 +130,10 @@ using Ref = T&;
|
|||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept C = requires {
|
concept C = requires {
|
||||||
typename T::value; // A) required nested member name
|
// Requirements on type `T`:
|
||||||
typename S<T>; // B) required class template specialization
|
typename T::value; // A) has an inner member named `value`
|
||||||
typename Ref<T>; // C) required alias template substitution
|
typename S<T>; // B) must have a valid class template specialization for `S`
|
||||||
|
typename Ref<T>; // C) must be a valid alias template substitution
|
||||||
};
|
};
|
||||||
|
|
||||||
template <C T>
|
template <C T>
|
||||||
@@ -109,14 +148,9 @@ g(Baz{}); // PASS.
|
|||||||
```c++
|
```c++
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept C = requires(T x) {
|
concept C = requires(T x) {
|
||||||
{*x} -> typename T::inner; // the expression *x must be valid
|
{*x} -> typename T::inner; // the type of the expression `*x` is convertible to `T::inner`
|
||||||
// AND the type T::inner must be valid
|
{x + 1} -> std::Same<int>; // the expression `x + 1` satisfies `std::Same<decltype((x + 1))>`
|
||||||
// AND the result of *x must be convertible to T::inner
|
{x * 1} -> T; // the type of the expression `x * 1` is convertible to `T`
|
||||||
{x + 1} -> std::Same<int>; // the expression x + 1 must be valid
|
|
||||||
// AND std::Same<decltype((x + 1)), int> must be satisfied
|
|
||||||
// i.e., (x + 1) must be a prvalue of type int
|
|
||||||
{x * 1} -> T; // the expression x * 1 must be valid
|
|
||||||
// AND its result must be convertible to T
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
* **Nested requirements** - denoted by the `requires` keyword, specify additional constraints (such as those on local parameter arguments).
|
* **Nested requirements** - denoted by the `requires` keyword, specify additional constraints (such as those on local parameter arguments).
|
||||||
|
|||||||
98
README.md
98
README.md
@@ -102,48 +102,86 @@ C++11 includes the following new library features:
|
|||||||
## C++20 Language Features
|
## C++20 Language Features
|
||||||
|
|
||||||
### Concepts
|
### Concepts
|
||||||
_Concepts_ are named compile-time predicates which constrain template arguments. They take the following form:
|
_Concepts_ are named compile-time predicates which constrain types. They take the following form:
|
||||||
```
|
```
|
||||||
template < template-parameter-list >
|
template < template-parameter-list >
|
||||||
concept concept-name = constraint-expression;
|
concept concept-name = constraint-expression;
|
||||||
```
|
```
|
||||||
where `constraint-expression` evaluates to a constexpr Boolean. Example:
|
where `constraint-expression` evaluates to a constexpr Boolean. _Constraints_ should model semantic requirements, such as whether a type is a numeric or hashable. A compiler error results if a given type does not satisfy the concept it's bound by (i.e. `constraint-expression` returns `false`). Because constraints are evaluated at compile-time, they can provide more meaningful error messages and runtime safety.
|
||||||
```c++
|
```c++
|
||||||
// `MyConcept` is always satisfied.
|
// `T` is not limited by any constraints.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept MyConcept = true;
|
concept AlwaysSatisfied = true;
|
||||||
|
// Limit `T` to integrals.
|
||||||
// Three syntactic forms for constraints (same for lambdas):
|
|
||||||
template <MyConcept T>
|
|
||||||
void f(T);
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
requires MyConcept<T>
|
|
||||||
void f(T);
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void f(T) requires MyConcept<T>;
|
|
||||||
```
|
|
||||||
_Constraints_ should model semantic requirements, such as whether a type is a numeric or hashable. Because constraints are evaluated at compile-time, they can provide more meaningful error messages and runtime safety.
|
|
||||||
```c++
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept Integral = std::is_integral_v<T>;
|
concept Integral = std::is_integral_v<T>;
|
||||||
|
// Limit `T` to both the `Integral` constraint and signedness.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept SignedIntegral = Integral<T> && std::is_signed_v<T>;
|
concept SignedIntegral = Integral<T> && std::is_signed_v<T>;
|
||||||
|
// Limit `T` to both the `Integral` constraint and the negation of the `SignedIntegral` constraint.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;
|
concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;
|
||||||
```
|
```
|
||||||
|
There are a variety of syntactic forms for enforcing concepts:
|
||||||
|
```c++
|
||||||
|
// Forms for function parameters:
|
||||||
|
// `T` is a constrained type template parameter.
|
||||||
|
template <MyConcept T>
|
||||||
|
void f(T v);
|
||||||
|
|
||||||
|
// `T` is a constrained type template parameter.
|
||||||
|
template <typename T>
|
||||||
|
requires MyConcept<T>
|
||||||
|
void f(T v);
|
||||||
|
|
||||||
|
// `T` is a constrained type template parameter.
|
||||||
|
template <typename T>
|
||||||
|
void f(T v) requires MyConcept<T>;
|
||||||
|
|
||||||
|
// `v` is a constrained deduced parameter.
|
||||||
|
void f(MyConcept auto v);
|
||||||
|
|
||||||
|
// `v` is a constrained non-type template parameter.
|
||||||
|
template <MyConcept auto v>
|
||||||
|
void g();
|
||||||
|
|
||||||
|
// Forms for auto-deduced variables:
|
||||||
|
// `foo` is a constrained auto-deduced value.
|
||||||
|
MyConcept auto foo = ...;
|
||||||
|
|
||||||
|
// Forms for lambdas:
|
||||||
|
// `T` is a constrained type template parameter.
|
||||||
|
auto f = []<MyConcept T> (T v) {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
// `T` is a constrained type template parameter.
|
||||||
|
auto f = []<typename T> requires MyConcept<T> (T v) {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
// `T` is a constrained type template parameter.
|
||||||
|
auto f = []<typename T> (T v) requires MyConcept<T> {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
// `v` is a constrained deduced parameter.
|
||||||
|
auto f = [](MyConcept auto v) {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
// `v` is a constrained non-type template parameter.
|
||||||
|
auto g = []<MyConcept auto v> () {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
```
|
||||||
The `requires` keyword is used either to start a requires clause or a requires expression:
|
The `requires` keyword is used either to start a requires clause or a requires expression:
|
||||||
```c++
|
```c++
|
||||||
template <typename T>
|
template <typename T>
|
||||||
requires MyConcept<T> // requires clause
|
requires MyConcept<T> // `requires` clause.
|
||||||
void f(T);
|
void f(T);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept Callable = requires (T f) { f(); }; // requires expression
|
concept Callable = requires (T f) { f(); }; // `requires` expression.
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
requires requires (T x) { x + x; } // requires clause and expression on same line
|
requires requires (T x) { x + x; } // `requires` clause and expression on same line.
|
||||||
T add(T a, T b) {
|
T add(T a, T b) {
|
||||||
return a + b;
|
return a + b;
|
||||||
}
|
}
|
||||||
@@ -182,9 +220,10 @@ using Ref = T&;
|
|||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept C = requires {
|
concept C = requires {
|
||||||
typename T::value; // A) required nested member name
|
// Requirements on type `T`:
|
||||||
typename S<T>; // B) required class template specialization
|
typename T::value; // A) has an inner member named `value`
|
||||||
typename Ref<T>; // C) required alias template substitution
|
typename S<T>; // B) must have a valid class template specialization for `S`
|
||||||
|
typename Ref<T>; // C) must be a valid alias template substitution
|
||||||
};
|
};
|
||||||
|
|
||||||
template <C T>
|
template <C T>
|
||||||
@@ -199,14 +238,9 @@ g(Baz{}); // PASS.
|
|||||||
```c++
|
```c++
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept C = requires(T x) {
|
concept C = requires(T x) {
|
||||||
{*x} -> typename T::inner; // the expression *x must be valid
|
{*x} -> typename T::inner; // the type of the expression `*x` is convertible to `T::inner`
|
||||||
// AND the type T::inner must be valid
|
{x + 1} -> std::Same<int>; // the expression `x + 1` satisfies `std::Same<decltype((x + 1))>`
|
||||||
// AND the result of *x must be convertible to T::inner
|
{x * 1} -> T; // the type of the expression `x * 1` is convertible to `T`
|
||||||
{x + 1} -> std::Same<int>; // the expression x + 1 must be valid
|
|
||||||
// AND std::Same<decltype((x + 1)), int> must be satisfied
|
|
||||||
// i.e., (x + 1) must be a prvalue of type int
|
|
||||||
{x * 1} -> T; // the expression x * 1 must be valid
|
|
||||||
// AND its result must be convertible to T
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
* **Nested requirements** - denoted by the `requires` keyword, specify additional constraints (such as those on local parameter arguments).
|
* **Nested requirements** - denoted by the `requires` keyword, specify additional constraints (such as those on local parameter arguments).
|
||||||
|
|||||||
Reference in New Issue
Block a user