C++ 20 Concepts: part 1 (the basics)

Gajendra Gulgulia
6 min readOct 24, 2021

--

Concepts is one of the 4 major additions in C++20 standard. The idea of concepts has existed as long as C++ templates themselves but it has only been until few years that the foundation of C++ concepts have been laid out in the manner it had earlier been desired desired, i.e., in coherence with C++ templates design principles:

  1. a generalized code
  2. better code than hand written code with zero overhead
  3. well specified interfaces on the template method/class usage.

C++ templates fell short on the last requirements but the first two requirements were good enough and templates few off with a huge success. In simpler terms the last requirement on concepts is about imposing constraints on the template parameters themselves, that a function or a class can accept.

In this article, I’ll give an introductory example of how concepts could be useful in terms of specifying the interface of a generic code, which among many advantages helps get rid of the verbose error message and achieve faster compilation, and consequently help make generic programming more expressive and fun. In the later long running series about concepts with gradually increasing complexity, I’ll present details as and when necessary. Lastly before beginning, I encourage all users to try out the code examples with a compiler that supports C++20 . So lets start with concepts of concepts ;)

1. The problem with classical template program

Consider a trivial generic print method:

#include <iostream>template <typename T>
void print(const T& msg){
std::cout << msg;
}
int main(){
int a{1};
char b{'2'};
std::string msg1{"hello wolrd!"};
print(a);
print(b);
print(msg);

return 0;
}

In the above simple example, one can look at the body of the template-tized print method and immediately figure that the method can print, apart from all fundamental data types, any type T that provides an overloaded operator<<.

For trivial print method it is anyhow obvious. For a more complex method or an algorithm that works on for e.g. STL containers, it might be that the generic method requires the container to provide an overloaded subscript operator ( []) or that the container it uses needs to provide both forward and backward iterators which might not be obvious even from a cursory glance to the body of the template-tized method. In such cases passing a type that doesn’t provide the necessary interface causes the compiler to vomit a huge error message which might be a nightmare even in simple cases, let alone a production environment.

For e.g. , consider a scenario when one does not pay attention to the details of the print method and assumes that it can also print the contents of std::vector .

#include <iostream>
#include <vector>
template<typeneme T>
void print(const T& msg){
std::cout << msg;
}
int main(){
std::vector<int> v{1,2,3,4,5,6};
print(v);
}
// a horrendously long compiler error

which probably will make the programmer run away from generic programming, which might have been the case for a long time for most of us!

source: memegenerator.net

The error is due to the result of compiler trying to generate code to print v , which is of type std::vector<int> and failing to do so since std::vector<T> doesn’t provide an overloaded operator<< in its interface. This error is substitution failure.

2. The solution: concepts

Concepts enable the first step check, whether or not a particular inteface is provided by the type T, before it is substituted during compilation. C++ reference page says the following about concepts (in addition to the gory details):

A concept is a named set of requirements …
The definition of a concept has the form

template <template-parameter-list>
concept concept-name = constraint-expression;

The concept-name and constraint-expression are decided by the server code (or the person who intends the template code to be used by other developers/users). In general the last two lines define the concept. The constraint-expression actually imposes the constrains on the type T and it is in this expression, it is defined what interface the type T should provide. In addition to the concept keyword, C++ provides requires keyword in the core language feature to allow user to define their concepts.

Without going into technical details for the time being, I’ll present the concept for our print method right away and keep the details for next issue. The newly added code is highlighted in bold:

#include <iostream>
#include <vector>
//first define a concept for print
//lets call it 'Printable'
template<typename T>
concept Printable = requires(std::ostream& os, const T& msg)
{
{os << msg};
};
//now impose the constraint define above on print method
template <Printable T>
void print(const T& msg){
std::cout << msg;
}

Now, when the variable v of type std::vector<int> is passed as an argument to print defined above, a much terse error message appears which at first glance does appear to be long, but in my opinion exponentially smaller than the ones without concept based constraint . Second half of the error message looks like below

note: constraints not satisfied:In substitution of 'template<class T>  requires  Printable<T> void print(const T&) [with T = std::vector<int>]':required for the satisfaction of 'Printable<T>' [with T = std::vector<int, std::allocator<int> >]in requirements with 'std::ostream& os', 'const T& msg' [with T = std::vector<int, std::allocator<int> >]the required expression '(os << msg)' is invalid    {os << msg};
~~~^~~~~~

which can be read as: constrains not satisfied in substitution of template type T=std::vector<int> required for satisfaction of (concept) Printable<T> in requirements with std::ostream& os

Here compiler tells clearly that print imposes a constraint on the type T which must satisfy the concept Printable requiring T to provide a suitable interface for std::ostream .

3. A small exercise

If you’ve made it till this section then why not try a small exercise to write a generic code with concept.

  1. Use the same generic print method without the concept first.
template <typename T>
void print(const T& msg){
std::cout << msg;
}

2. Create a class Foo with an int data member, say intData_ and provide a constructor to initialize the member to a desired value

3. Instantiate an object Foo and try to print its value by passing it to the generic print method.

4. Observe the error if you are brave. If not (like me) then ignore it and transform the generic print method to a concept based print method like in section 2 of the article.

5. Again observe the error when trying to print the variable of type Foo .

6. Complete the interface of Foo by providing an overloaded operator<< to print the value of the int data member within Foo .

Conclusion

I hope you enjoyed writing your first C++20 concept (at least enjoyed more compared to the naked generic programming). Stay tuned for more articles on concepts which will gradually provide the nifty details with increasing complexity.

C++20 videos on youtube and course

You can learn more about Concepts from on my youtube channel for concepts. Subscribe to my youtube channel to learn more about coroutines and other advanced topics on C++20 or browse my course page to get access to all C++20 videos, quizzes and personal support.

Support me by becoming a member

If you like my tutorials and articles, please consider supporting me by becoming a member through my medium referral link and get unlimited access to all articles on medium.com for just $5 a month.

--

--

Gajendra Gulgulia
Gajendra Gulgulia

Written by Gajendra Gulgulia

I'm a backend software developer and modern C++ junkie. I run a modern cpp youtube channel (https://www.youtube.com/@masteringmoderncppfeatures6302/videos)

Responses (1)