C++20: Define the Concept Regular and SemiRegular

Contents[Show]

When you want to define a concrete type that works well in the C++ ecosystem, you should define a type that "behaves link an int". Formally, your concrete type should be a regular type. In this post, I define the concepts Regular and SemiRegular.

 

TimelineCpp20Concepts

Regular and SemiRegular are important ideas in C++. Sorry, I have to say concepts. For example, here is the rule T.46 from the C++ Core Guidelines: T.46: Require template arguments to be at least Regular or SemiRegular. Now, only one important question is left to answer: What are Regular or SemiRegular types? Before I dive into the details, this is the informal answer:  

  • A regular type "behaves link an int".  It could be copied and, the result of the copy operation is independent of the original one and has the same value. 

Okay, let me be more formal. A Regular type is also a SemiRegular type. Consequentially a start with a SemiRegular type. 

SemiRegular

A SemiRegular type has to support the rule of six: and has to be swappable. 

  • Default constructor: X()
  • Copy constructor: X(const X&)
  • Copy assignment: operator=(const X&)
  • Move constructor: X(X&&)
  • Move assignment: operator=(X&&)
  • Destructor: ~X()
  • swappable: swap(X&, Y&)

This was easy. Thanks to the type-traits library, defining the corresponding concepts is a no-brainer. Let me first define the corresponding type-trait isSemiRegular and then use it to define the concept SemiRegular.

template<typename T>
struct isSemiRegular: std::integral_constant<bool,
                                      std::is_default_constructible<T>::value &&
                                      std::is_copy_constructible<T>::value &&
                                      std::is_copy_assignable<T>::value &&
                                      std::is_move_constructible<T>::value &&
                                      std::is_move_assignable<T>::value &&
                                      std::is_destructible<T>::value &&
                                      std::is_swappable<T>::value >{};


template<typename T>
concept SemiRegular = isSemiRegular<T>::value;

Let's continue.

Regular

Only one little step and I'm done with the concept Regular. Additionally, to the concept SemiRegular the concept Regular requires that the type is equality comparable. I already defined in my last post the concept Equal

template<typename T>
concept Equal =
    requires(T a, T b) {
        { a == b } -> std::convertible_to<bool>;
        { a != b } -> std::convertible_to<bool>;
};

 

Let me reuse the concept Equal to define the concept Regular.

template<typename T>
concept Regular = Equal<T> && 
SemiRegular<T>;

Now, I'm curious. How are SemiRegular and Regular defined in C++20?

The concepts regular and semiregular in C++20

 

template<class T>
concept movable = is_object_v<T> && move_constructible<T> &&
assignable_from<T&, T> && swappable<T>;

template<class T>
concept copyable = copy_constructible<T> && movable<T> && assignable_from<T&, const T&>;

template<class T>
concept semiregular = copyable<T> && default_constructible<T>;

template<class T>
concept regular = semiregular<T> && equality_comparable<T>;

You see, there is not any reason to define the concept Regular and SemiRegular but to explain it.

Interestingly, the concept regular is similar to my concept Regular but the concept semiregular is composed of more elementary concepts such as copyable and moveable. The concept movable is based on the function is_object from the type-traits library. From the already reference page, here is a possible implementation of the type-traits is_object.

 

template< class T>
struct is_object : std::integral_constant<bool,
                     std::is_scalar<T>::value ||
                     std::is_array<T>::value  ||
                     std::is_union<T>::value  ||
                     std::is_class<T>::value> {};

 

The final step in my post is missing. Let me try it out.

Usage for the concepts Regular and regular

To make it simple, the function templates behavesLikeAnInt and behavesLikeAnInt2 check if the arguments "behaves like an int". This means my concept Regular and the C++20 concept regular is used to establish the requirement.

// regularSemiRegular.cpp

#include <concepts>
#include <vector>
#include <utility>

template<typename T>
struct isSemiRegular: std::integral_constant<bool,
                                      std::is_default_constructible<T>::value &&
                                      std::is_copy_constructible<T>::value &&
                                      std::is_copy_assignable<T>::value &&
                                      std::is_move_constructible<T>::value &&
                                      std::is_move_assignable<T>::value &&
                                      std::is_destructible<T>::value &&
                                      std::is_swappable<T>::value >{};


template<typename T>
concept SemiRegular = isSemiRegular<T>::value;

template<typename T>
concept Equal =
    requires(T a, T b) {
        { a == b } -> std::convertible_to<bool>;
        { a != b } -> std::convertible_to<bool>;
};

template<typename T>                              // (1)
concept Regular = Equal<T> && 
                  SemiRegular<T>;

template <Regular T>                              // (2)
void behavesLikeAnInt(T) {
    // ...
}

template <std::regular T>                         // (3)
void behavesLikeAnInt2(T) {
    // ...
}

struct EqualityComparable { };                    // (4)                                          
bool operator == (EqualityComparable const&, EqualityComparable const&) { return true; }

struct NotEqualityComparable { };                 // (5)

int main() {

    int myInt{};
    behavesLikeAnInt(myInt);
    behavesLikeAnInt2(myInt);

    std::vector<int> myVec{};
    behavesLikeAnInt(myVec);
    behavesLikeAnInt2(myVec);

    EqualityComparable equComp;
    behavesLikeAnInt(equComp);
    behavesLikeAnInt2(equComp);

    NotEqualityComparable notEquComp;             
    behavesLikeAnInt(notEquComp);                  // (6)
    behavesLikeAnInt2(notEquComp);                 // (7)
    
}

 

I put all pieces from the previous code-snippets together to get the concept Regular (Zeile 1) The functions behavesLikeAnInt (line 2) and behaves behavesLikeAnInt2 (line 3) use both concepts. As the name suggests, the type EqualityComparable (line 4) supports equality but not the type NotEqualityComparable (line 5). Using the type NotEqualityComparable in both functions (lines 6 and 7) is the most interesting part.

GCC

If you want to see the program in action, use the link to the Compiler Explorer: https://godbolt.org/z/XAJ2w3. The error message in the Compiler Explorer with GCC is very accurate but a little overwhelming. This is probably due to the fact, that both concepts failed, concepts are still in an early implementation stage, and the online tools are not as comfortable as a console.

The Concept Regular

Essentially this is the message from my failed concept Regular (line 6) using the Compiler Explorer.

RegularError

The Concept regular

The C++20 concept regular (line 7) uses a more elaborate implementation. Consequentially, I got a more elaborated error message.

regularCpp20Error

MSVC

The error message of the window's compiler is too unspecific.

regularWin

What's next?

Now I'm done with my miniseries to concepts in C++20, and I'm curious to know your opinion on concepts. Are concepts an evolution or a revolution in C++? I'm happy when you drop me an E-Mail including Thursday (06.02). I use my next final post to concepts to present your opinions. When I should mention your name, say it explicitly. 

 

Thanks a lot to my Patreon Supporters: Meeting C++, Matt Braun, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Richard Ohnemus, Frank Grimm, Sakib, Broeserl, António Pina, Markus Falkner, Darshan Mody, Sergey Agafyin, Андрей Бурмистров, Jake, GS, and Lawton Shoemake.

 

Thanks in particular to:   crp4

 

   

Get your e-book at Leanpub:

The C++ Standard Library

 

Concurrency With Modern C++

 

Get Both as one Bundle

cover   ConcurrencyCoverFrame   bundle
With C++11, C++14, and C++17 we got a lot of new C++ libraries. In addition, the existing ones are greatly improved. The key idea of my book is to give you the necessary information to the current C++ libraries in about 200 pages. I also included more than 120 source files.  

C++11 is the first C++ standard that deals with concurrency. The story goes on with C++17 and will continue with C++20.

I'll give you a detailed insight in the current and the upcoming concurrency in C++. This insight includes the theory and a lot of practice with more than 140 source files.

 

Get my books "The C++ Standard Library" (including C++17) and "Concurrency with Modern C++" in a bundle.

In sum, you get more than 700 pages full of modern C++ and more than 260 source files presenting concurrency in practice.

 

Get your interactive course

 

Modern C++ Concurrency in Practice

C++ Standard Library including C++14 & C++17

educative CLibrary

Based on my book "Concurrency with Modern C++" educative.io created an interactive course.

What's Inside?

  • 140 lessons
  • 110 code playgrounds => Runs in the browser
  • 78 code snippets
  • 55 illustrations

Based on my book "The C++ Standard Library" educative.io created an interactive course.

What's Inside?

  • 149 lessons
  • 111 code playgrounds => Runs in the browser
  • 164 code snippets
  • 25 illustrations
Tags: concepts

My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 3647

All 3920257

Currently are 117 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments