Defining Concepts with Requires Expressions
In my last post, “Define Concepts“, I defined the concepts Integral
, SignedIntegral
, and UnsigendIntegral
using logical combinations of existing concepts and compile-time predicates. Today, I use Requires Expressions to define concepts.
Before I write about the use of requires expressions to define a concept, here is a short reminder:
template <template-parameter-list> concept concept-name = constraint-expression;
template
and has a template parameter list. It uses the keyword concept
followed by the concept name and the constraint expression.constraint-expression
is a compile-time predicate. A compile-time predicate is a function that runs at compile time and returns a boolean. This compile-time predicate can either be:- A logical combination of other concepts or compile-time predicates using conjunctions (
&&
), disjunctions (||
), or negations(!
). I wrote about syntactical form in my previous post t “Define Concepts“.
- A requires expression
- Simple requirements
- Type requirements
- Compound requirements
- Nested requirements
Now, let’s write about requires expression.
Requires Expression
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
requires (parameter-list(optional)) {requirement-seq}
parameter-list:
A comma-separated list of parameters, such as in a function declarationrequirement-seq:
A sequence of requirements, consisting of simple, type, compound, or nested requirements
Simple Requirements
Addable
is a simple requirement:
template<typename T> concept Addable = requires (T a, T b) { a + b; };
Addable
requires that the addition a + b
of two values of the same type T
is possible.Type Requirements
typename
together with a type name.
template<typename T> concept TypeRequirement = requires { typename T::value_type; typename Other<T>; };
TypeRequirement
requires that type T
has a nested member value_type
, and that the class template Other
can be instantiated with T
.
#include <iostream> #include <vector> template <typename> struct Other; template <> struct Other<std::vector<int>> {}; template<typename T> concept TypeRequirement = requires { typename T::value_type; // (2) typename Other<T>; // (3) }; int main() { TypeRequirement auto myVec= std::vector<int>{1, 2, 3}; // (1) }
TypeRequirement auto myVec = std::vector<int>{1, 2, 3}
(line 1) is valid. A std::vector
has an inner member value_type
(line 2) and the class template Other
(line 2) can be instantiated with std::vector<int>
(line 3).TypeRequirement
, in combination with auto
in line 1 is called a constrained placeholder. In my previous post, “C++20: Concepts, the Placeholder Syntax“, read more about constrained and unconstrained placeholders.Compound Requirements
{expression} noexcept(optional) return-type-requirement(optional);
noexcept
specifier and a requirement on its return type. You essentially express with the noexcept
specifier that this expression does not throw an exception, and if it throw, you do not care and let the program just crash. Read more about the noexcept
specifier in my previous post: C++ Core Guidelines: The noexcept
specifier and operator.Equal
, demonstrated in the following example, uses compound requirements.
// conceptsDefinitionEqual.cpp #include <concepts> #include <iostream> template<typename T> // (1) concept Equal = requires(T a, T b) { { a == b } -> std::convertible_to<bool>; { a != b } -> std::convertible_to<bool>; }; bool areEqual(Equal auto a, Equal auto b){ return a == b; } struct WithoutEqual{ // (2) bool operator==(const WithoutEqual& other) = delete; }; struct WithoutUnequal{ // (3) bool operator!=(const WithoutUnequal& other) = delete; }; int main() { std::cout << std::boolalpha << '\n'; std::cout << "areEqual(1, 5): " << areEqual(1, 5) << '\n'; /* bool res = areEqual(WithoutEqual(), WithoutEqual()); // (4) bool res2 = areEqual(WithoutUnequal(), WithoutUnequal()); */ std::cout << '\n'; }
Equal
(line 1) requires that its type parameter T
supports the equal and non-equal operators. Additionally, both operators have to return a convertible value to a boolean. Of course, int
supports the concept Equal
, but this does not hold for the types WithoutEqual
(line 2) and WithoutUnequal
(line 3). Consequently, when I use the type WithoutEqual
(line 4), I get the following error message when using the GCC compiler.Nested Requirements
requires constraint-expression;
UnsignedIntegral
using logical combinations of existing concepts and compile-time predicates. Now, I define it using nested requirements:// nestedRequirements.cpp #include <type_traits> template <typename T> concept Integral = std::is_integral<T>::value; template <typename T> concept SignedIntegral = Integral<T> && std::is_signed<T>::value; // template <typename T> // (2) // concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>; template <typename T> // (1) concept UnsignedIntegral = Integral<T> && requires(T) { requires !SignedIntegral<T>; }; int main() { UnsignedIntegral auto n = 5u; // works // UnsignedIntegral auto m = 5; // compile time error, 5 is a signed literal }
SignedIntegral
as a nested requirement to refine the concept Integral
. The commented-out concept UnsignedIntegral in line (2) is more convenient to read.What’s next?
static_assert
or constexpr if
. I will write in my next post about these unique use cases of requires expressions.Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!