C++ Core Guidelines: Regular and SemiRegular Types

Contents[Show]

The topic for today is quite important when you create your type: Regular and SemiRegular types.

 

banner 1183445 1280

Here is the exact rule for today.

T.46: Require template arguments to be at least Regular or SemiRegular

Okay, the first question I have to answer is quite obvious. What is a Regular or a SemiRegular type? My answer is based on the proposal p0898. I assume you may already guess it. Regular and SemiRegular are concepts, which are defined by concepts.

Regular

  • DefaultConstructible
  • CopyConstructible, CopyAssignable
  • MoveConstructible, MoveAssignable
  • Destructible
  • Swappable
  • EqualityComparable

SemiRegular

  • Regular - EqualityComparable

The term Regular goes back to the father of the Standard Template Library Alexander Stepanov. He introduced the term it in his book Fundamentals of Generic Programming for which you have here a short excerpt. It's quite easy to remember the eight concepts used to define a regular type. There is the well-known rule of six:

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

Just add the Swappable and EqualityComparable concepts to it. There is a more informal way to say that a type T is regular: T behaves like an int

To get SemiRegular, you have to subtract EqualityComparable from Regular.

I hear your next question: Why should our template arguments at least be Regular or SemiRegular or do as the ints do? The STL containers and algorithms, in particular, assume Regular data types.

What is commonly used but not a Regular type? Right: a reference.

References are not Regular

Thanks to the type-traits library the following program checks at compile time if int& is a SemiRegular type.

// semiRegular.cpp

#include <iostream>
#include <type_traits>

int main(){
    
    std::cout << std::boolalpha << std::endl;
    
    std::cout << "std::is_default_constructible<int&>::value: " << std::is_default_constructible<int&>::value << std::endl;
    std::cout << "std::is_copy_constructible<int&>::value: " << std::is_copy_constructible<int&>::value << std::endl;
    std::cout << "std::is_copy_assignable<int&>::value: " << std::is_copy_assignable<int&>::value << std::endl;
    std::cout << "std::is_move_constructible<int&>::value: " << std::is_move_constructible<int&>::value << std::endl;
    std::cout << "std::is_move_assignable<int&>::value: " << std::is_move_assignable<int&>::value << std::endl;
    std::cout << "std::is_destructible<int&>::value: " << std::is_destructible<int&>::value << std::endl;
    std::cout << std::endl;
    std::cout << "std::is_swappable<int&>::value: " << std::is_swappable<int&>::value << std::endl;        // requires C++17

    std::cout << std::endl;

}

 

First of all. The function std::is_swappable requires C++17. Second here is the output.

semiRegular

You see, a reference such as an int& is not default constructible. The output shows that a reference is not SemiRegular and, therefore, not Regular. To check, if a type is Regular at compile time, I need a function isEqualityComparable which is not part of the type-traits library. Let's do it by myself.

isEqualityComparable

In C++20 we might get the detection idiom which is part of the library fundamental TS v2. Now, it a piece of cake to implement isEqualityComparable.

// equalityComparable.cpp

#include <experimental/type_traits>                                                       // (1)
#include <iostream>

template<typename T>
using equal_comparable_t = decltype(std::declval<T&>() == std::declval<T&>());           // (2)

template<typename T>
struct isEqualityComparable: 
       std::experimental::is_detected<equal_comparable_t, T>{};                           // (3)

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

struct NotEqualityComparable { };                                                         // (5)
 
int main() {
    
    std::cout << std::boolalpha << std::endl;
    
    std::cout << "isEqualityComparable<EqualityComparable>::value: " << 
                  isEqualityComparable<EqualityComparable>::value << std::endl;
                  
    std::cout << "isEqualityComparable<NotEqualityComparable>::value: " << 
                  isEqualityComparable<NotEqualityComparable>::value << std::endl;
    
    std::cout << std::endl;
    
}

 

The new feature is in the experimental namespace (1). Line (3) is the crucial one. It detects if the expression (2) is valid for the type T.  The type-trait isEqualityComparable works for an EqualityComparable (4) and a NotEqualityComparable (5) type. Only EqualityCompable returns true because I overloaded the Equal-Comparison Operator.

To compile the program, you need a new C++ compiler such as GCC 8.2.

equalityComparable

Until C++20, comparison operators are automatically generated for arithmetic types, enumerations, and with restrictions for pointers. This may change with C++20 due to the spaceship operator: <=>.  With C++20, when a class defines operator <=>, automatically the operators  ==, !=, <, <=, >, and >= are generated. It's even possible just to define operator <=> as defaulted such as for the type Point.

class Point {
   int x;
   int y;
public:
   auto operator<=>(const Point&) const = default;
   ....
};
// compiler generates all six relational operators

 

In this case, the compiler will generate the implementation. The default operator<=> performs a lexicographical comparison on its bases (left-to-right, depth-first) and continues with its non-static member in declaration order. This comparison applies to. This means the evaluation of a logical expression ends if the result is known.

 

Now, I have all the ingredients to define Regular and SemiRegular. Here are my new type-traits.

// isRegular.cpp

#include <experimental/type_traits>
#include <iostream>

template<typename T>
using equal_comparable_t = decltype(std::declval<T&>() == std::declval<T&>());

template<typename T>
struct isEqualityComparable: 
       std::experimental::is_detected<equal_comparable_t, T>
       {};

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>
struct isRegular: std::integral_constant<bool, 
                                         isSemiRegular<T>::value &&
                                         isEqualityComparable<T>::value >{};
                                         
                                            
int main(){
    
    std::cout << std::boolalpha << std::endl;
                  
    std::cout << "isSemiRegular<int>::value: " << isSemiRegular<int>::value << std::endl;
    std::cout << "isRegular<int>::value: " << isRegular<int>::value << std::endl;
    
    std::cout << std::endl;
    
    std::cout << "isSemiRegular<int&>::value: " << isSemiRegular<int&>::value << std::endl;
    std::cout << "isRegular<int&>::value: " << isRegular<int&>::value << std::endl;
    
    std::cout << std::endl;
    
}

 

The usage of the new type-traits isSemiRegular and isRegular makes the main program quite readable.

isRegular

What's next?

With my next post, I jump directly to the template definition.

 

 

 

Thanks a lot to my Patreon Supporters: Eric Pederson, Paul Baxter,  Meeting C++, Matt Braun, Avi Lachmish, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Dilettant, Marko, and Ramesh Jangama.

Thanks in particular to:  TakeUpCode 450 60

 

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.  

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 the 100 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 600 pages full of modern C++ and more than 100 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

Add comment


Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 175

All 1406363

Currently are 157 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments