TimelineCpp20Concepts

C++20: Define the Concept Regular and SemiRegular

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 regular. In this post, I define the concepts Regular and SemiRegular.

 

TimelineCpp20Concepts

Regular and SemiRegular are essential ideas in C++. Sorry, I have to say concepts. For example, the rule T.46 from the C++ Core Guidelines: T.46: Require template arguments to be at least Regular or SemiRegular. 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 I start with a SemiRegular type. 

SemiRegular

A SemiRegular type has to support the rule of six and 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;

 

Rainer D 6 P2 500x500

 

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.

     

    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 equally 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>;
    

     

    There is no 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 referenced 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 simplify it, 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 combined all pieces from the previous code snippets 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 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 on concepts in C++20, I’m curious to know your opinion on concepts. Are concepts an evolution or a revolution in C++? I’ll be happy if you drop me an E-Mail, including Thursday (06.02). I will 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: 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 *