Defining Concepts with Requires Expressions

Defining Concepts with Requires Expressions

This post is a cross-post from www.ModernesCpp.com.

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:

The syntax to define a concept is straightforward:

template <template-parameter-list>
concept concept-name = constraint-expression;        

?

A concept definition starts with the keyword template and has a template parameter list. It uses the keyword concept followed by the concept name and the constraint expression.

A 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:

  1. 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".
  2. A requires expression

  • Simple requirements
  • Type requirements
  • Compound requirements
  • Nested requirements

Now, let's write about requires expression.

Requires Expression

Thanks to requires expressions, you can define powerful concepts. A requires expression has the following form:

 requires (parameter-list(optional)) {requirement-seq}          

  • parameter-list: A comma-separated list of parameters, such as in a function declaration
  • requirement-seq: A sequence of requirements, consisting of simple, type, compound, or nested requirements

Requires expressions can also be used as a standalone feature when a compile-time predicate is required. I will write about this feature later.

Simple Requirements

The following concept Addable is a simple requirement:

 template<typename T>
concept Addable = requires (T a, T b) {
    a + b;
};        

The concept Addable requires that the addition a + b of two values of the same type T is possible.

Before I continue with type requirements, I want to add. This concept has only one purpose: exemplifying simple requirements. Writing a concept that just checks a type for its support of the + operator is bad. A concept should model an idea, such as Arithmetic.

Type Requirements

In a type requirement, you have to use the keyword typename together with a type name.

 template<typename T>
concept TypeRequirement = requires {
    typename T::value_type;
    typename Other<T>;    
};        

The concept TypeRequirement requires that type T has a nested member value_type, and that the class template Other can be instantiated with T.

Let's try this out:

#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)

}        

The expression 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).

The concept TypeRequirement in combination with auto in line 1 is called a constrained placeholder. Read more about constrained and unconstrained placeholders in my previous post "C++20: Concepts, the Placeholder Syntax".

Compound Requirements

A compound requirement has the form

 {expression} noexcept(optional) return-type-requirement(optional);            

In addition to a simple requirement, a compound requirement can have a 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.

The concept 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';
 
}        


The concept Equal (line 1) requires that its type parameter T supports the equal and not-equal operator. Additionally, both operators have to return a value that is convertible 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.

No alt text provided for this image

Nested Requirements

A nested requirement has the form

 requires constraint-expression;           

Nested requirements are used to specify requirements on type parameters.

In my previous post "Define Concepts", I defined the concept 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

}        


Line (1) uses the concept SignedIntegral as a nested requirement to refine the concept Integral. Honestly, the commented-out concept UnsignedIntegral in line (2) is more convenient to read.

What's next?

Typically, you use a requires expressions to define a concept, but they can also be used as a standalone feature when a compile-time predicate is required. Therefore, use cases for requires expression can be a requires clause, static_assert, or constexpr if. I will write in my next post about these special use cases of requires expressions.

?

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, Marko, G Prvulovic, Reinhold Dr?ge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, Louis St-Amour, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschl?ger, Red Trip, Alessandro Pezzato, Evangelos Denaxas, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, Eduardo Velasquez, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Ralf Holly, and Juan Dent.

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, Ralf Abramowitsch, John Nebel, and Mipko.

My special thanks to Embarcadero

My special thanks to PVS-Studio

Mentoring Program

My new mentoring program "Fundamentals for C++ Professionals" starts on the 22nd of April. Get more information here: https://bit.ly/MentoringProgramModernesCpp.

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++


要查看或添加评论,请登录

Rainer Grimm的更多文章

  • Placeholders and Extended Character Set

    Placeholders and Extended Character Set

    Placeholders are a nice way to highlight variables that are no longer needed. Additionally, the character set of C++26…

    2 条评论
  • Contracts in C++26

    Contracts in C++26

    Contracts allow you to specify preconditions, postconditions, and invariants for functions. Contracts should already be…

    4 条评论
  • Mentoring as a Key to Success

    Mentoring as a Key to Success

    I know that we are going through very challenging times. Saving is the top priority.

  • Reflection in C++26: Determine the Layout

    Reflection in C++26: Determine the Layout

    Thanks to reflection, you can determine the layout of types. My examples are based on the reflection proposal P2996R5.

    5 条评论
  • My ALS Journey (15/n): A typical Day

    My ALS Journey (15/n): A typical Day

    You may wonder how my day looks. Let me compare a day before ALS with a current day.

    3 条评论
  • Reflection in C++26: Metafunctions for Enums and Classes

    Reflection in C++26: Metafunctions for Enums and Classes

    Today, I continue my journey through reflection in C++26 and play with enums and classes. The names of the…

    2 条评论
  • Become a Professional C++ Developer

    Become a Professional C++ Developer

    Do you want to become a professional C++ developer? Here is your chance. Take my mentoring program "Fundamentals for…

  • Reflection in C++26: Metafunctions

    Reflection in C++26: Metafunctions

    Reflection offers many metafunctions that run at compile time. The metafunctions are declared as consteval.

    1 条评论
  • Improve You Or Your Team

    Improve You Or Your Team

    Do you want to become a professional C++ developer? Here is your chance. Take one of my mentoring programs and try out…

  • Reflection in C++

    Reflection in C++

    After the search into the breadth starts today, the search into the depth: reflection. Reflection Reflection is the…

社区洞察

其他会员也浏览了