Concepts
We stay in the year 2020. With high probability, we will get concepts. Of course, waterproof statements about the future are challenging, but the statement is from Bjarne Stroustrup (Meeting C++ 2016 at Berlin).
The classical concepts
The key idea of generic programming with templates is to define functions and classes that can be used with different types. But it will often happen that you instantiate a template with the wrong type. The result may be a cryptic error message that is many pages long. Sadly to say, templates in C++ are known for this. Therefore, classical concepts were planned as one of the great features of C++11. They should allow you to specify constraints for templates that the compiler can verify. Thanks to their complexity, they were removed in July 2009 from the standard: “The C++0x concept design evolved into a monster of complexity.” (Bjarne Stroustrup)
Concepts
With C++20 we will get concepts. Although concepts are in the first implementations of simplified classical concepts, they have much to offer.
They
- empower the programmer to express their requirements as part of the interface directly.
- support the overloading of functions and the specialization of class templates based on the requirements of the template parameters.
- produce drastically improved error messages by comparing the requirements of the template parameter with the applied template arguments.
- can be used as placeholders for generic programming.
- empower you to define your own concepts.
Although concepts are sometimes called concepts lite, their functionality is by no means lite and I can not be presented in one post. Therefore, I will postpone points 4 and 5 to later posts. Promised!
You will benefit without additional compile-time or runtime time of the program. Concepts are similar to Haskell’s type classes. Concepts will describe semantic categories and not syntactic restrictions. For standard library types, we get library concepts such as DefaultConstructible, MoveConstructible, CopyConstructible, MoveAssignable, CopyAssignable, or Destructible. For the containers, we get concepts such as ReversibleContainer, AllocatorAwareContainer, SequenceContainer, ContinousContainer, AssociativeContainer, or UnorderedAssociativeContainer. You can read the about concepts and their constraints here: cppreference.com.
Before I present concepts, let me have a view of Haskell’s type classes.
Type classes in Haskell
Type classes are interfaces for similar types. If a type is a member of a type class, it has to have specific properties. Type classes play a similar role in generic programming as interfaces play in object-oriented programming. Here you can see a part of Haskell’s type classes hierarchy.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
What is unique for a type if it is a member of a type class Eq? Eq stands for equality and requires from its members:
class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool a == b = not (a /= b) a /= b = not (a == b)
Eq requires that its types support the functions equality (==) and inequality (/=). The expression a -> a -> Bool stands for the function’s signature. The function takes two identical types a and returns a Boolean: Bool. But for a concrete type, it is sufficient to implement equality or inequality because equality will be mapped to inequality and vice versa. The default implementations of both functions are provided in the two last lines.
By the following code snipped, the built-in type Bool becomes an instance of the type class Eq.
instance Eq Bool where True == True = True False == False = True _ == _ = False
Haskell’s type classes build a hierarchy. The type class Ord is a subclass of the type class Eq. Therefore, instances of the type class Ord have to be members of the type class Eq and have, in addition, support the comparison operators.
Haskell can automatically create the necessary functions of some class types. Therefore, I can compare the values Morning and Afternoon of the data type day for equality and output them. I have only to derive Day from the type class Eq and Show.
data Day= Morning | Afternoon deriving (Eq,Show)
I can directly test my data type Day in the interactive Haskell Shell. The formal name for the interactive Shell is REPL. Many programming languages, such as Python or Perl, have a REPL. REPL stands for Read Evaluate Print Loop.
Type classes in Haskell have a lot more to offer. For example, you can define your own class.
Concepts for functions, classes, and members of a class
Concepts are part of the template declaration.
Functions
The function template sort requires
template<Sortable Cont> void sort(Cont& container){...}
that the container has to be Sortable. It is also possible to define the requirement for the template parameters more explicitly:
template<typename Cont> requires Sortable<Cont>() void sort(Cont& container){...}
Sortable has to be a constant expression that is a predicate. That means that the expression must be evaluable at compile time and return a boolean.
If you invoke the sort algorithm with a container lst that is not sortable, you will have a unique error message from the compiler.
std::list<int> lst = {1998,2014,2003,2011}; sort(lst); // ERROR: lst is no random-access container with <
You can use concepts for all kinds of templates.
Classes
Therefore, you can define a class template MyVector that will only accept objects as template arguments:
template<Object T> class MyVector{}; MyVector<int> v1; // OK MyVector<int&> v2 // ERROR: int& does not satisfy the constraint Object
Now, the compiler complains that the pointer (int&) is no object. MyClass can be further adjusted.
Members of a class
template<Object T> class MyVector{ ... requires Copyable<T>() void push_back(const T& e); ... };
Now the method push_back from MyVector requires that the template argument has to be copyable.
Extended functionality
A template can have more than one requirement for its template parameters.
More than one requirement
template <SequenceContainer S,EqualityComparable<value_type<S>> T> Iterator_type<S> find(S&& seq, const T& val){...}
The function template find has two requirements. On the one hand, the container has to store its elements in a linear arrangement (SequenceContainer); on the other hand, the elements of the container have to be equality comparable: EqualityComparable<value_type<S>>).
Concepts support the overloading of functions.
Overloading of functions
template<InputIterator I> void advance(I& iter, int n){...} template<BidirectionalIterator I> void advance(I& iter, int n){...} template<RandomAccessIterator I> void advance(I& iter, int n){...} std::list<int> lst{1,2,3,4,5,6,7,8,9}; std::list<int>:: iterator i= lst.begin(); std::advance(i,2); // BidirectionalIterator
The function template advance puts its’s iterator iter n positions further. Different function templates will be applied depending on whether the iterator is a forward, a bi-directional, or a random access iterator. If I use a std::list, the BidirectionalIterator will be chosen.
Concepts also support the specialization of class templates.
The specialization of class templates
template<typename T> class MyVector{}; template<Object T> class MyVector{}; MyVector<int> v1; // Object T MyVector<int&> v2 // typename T
Therefore, the compiler maps MyVector<int&> v2 to the general template in the first line; the compiler maps MyVector<int> v1 on the contrary to the specialization template<Object T> class MyVector{}.
What’s next?
Haskell has the type class Monad. A known instance is the Maybe Monad. Why did I write about that stuff? That is simple. C++17 gets with the data type std::optional a Monad that represents a calculation that can or can not return a result. The details about std::optional will follow in the next post.
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,and Matt Godbolt.
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)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!