Concepts Lite
This is a crosspost from www.ModernesCpp.com.
We stay in the year 2020. We will get with high probability concepts lite. Of course, waterproof statements about the future are difficult 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, but templates in C++ are known for this. Therefore, concepts were planned as one of the great features of C++11. They should allow you to specify constraints for templates that can be verified by the compiler. Thanks to their complexity, the were remove in July 2009 from the standard: "The C++0x concept design evolved into a monster of complexity." (Bjarne Stroustrup)
Concepts lite
With C++20 we will get concepts lite. Although concepts lite are in the first implementations simplified concepts, they have a lot to offer.
They
- empowers programmer to directly express their requirements as part of the interface.
- supports the overloading of functions and the specialisation of class templates based on the requirements of the template parameters.
- produces 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.
- empowers you to define your own concepts.
Although concepts lite are called lite, their functionality is by no means lite and I can not be present them in one post. Therefore, I will postpone the points 4 and 5 to later posts. Promised!
You will get the benefit without additional compile time or runtime time of the program. Concepts lite are similar to Haskells type classes. Concepts lite will describe semantic categories and not syntactic restrictions. For types of the standard library, 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 lite, let me have a view at 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 for generic programming as interfaces play for object-oriented programming. Here you can see a part of Haskell's type classes hierarchy.
What is special 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 have to support the functions equality (==) and inequality (/=). The expression a -> a -> Bool stands for the signature of the function. 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 is able to automatically create the necessary functions of some type classes. 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)
Now 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 type classes.
Concepts lite for functions, classes, and members of a class
Concepts lite 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 to 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 has to be evaluable at compile time and has to return a boolean.
If you invoke the sort algorithm with a container lst that is not sortable, you will be 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 lite for all kind 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 a 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 the one requirements 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. At one hand, the container has to store its elements in a linear arrangement (SequenceContainer), at the other hand the elements of the container has to be equal comparable: EqualityComparable<value_type<S>>).
Concepts lite supports 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. Depending, if the iterator is a forward, a bi-directional of a random access iterator different function templates will be applied. If I use a std::list, the BidirectionalIterator will be chosen.
Concepts lite also support the specialisation of class templates.
Specialisation 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 in the contrary to the specialisation 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 represent a calculation the can or can not return a result. The details about std::optional will follow in the next post.
Ph.D. Computer Science
7 年Am I the only one who sees this as an idea with something in common to traits?