diff --git a/CPP20.md b/CPP20.md index 0b5582f..67d274e 100644 --- a/CPP20.md +++ b/CPP20.md @@ -12,48 +12,86 @@ C++20 includes the following new library features: ## C++20 Language Features ### 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 > 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++ -// `MyConcept` is always satisfied. +// `T` is not limited by any constraints. template -concept MyConcept = true; - -// Three syntactic forms for constraints (same for lambdas): -template -void f(T); - -template - requires MyConcept -void f(T); - -template -void f(T) requires MyConcept; -``` -_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++ +concept AlwaysSatisfied = true; +// Limit `T` to integrals. template concept Integral = std::is_integral_v; +// Limit `T` to both the `Integral` constraint and signedness. template concept SignedIntegral = Integral && std::is_signed_v; +// Limit `T` to both the `Integral` constraint and the negation of the `SignedIntegral` constraint. template concept UnsignedIntegral = Integral && !SignedIntegral; ``` +There are a variety of syntactic forms for enforcing concepts: +```c++ +// Forms for function parameters: +// `T` is a constrained type template parameter. +template +void f(T v); + +// `T` is a constrained type template parameter. +template + requires MyConcept +void f(T v); + +// `T` is a constrained type template parameter. +template +void f(T v) requires MyConcept; + +// `v` is a constrained deduced parameter. +void f(MyConcept auto v); + +// `v` is a constrained non-type template parameter. +template +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 = [] (T v) { + // ... +}; +// `T` is a constrained type template parameter. +auto f = [] requires MyConcept (T v) { + // ... +}; +// `T` is a constrained type template parameter. +auto f = [] (T v) requires MyConcept { + // ... +}; +// `v` is a constrained deduced parameter. +auto f = [](MyConcept auto v) { + // ... +}; +// `v` is a constrained non-type template parameter. +auto g = [] () { + // ... +}; +``` The `requires` keyword is used either to start a requires clause or a requires expression: ```c++ template - requires MyConcept // requires clause + requires MyConcept // `requires` clause. void f(T); template -concept Callable = requires (T f) { f(); }; // requires expression +concept Callable = requires (T f) { f(); }; // `requires` expression. template - 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) { return a + b; } @@ -92,9 +130,10 @@ using Ref = T&; template concept C = requires { - typename T::value; // A) required nested member name - typename S; // B) required class template specialization - typename Ref; // C) required alias template substitution + // Requirements on type `T`: + typename T::value; // A) has an inner member named `value` + typename S; // B) must have a valid class template specialization for `S` + typename Ref; // C) must be a valid alias template substitution }; template @@ -109,14 +148,9 @@ g(Baz{}); // PASS. ```c++ template concept C = requires(T x) { - {*x} -> typename T::inner; // the expression *x must be valid - // AND the type T::inner must be valid - // AND the result of *x must be convertible to T::inner - {x + 1} -> std::Same; // the expression x + 1 must be valid - // AND std::Same 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 + {*x} -> typename T::inner; // the type of the expression `*x` is convertible to `T::inner` + {x + 1} -> std::Same; // the expression `x + 1` satisfies `std::Same` + {x * 1} -> T; // the type of the expression `x * 1` is convertible to `T` }; ``` * **Nested requirements** - denoted by the `requires` keyword, specify additional constraints (such as those on local parameter arguments). diff --git a/README.md b/README.md index d23ef50..d5695df 100644 --- a/README.md +++ b/README.md @@ -102,48 +102,86 @@ C++11 includes the following new library features: ## C++20 Language Features ### 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 > 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++ -// `MyConcept` is always satisfied. +// `T` is not limited by any constraints. template -concept MyConcept = true; - -// Three syntactic forms for constraints (same for lambdas): -template -void f(T); - -template - requires MyConcept -void f(T); - -template -void f(T) requires MyConcept; -``` -_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++ +concept AlwaysSatisfied = true; +// Limit `T` to integrals. template concept Integral = std::is_integral_v; +// Limit `T` to both the `Integral` constraint and signedness. template concept SignedIntegral = Integral && std::is_signed_v; +// Limit `T` to both the `Integral` constraint and the negation of the `SignedIntegral` constraint. template concept UnsignedIntegral = Integral && !SignedIntegral; ``` +There are a variety of syntactic forms for enforcing concepts: +```c++ +// Forms for function parameters: +// `T` is a constrained type template parameter. +template +void f(T v); + +// `T` is a constrained type template parameter. +template + requires MyConcept +void f(T v); + +// `T` is a constrained type template parameter. +template +void f(T v) requires MyConcept; + +// `v` is a constrained deduced parameter. +void f(MyConcept auto v); + +// `v` is a constrained non-type template parameter. +template +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 = [] (T v) { + // ... +}; +// `T` is a constrained type template parameter. +auto f = [] requires MyConcept (T v) { + // ... +}; +// `T` is a constrained type template parameter. +auto f = [] (T v) requires MyConcept { + // ... +}; +// `v` is a constrained deduced parameter. +auto f = [](MyConcept auto v) { + // ... +}; +// `v` is a constrained non-type template parameter. +auto g = [] () { + // ... +}; +``` The `requires` keyword is used either to start a requires clause or a requires expression: ```c++ template - requires MyConcept // requires clause + requires MyConcept // `requires` clause. void f(T); template -concept Callable = requires (T f) { f(); }; // requires expression +concept Callable = requires (T f) { f(); }; // `requires` expression. template - 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) { return a + b; } @@ -182,9 +220,10 @@ using Ref = T&; template concept C = requires { - typename T::value; // A) required nested member name - typename S; // B) required class template specialization - typename Ref; // C) required alias template substitution + // Requirements on type `T`: + typename T::value; // A) has an inner member named `value` + typename S; // B) must have a valid class template specialization for `S` + typename Ref; // C) must be a valid alias template substitution }; template @@ -199,14 +238,9 @@ g(Baz{}); // PASS. ```c++ template concept C = requires(T x) { - {*x} -> typename T::inner; // the expression *x must be valid - // AND the type T::inner must be valid - // AND the result of *x must be convertible to T::inner - {x + 1} -> std::Same; // the expression x + 1 must be valid - // AND std::Same 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 + {*x} -> typename T::inner; // the type of the expression `*x` is convertible to `T::inner` + {x + 1} -> std::Same; // the expression `x + 1` satisfies `std::Same` + {x * 1} -> T; // the type of the expression `x * 1` is convertible to `T` }; ``` * **Nested requirements** - denoted by the `requires` keyword, specify additional constraints (such as those on local parameter arguments).