banner 1183445 1280

C++ Core Guidelines: Regular and SemiRegular Types

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 that 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 in his book Fundamentals of Generic Programming. Here is a short excerpt. It’s quite easy to remember the eight concepts used to define a regular type. There is a 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.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    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 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’s 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 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 possible 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 short-circuit evaluation. This means evaluating 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: 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, Matt Godbolt, and Honey Sukesan.

    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)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *