100% found this document useful (2 votes)
14 views

Programming with C 20 Concepts Coroutines Ranges and more 1st Edition Andreas Fertig download

The document provides information about the book 'Programming with C++20' by Andreas Fertig, which covers new features of C++20 for programmers familiar with C++11. It includes topics such as Concepts, Coroutines, and Ranges, along with practical code examples and improvements in the language. Additionally, it mentions the author's background and resources for downloading code examples.

Uploaded by

kelopolapre
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (2 votes)
14 views

Programming with C 20 Concepts Coroutines Ranges and more 1st Edition Andreas Fertig download

The document provides information about the book 'Programming with C++20' by Andreas Fertig, which covers new features of C++20 for programmers familiar with C++11. It includes topics such as Concepts, Coroutines, and Ranges, along with practical code examples and improvements in the language. Additionally, it mentions the author's background and resources for downloading code examples.

Uploaded by

kelopolapre
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 53

Programming with C 20 Concepts Coroutines Ranges

and more 1st Edition Andreas Fertig install


download

https://ebookmeta.com/product/programming-with-c-20-concepts-
coroutines-ranges-and-more-1st-edition-andreas-fertig/

Download more ebook from https://ebookmeta.com


We believe these products will be a great fit for you. Click
the link to download now, or visit ebookmeta.com
to discover even more!

Pro Cryptography and Cryptanalysis with C 20 Creating


and Programming Advanced Algorithms 1st Edition Marius
Iulian Mihailescu

https://ebookmeta.com/product/pro-cryptography-and-cryptanalysis-
with-c-20-creating-and-programming-advanced-algorithms-1st-
edition-marius-iulian-mihailescu/

Pro Cryptography and Cryptanalysis with C 20 Creating


and Programming Advanced Algorithms 1st Edition Marius
Iulian Mihailescu

https://ebookmeta.com/product/pro-cryptography-and-cryptanalysis-
with-c-20-creating-and-programming-advanced-algorithms-1st-
edition-marius-iulian-mihailescu-2/

Functional Programming with C#: Create more


supportable, robust, and testable code (Second Early
Release) Simon J. Painter

https://ebookmeta.com/product/functional-programming-with-c-
create-more-supportable-robust-and-testable-code-second-early-
release-simon-j-painter/

Media in Process Transformation and Democratic


Transition 1st Edition Sai Felicia Krishna Hensel
Editor

https://ebookmeta.com/product/media-in-process-transformation-
and-democratic-transition-1st-edition-sai-felicia-krishna-hensel-
editor/
The House on Woody Creek Lane 1st Edition Claudine
Marcin

https://ebookmeta.com/product/the-house-on-woody-creek-lane-1st-
edition-claudine-marcin/

Fantastic Schools Christopher G Nuttall Et El

https://ebookmeta.com/product/fantastic-schools-christopher-g-
nuttall-et-el/

Ready for First: Coursebook with key 3rd Edition Roy


Norris

https://ebookmeta.com/product/ready-for-first-coursebook-with-
key-3rd-edition-roy-norris/

Constituent Power: Law, Popular Rule and Politics 1st


Edition Matilda Arvidsson (Editor)

https://ebookmeta.com/product/constituent-power-law-popular-rule-
and-politics-1st-edition-matilda-arvidsson-editor/

Culture and Politics Perez Zagorin

https://ebookmeta.com/product/culture-and-politics-perez-zagorin/
The Labour of Laziness in Twentieth Century American
Literature 1st Edition Zuzanna Ladyga

https://ebookmeta.com/product/the-labour-of-laziness-in-
twentieth-century-american-literature-1st-edition-zuzanna-ladyga/
Programming
with C++20
Concepts, Coroutines,
Ranges, and more

Andreas Fertig
Andreas Fertig

Programming with C++20

Concepts, Coroutines, Ranges, and more

1. Edition
© 2021 Andreas Fertig
https://AndreasFertig.Info
All rights reserved

Bibliographic information published by the Deutsche Nationalbibliothek


The Deutsche Nationalbibliothek lists this publication in the Deutsche Nationalbibliografie; detailed bib-
liographic data are available on the Internet at http://dnb.dnb.de.

The work including all its parts is protected by copyright. Any use outside the limits of the copyright law
requires the prior consent of the author. This applies in particular to copying, editing, translating and
saving and processing in electronic systems.

The reproduction of common names, trade names, trade names, etc. in this work does not justify
the assumption that such names are to be regarded as free within the meaning of the trademark
and trademark protection legislation and therefore may be used by everyone, even without special
identification.

Planning and text:


Andreas Fertig

Cover art and illustrations:


Franziska Panter
https://franziskapanter.com

Published by:
Fertig Publications
https://andreasfertig.info

ISBN: 978-3-949323-01-0

This book is available as ebook at https://leanpub.com/programming-with-cpp20


Thank you, Brigitte & Karl! You opened the world for me. You were
always there, supporting me, letting me find my own way, making me what
I am.

To Franziska, without her, I would not have accomplished this project.


Never tired of reminding me of my talents, driving me when I’m tired, keep-
ing my focus on. A lot of more could be written here. I like to close with:
Thank You!
4
Using Code Examples

This book exists to assist you during your daily job life or hobbies. All examples in
this book are released under the MIT license.
The main reason for the MIT license was to avoid uncertainty. It is a well estab-
lished open source license and comes without many restrictions. That should make
it easy to use it even in closed-source projects. In case you need a dedicated license
or have some questions around it, feel free to contact me.

Code download

The source code for this book’s examples is available at


https://github.com/andreasfertig/programming-with-cpp20

Used Compilers

For those of you who try to test the code and like to know the compiler and revision
I used here you go:

■ g++ 11.1.0

■ clang version 13.0.0


6
About the Author

Andreas Fertig, CEO of Unique Code GmbH, is an experienced trainer and lecturer
for C++ for standards 11 to 20.
Andreas is involved in the C++ standardization committee, in which the new stan-
dards are developed. At international conferences, he presents how code can be writ-
ten better. He publishes specialist articles, e.g., for iX magazine, and has published
several textbooks on C++.
With C++ Insights (https://cppinsights.io), Andreas has created an internationally
recognized tool that enables users to look behind the scenes of C++ and thus to un-
derstand constructs even better.
Before working as a trainer and consultant, he worked for Philips Medizin Sys-
teme GmbH for ten years as a C++ software developer and architect focusing on
embedded systems.
You can find him online at andreasfertig.info and his blog at andreasfertig.blog
8
About the Book

Programming with C++20 teaches programmers with C++ experience the new fea-
tures of C++20 and how to apply them. It does so by assuming C++11 knowledge.
Elements of the standards between C++11 and C++20 will be briefly introduced, if
necessary. However, the focus is on teaching the features of C++20.
You will start with learning about the so-called big four Concepts, Coroutines,
std::ranges, and modules. The big four a followed by smaller yet not less important
features. You will learn about std::format, the new way to format a string in C++.
In chapter 6, you will learn about a new operator, the so-called spaceship operator,
which makes you write less code.
You then will look at various improvements of the language, ensuring more con-
sistency and reducing surprises. You will learn how lambdas improved in C++20 and
what new elements you can now pass as non-type template parameters. Your next
stop is the improvements to the STL.
Of course, you will not end this book without learning about what happened in
the constexpr-world.

Style and conventions

The following shows the execution of a program. I used the Linux way here and
skipped supplying the desired output name, resulting in a.out as the program name.

$ ./a.out
Output

Hello, Programming with C++20!


10

■ <string> stands for a header file with the name string

■ [[xyz]] marks a C++ attribute with the name xyz.

From time to time, I use an element from a previous standard after C++11. I ex-
plain these elements in dedicated standard boxes such as the following:
0.1 C++14: A sample element

A sample element from C++14.

These boxes carry the standard in which this element was introduced and are num-
bered such that I can reference them like this: Std-Box 0.1.
All listings are numbered and sometimes come with annotations which I refer to
like this A .
References carry a page number in case the reference isn’t on the same page. For
example, Std-Box 0.1 has no page number because it appears on the same page.

Feedback

This book is published on Leanpub (https://leanpub.com/programming-with-


cpp20) as a digital version. A full-color and a grayscale paperback version are
available on Amazon. It helps if you indicate the book type you’re referring to.

In any case, I appreciate your feedback. Please report it to me, whether it is a


typo, a grammatical issue, naming of variables and functions, or logical things. You
can send your feedback to books@andreasfertig.info.

Thank you

I like to say thank you to everyone who reviewed drafts for this book and gave me
valuable feedback. Thank you! All this feedback helped to improve the book. Here
is a list of people who provided feedback: Vladimir Krivopalov, Hristiyan Nevelinov,
John Plaice, Peter Sommerlad, Salim Pamukcu, and others.
About the Book 11

A special thanks goes to Fran Buontempo, editor of ACCU’s Overload magazine.


She provided extensive feedback from the beginning and was never tired of pointing
out some grammar issues along with poking to the base of my code examples, helping
me make them better.

Revision History

2021-01-30: First release (Leanpub)


2021-04-22: Various spelling and grammar updates due to feedback. Added chapter
9. (Leanpub)
2021-10-01: Added chapter 3. (Leanpub)
2021-10-15: Added chapter 10. (Leanpub)
2021-10-18: Various spelling and grammar updates due to feedback. Fixed listing
numbers. Better styling of boxes (Leanpub)
2021-10-25: Added chapter 4. Various layout improvements. (Leanpub)
2021-10-27: Added chapter 11. Various layout improvements. (Leanpub)
2021-11-10: Added chapter 12, rewrote chapter 1. Various layout improvements.
(Leanpub)
2021-11-26: Full release (Leanpub, Amazon)
12
Table of Contents

1 Concepts: Predicates for strongly typed generic code 17


1.1 Programming before Concepts . . . . . . . . . . . . . . . . . . . 18
1.2 Start using Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.3 Application areas for Concepts . . . . . . . . . . . . . . . . . . . 22
1.4 The requires-expression: The runway for Concepts . . . . . . 23
1.5 Requirement kinds in a requires-expression . . . . . . . . . . . 25
1.6 Ad hoc constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1.7 Defining a concept . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
1.8 Testing requirements . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1.9 Abbreviated function template with auto as a generic pa-
rameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
1.10 Using a constexpr function in a concept . . . . . . . . . . . . . 36
1.11 Concepts and constrained auto types . . . . . . . . . . . . . . . 38
1.12 The power of Concepts: requires instead of enable_if . . . 40
1.13 Concepts ordering . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
1.14 Improved error message . . . . . . . . . . . . . . . . . . . . . . . 56
1.15 Existing Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

2 Coroutines: Suspending functions 65


2.1 Regular functions and their control flow . . . . . . . . . . . . . 65
2.2 What are Coroutines . . . . . . . . . . . . . . . . . . . . . . . . . . 67
2.3 The elements of Coroutines in C++ . . . . . . . . . . . . . . . . 69
2.4 Writing a byte-stream parser the old way . . . . . . . . . . . . 79
2.5 A byte-stream parser with Coroutines . . . . . . . . . . . . . . 82
14

2.6 A different strategy of the Parse generator . . . . . . . . . . . 91


2.7 Using a coroutine with custom new / delete . . . . . . . . . . . 99
2.8 Using a coroutine with a custom allocator . . . . . . . . . . . . 101
2.9 Exceptions in coroutines . . . . . . . . . . . . . . . . . . . . . . . 104

3 Ranges: The next generation STL 107


3.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
3.2 The who is who of ranges . . . . . . . . . . . . . . . . . . . . . . . 114
3.3 A range . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
3.4 A range algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
3.5 A view into a range . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
3.6 A range adaptor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
3.7 The new ranges namespaces . . . . . . . . . . . . . . . . . . . . . 122
3.8 Ranges Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
3.9 Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
3.10 Creating a custom range . . . . . . . . . . . . . . . . . . . . . . . 124

4 Modules: The superior way of includes 131


4.1 Background about the need for modules . . . . . . . . . . . . 131
4.2 Creating modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
4.3 Applying modules to an existing code base . . . . . . . . . . . 137

5 std::format: Modern & type­safe text formatting 145


5.1 Formatting a string before C++20 . . . . . . . . . . . . . . . . . 145
5.2 Formatting a string using std::format . . . . . . . . . . . . . . 153
5.3 Formatting a custom type . . . . . . . . . . . . . . . . . . . . . . 158
5.4 Referring to a format argument . . . . . . . . . . . . . . . . . . . 165
5.5 Using a custom buffer . . . . . . . . . . . . . . . . . . . . . . . . . 166
5.6 Writing our own logging function . . . . . . . . . . . . . . . . . 168

6 Three­way comparisons: Simplify your comparisons 175


6.1 Writing a class with equal comparison . . . . . . . . . . . . . . 176
6.2 Writing a class with ordering comparison, pre C++20 . . . . 180
6.3 Writing a class with ordering comparison in C++20 . . . . . 183
6.4 The different comparison categories . . . . . . . . . . . . . . . 186
About the Book 15

6.5 Converting between comparison categories . . . . . . . . . . 190


6.6 New operator abilities: reverse and rewrite . . . . . . . . . . . 192
6.7 The power of the default spaceship . . . . . . . . . . . . . . . . 193
6.8 Applying a custom sort order . . . . . . . . . . . . . . . . . . . . 196
6.9 Spaceship-operation interaction with existing code . . . . . 197

7 Lambdas in C++20: New features 201


7.1 [=, this] as a lambda capture . . . . . . . . . . . . . . . . . . . . . 201
7.2 Default-constructible lambdas . . . . . . . . . . . . . . . . . . . 205
7.3 Captureless lambdas in unevaluated contexts . . . . . . . . . 207
7.4 Lambdas in generic code . . . . . . . . . . . . . . . . . . . . . . . 209
7.5 Pack expansions in lambda init-captures . . . . . . . . . . . . . 214
7.6 Restricting lambdas with Concepts . . . . . . . . . . . . . . . . 218

8 Aggregates: Designated initializers and more 223


8.1 What is an aggregate . . . . . . . . . . . . . . . . . . . . . . . . . 223
8.2 Designated initializers . . . . . . . . . . . . . . . . . . . . . . . . . 224
8.3 Direct-initialization for aggregates . . . . . . . . . . . . . . . . . 235
8.4 Class Template Argument Deduction for aggregates . . . . . 241

9 Class­types as non­type template parameters 249


9.1 What are non-type template parameters again . . . . . . . . 249
9.2 The requirements for class types as non-type template pa-
rameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
9.3 Class types as non-type template parameters . . . . . . . . . 252
9.4 Building a format function with specifier count check . . . . 255

10 New STL elements 267


10.1 bit_cast: Reinterpreting your objects . . . . . . . . . . . . . . 267
10.2 endian: Endianness detection at compile time . . . . . . . . . 270
10.3 to_array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
10.4 span: A view of continuous memory . . . . . . . . . . . . . . . . 274
10.5 source_location: The modern way for __FUNCTION__ . . . . . 277
10.6 contains for all associative containers . . . . . . . . . . . . . . 284
10.7 starts_with and ends_with for std::string . . . . . . . . . . . 286
16

11 Language Updates 289


11.1 Range-based for-loops with initializers . . . . . . . . . . . . . . 289
11.2 New Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
11.3 using enums . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
11.4 conditional explicit . . . . . . . . . . . . . . . . . . . . . . . . . . 298

12 Doing (more) things at compile­time 301


12.1 The two worlds: compile- vs. run-time . . . . . . . . . . . . . . 301
12.2 is_constant_evaluated: Is this a constexpr-context? . . . . . 305
12.3 Less restrictive constexpr-function requirements . . . . . . . 309
12.4 Utilizing the new compile-time world: Sketching a car rac-
ing game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
12.5 consteval: Do things guaranteed at compile-time . . . . . . 315
12.6 constinit: Initialize a non-const object at compile-time . . 321

Acronyms 327

Bibliography 329

Index 331
Chapter 1

Concepts:
Predicates for strongly typed
generic code

Templates have been with C++ since the early beginnings. Recent standard updates
have added new facilities, such as variadic template. Templates enable Generic Pro-
gramming (GP), the idea of abstracting concrete algorithms to get generic algorithms.
They can then be combined and used with different types to produce a wide variety
of software without providing a dedicated algorithm for each type. GP or Template
Meta-Programming (TMP) are powerful tools. For example, the Standard Template
Library (STL) heavily uses them.
However, template code has always been a bit, well, clumsy. When we write a
generic function, we only need to write the function once; then it can be applied to
various different types. Sadly, when using the template with an unsupported type,
finding any error requires a good understanding of the compiler’s error message.
All we needed to face such a compiler error message was a missing operator< that
wasn’t defined for the type. The issue was that we had no way of specifying the re-
quirements to prevent the misuse and at the same time give a clear error message.
18

Concepts vs concepts vs concepts

This chapter comes with an additional challenge. The language feature we will discuss in this
chapter is called Concepts. We can also define a concept ourselves, and there is a concept key-
word. When I refer to the feature itself, it is spelled with a capital C, Concepts. The lower case
version is used when I refer to a single concept definition, and the code-font version concept
refers to the keyword.

1.1 Programming before Concepts

Let’s consider a simple generic Add function. This function should be able to add an
arbitrary number of values passed to it and return the result. Much like this:
1 const int x = Add(2,3,4,5);
2 const int y = Add(2,3);
3 const int z = Add(2, 3.0); A This should not compile

While the first two calls to Add, x and y, are fine, the third call should result in a
compile error. With A we are looking at implicit conversions, namely a promotion
from int to double because of 3.0. Implicit conversions can be a good thing, but in
this case, I prefer explicitness over the risk of loss of precision. Here Add should only
accept an arbitrary number of values of the same data type.
To make the implementation a little more challenging, let’s say that we don’t want
a static_assert in Add, which checks that all parameters are of the same type. We
would like to have the option of providing an overload to Add that could handle cer-
tain cases of integer promotions.
To see the power of Concepts, we start with an implementation in C++17. For
the implementation of Add, we obviously need a variadic function template as well
as a couple of helpers. The implementation I present here requires two helpers,
are_same_v and first_arg_t. You can see the implementation in Listing 1.1.

1 template<typename T, typename... Ts>


Listing 1.1

2 constexpr inline bool are_same_v =


3 std::conjunction_v<std::is_same<T, Ts>...>;
4
Chapter 1: Concepts: Predicates for strongly typed generic code 19

5 template<typename T, typename...>
6 struct first_arg {
7 using type = T;

Listing 1.1
8 };
9

10 template<typename... Args>
11 using first_arg_t = typename first_arg<Args...>::type;

The job of are_same_v, which is a C++14 variable template ( Std-Box 1.1), is to


ensure, with the help of the type-traits std::is_same and std::conjunction_v,
that all types in the parameter pack passed to are_same_v are the same. For that, the
variable template uses the usual trick of splitting up the first argument from the pack
and comparing all other arguments against this first one.
1.1 C++14: Variable templates

Variable templates were introduced with C++14. They allow us to define a variable, which is a
template. This feature allows us to have generic constants like π :

Listing 1.2
1 template<typename T>
2 constexpr T pi(3.14);

One other use case is to make TMP more readable. Whenever we had a type-trait that had a value
we wanted to access, before C++14, we needed to do this: std::is_same<T, int>::value
. Admittingly, the ::value part was not very appealing. Variable templates allow the value of
::value to be stored in a variable.

Listing 1.3
1 template<typename T, typename U>
2 constexpr bool is_same_v = std::is_same<T, U>::value;

With that, the shorter and more readable version is is_same_v<T, int>. Whenever you see a
_v together with a type-trait, you’re looking at a variable template.

Our second helper, first_arg_t, uses a similar trick. It extracts the first type
from a pack and stores thing one in a using-alias. That way, we have access to the
first data type in a parameter pack, and since we later ensure that all types in the
pack are the same, this first type is as good as that from any other index choice in the
parameter pack.
20

Great, now that we have our helpers in place, let’s implement Add. Listing 1.4
provides an implementation using C++17.

1 template<typename... Args>
2 std::enable_if_t<are_same_v<Args...>, first_arg_t<Args...>>

Listing 1.4
3 Add(const Args&... args) noexcept
4 {
5 return (... + args);
6 }

In this implementation, as promised, Add is a variadic function template which we


quickly spot looking at the template head. If we go down two lines, we can see the
name of the function, Add, and that it takes the parameter pack as a const &.
The body of Add reveals that I use C++17’s fold expressions ( Std-Box 1.2) to apply
the plus operation to all the elements in the parameter pack.
1.2 C++17: Fold expressions

Before C++17, whenever we had a parameter pack, we needed to recursively call the function
which received the pack and split up the first parameter. That way, we could traverse a parameter
pack. C++17 allows us to apply an operation to all elements in the pack. For example, int
result = (... + args); applies the + operation to all elements in the pack. Assuming that
the pack consists of three objects, this example will produce int result = arg0 + arg1 +
arg2;. This is much shorter to write than the recursive version. That one needs to be terminated
at some point. With fold expressions, this is done automatically by the compiler. We can use other
operations instead of +, like −, /, ∗, and so on.
The important thing to realize about fold expressions is that it is only a fold expression if the pack
expansion has parentheses around it and an operator like +.

So far, I hope that’s all understandable. The part I heavily object to, despite that
it is my own code, is the line with the enable_if_t. Yes, it is the state-of-the-art
enable_if because, with the _t, we don’t need to say typename in front of it. How-
ever, this single line is very hard to read and understand. Depending on your knowl-
edge of C++, it can be easy, but remember the days when you started with C++. There
is a lot that one has to learn to understand this single line.
The first part, or argument, is the condition. Here we pass are_same_v. Should
this condition be true, the next parameter gets enabled, which is first_arg_t. This
then becomes the return type of Add. Right, did you also miss the return type initially?
Chapter 1: Concepts: Predicates for strongly typed generic code 21

Should the condition be false, then this entire expression isn’t instantiable, we speak
of substitution failure is not an error (SFINAE) as the technique used here, and this
version of Add isn’t used for lookups by the compilers. The result is that we can end
up with page-long error messages where the compiler informs us about each and
every overload of Add it tried.
One more subtle thing is that in this case, enable_if does something slightly dif-
ferent than just enabling or disabling things. It tells us the requirements for this func-
tion. Yet, the name enable_if doesn’t give many clues about that.
All these things are reasons why people might find templates tremendously diffi-
cult to process. But, yes, I know, those who stayed accommodated to all these short-
comings.
Now it is time to see how things change with C++20.

1.2 Start using Concepts

Sticking with the initial example, we ignore the helpers, as they stay the same. List-
ing 1.5 presents the C++20 implementation of Add.

1 template<typename... Args>
2 A Requires-clause using are_same_v to ensure all Args are of the same
type.

Listing 1.5
3 requires are_same_v<Args...>
4 auto Add(Args&&... args) noexcept
5 {
6 return (... + args);
7 }

Here we can see that Add remains a variadic function template, probably not the
biggest surprise. Let’s again skip two lines and go to the definition of Add. What first
springs into our eyes is the return type. I chose auto. But the important thing is that
the return type is there! The rest of the function’s signature, as well as the function
body, are unchanged. I see this return type as the first win. Before the enable_if
obfuscated the return type.
22

The biggest improvement is the line that says requires. Isn’t that what’s really
going on here? This function Add requires that are_same_v is true. That’s all. I find
that pretty easy to read. The intent is clearly expressed without obfuscating anything
or requiring weird tricks. Okay, maybe we have to look up what are_same_v does,
but I can live with that.
We are looking at one of the building blocks of Concepts in Listing 1.5 on page 21,
the requires-clause.

1.3 Application areas for Concepts

Before we talk about how we can great Concepts are, let’s first see where we can apply
them. Figure 1.1 on page 23 lists all the places in a template declaration where we
can apply Concepts.
We see a type-constraint in C1. In this place, we can only use Concepts. We can use
a type-constraint instead of either class or typename in a template-head to state as
early as possible that this template takes a type deduced by the compiler, but it must
meet some requirements.
The next option is with C2, using a requires-clause. We already applied that in our
Add example in Listing 1.5 on page 21. In a requires-clause, we can use either con-
cepts or type-traits. The expression following the requires must return a boolean
value at compile time. If that value is true, the requirement(s) is (are) fulfilled.
The two places of C3 and C4 are similar. They both apply to placeholder types
constraining them. We can also use Concepts to constrain auto variables, which we
will see later. A constraint placeholder type works only with Concepts. Type-traits
are not allowed. In C4, we see something that you might already know from C++14’s
generic lambdas, Std-Box 7.1 on page 210, auto as a parameter type. Since C++20,
they are no longer limited to generic lambdas.
At the end, we have the trailing requires-clause. This one is similar to the requires-
clause. We can use Concepts or type-traits and can use boolean logic to combine
them. Table 1.1 on page 23 gives a guidance when to use which constraint form.
Chapter 1: Concepts: Predicates for strongly typed generic code 23

type-constraint

requires-clause
template<C1 T>
requires C2<T>
C3 auto Fun(C4 auto param) requires C5<T>

trailing requires-clause
constrained placeholder type

Figure 1.1: The different places where we can constrain a template or template argument.

Table 1.1: When to use which constraint form

Type When to use

C1 type-constraint Use this when you already know that a template type
parameter has a certain constraint. For example, not all
types are allowed. In Figure 1.1 the type is limited to a
floating-point type.

C2 requires-clause Use this when you need to add constraints for multiple
template type or non-type template parameters.

C5 trailing requires-clause Use this for a method in a class template to constrain it


based on the class template parameters.

1.4 The requires­expression: The runway for Concepts

We’ve already seen the two forms of a requires-clause. It is time to look at our Add
example again and see what we can improve with the help of Concepts.
The current implementation of Add only prevents mixed types. Let’s call this re-
quirement A of Add. By that, the implementation leaves a lot unspecified:

B Add can nonsensically be called with only one parameter. The function’s name,
on the other hand, implies that things are added together. It would make more
sense if Add would also require to be called with at least two parameters. Ev-
erything else is a performance waste.
24

C The type used in Args must support the + operation. This is a very subtle re-
quirement that harshly yells at us once we violate it. It is also a design choice of
the implementor of Add. Instead of operator+, one could also require that the
type comes with a member function Addition. That would, of course, rule out
built-in types. Should we miss that, we again get these page-long errors that
are hard to see through. Only documentation helps at this point, and documen-
tation over time may disagree with the implementation. In such a case, I prefer
a check by the compiler over documentation.

D The operation + should be noexcept, since Add itself is noexcept. Did you
spot that initially? The implementation of Add in Listing 1.5 on page 21 and
before was always marked noexcept. Why? Because it mainly adds num-
bers, and I don’t want to have a try-catch-block around something like 3 + 4.
But since Add is a generic function, it also works with, for example, a std::
string, which can throw an exception. Writing a check for the noexceptness
of operator+ pre C++17 is an interesting exercise.

E The return type of operation + should match that of Args. Another interesting
and often overlooked requirement. It is surprising should operator+ of type
T return a type U. Sometimes, there are good reasons for such a behavior, but
is doesn’t seem plausible in the case of Add. Let’s restrict this as well.

To implement all these requirements above the standard provides us with a


requires-expression. Figure 1.2 on page 25 shows how a requires-expression looks.
I read it like a constructor of type requires. You can also see it as a function
without a return type. After the name requires, which is always the same, we have
the optional parameter list. Like in a regular function, this parameter list can be
empty or can have entries. It is important to understand that a requires-expression
is only used at compile-time to evaluate the different requirements it brings. It never
enters our binary.
The parameters we can define in the parameter list of a requires-expression can
consist of any type that is available, like int, char, std::string, or custom classes
like MyClass. Because a requires-expression is always used in some kind of template
context, we can also refer to template arguments, as Figure 1.2 on page 25 shows
with T and U.
Chapter 1: Concepts: Predicates for strongly typed generic code 25

Parameter list of the


requires-expression.

requires(T t, U u)
{ Body of the
// some requirements
requires-expression
}

One or multiple requirements.

Figure 1.2: Parts of a requires-expression.

We are totally free when it comes to the qualifiers of these types. We can say that
a requires-expression takes a const T& or a const T*. Well, we can even start en-
tering the east and west const debate. Shaping these parameters helps us later when
we refer to them in the body of a requires-expression which we will see in §1.12.1 on
page 40.
Next in a requires-expression is the body, like with a function. This body comes
with a requirement itself. It must contain at least one requirement.
The difference between a requires­clause and a requires­expression

A requires-clause (C3, C5) is a boolean expression. A requires-expression is more complicated.


Much like we know it from noexcept. We can say that a function is noexcept, but we can also
query whether a function is noexcept.
We can see a requires-clause like an if that evaluates a boolean expression, and a requires-
expression returns such a boolean value.

1.5 Requirement kinds in a requires­expression

The body of a requires-expression can use four different requirements:

■ Simple requirement (SR)

■ Nested requirement (NR)


26

■ Compound requirement (CR)


■ Type requirement (TR)
The parenthesized letter pairs are for reference in the following code examples.
We will explore the first three requirements using Add, and the requirements we
established in §1.4 on page 23. The type requirement doesn’t make sense for Add. It
is explained without using Add.

1.5.1 The simple requirement

As the name implies, a simple requirement checks for a simple thing, namely whether
a certain statement is valid. For the Add example, Listing 1.6 illustrates a simple
requirement that checks whether the fold expression used in the body of Add is valid.

1 requires(Args... args)

Listing 1.6
2 {
3 (... + args); C SR: args provides +
4 }

This check ensures that the type passed to Add provides operator+. We just have
to check our first requirement C .

1.5.2 The nested requirement

Having successfully checked our requirement C brings us to the next requirement,


the nested requirement. With this requirement kind, we can implement our require-
ments A and B, as Listing 1.7 shows. A nested requirement can nest all the other
requirement types. You can see it as a way to start a new requires-clause inside a
requires-expression.

1 requires(Args... args)
2 {
3 (... + args); C SR: args provides +
Listing 1.7

4 requires are_same_v<Args...>; A NR: All types are the same


5 requires sizeof...(Args) > 1; B NR: Pack contains at least two
elements
6 }
Chapter 1: Concepts: Predicates for strongly typed generic code 27

A nested requirement evaluates the return value of an expression. In the case of B ,


the nested requirement ensures that there are at least two elements in the parameter
pack. The requires in front of each nested requirement is crucial. Without this
requires, we look at a simple requirement. As great as a simple requirement is, at
this point, it’s the wrong tool. A simple requirement would, in the case of B , always
return true regardless of the number of elements in the parameter pack. Only the
requires in front makes it a nested requirement. This is a trap you should not fall
into.
One important remark is that all parameters we define in the optional parame-
ter list of the requires-expression should only appear as unevaluated operands. That
means that these optional local parameters can be used in a sizeof or decltype ex-
pression. Still, we cannot pass them, for example, to a constexpr function, because
even in this case, they would be evaluated.

1.5.3 The compound requirement

The two last requirements for the function Add can be checked with a compound
requirement. We can check two things with a compound requirement, the return
type of an expression and the noexceptness of that expression. Listing 1.8 shows the
compound requirement for the requirements D and E of Add.

1 requires(Args... args)
2 {
3 (... + args); C SR: args provides +
4 requires are_same_v<Args...>; A NR: All types are the same
5 requires sizeof...(Args) > 1; B NR: Pack contains at least two
elements
Listing 1.8

7 D E CR: ...+args is noexcept and the return type is the same as the
first argument type
8 // { (... + args) } noexcept;
9 // { (... + args) } -> same_as<first_arg_t<Args...>>;
10 { (... + args) } noexcept -> same_as<first_arg_t<Args...>>;
11 }
28

Probably no surprise, a compound requirement uses curly braces to form a scope


or compound statement. Here we again see Add’s fold expression. I dislike this repe-
tition, but it comes mainly from the fact that the Add example is trivial. Next to the
fold expression in the compound statement, we see noexcept. This tells the compiler
to check whether the expression in the curly braces is noexcept. The compound re-
quirement yields false should that be not true. With that, we have a very easy way to
check the noexceptness of an expression.

Table 1.2: The four different kinds of requires-expression s

Code Requires­ Description


expression
kind

(... + args); Simple Asserts that the


requirement operation a + b is
possible.

requires are_same_v<Args...>; Nested Asserts that all types of


requirement the pack Args are of the
same type.

{ (... + args) } noexcept; Compound Asserts that the plus


requirement operation is noexcept.

{ (... + args) } -> same_as<U>; Compound Asserts that the return


requirement type of the plus
operation as the same as
U.

{ (... + args) } noexcept -> same_as<U>; Compound Combination of the


requirement former two compound
requirements. Asserts
that the return type of
the plus operation as the
same as U and the
operation is noexcept.

After noexcept, we see a token sequence that looks like a trailing return type, and
we can read it as such. This trailing return type-like arrow is followed by a concept.
At this point, we must use a concept. Type-traits won’t work at this place. The con-
Chapter 1: Concepts: Predicates for strongly typed generic code 29

cept you can see is same_as from the STL. Its purpose is to compare two types and
check whether they are the same. If you look at Listing 1.8 on page 27 closely, you
can see that I pass only one argument to same_as, the resulting type of first_arg_t.
So, where is the second parameter? The answer is, the compiler injects as the first
parameter the resulting type from the compound statement at the beginning of the
line. Pretty handy, right?
Basically, the form of the compound requirement as presented here does two
checks in one. It checks the noexcept state of the expression and the resulting
type. We can split this into two steps and check for noexcept, simply but omitting
everything after noexcept. Then we can do the return type check in the second
check by striking noexcept from the line as presented. I prefer having both checks
in a single statement.
Table 1.2 on page 28 captures all four kinds of requires-expression s in a compact
manner.
Fine, at this point, we have created a requires expression that checks all the re-
quirements of Add we established in §1.4 on page 23. Next, we look at how to attach
this requires expression function Add.

1.5.4 The type requirement

The last variant of requirement we can have in a requires-expression is the type re-
quirement. This type of requirement asserts that a certain type is valid. Listing 1.9
defines a concept containerTypes that checks that a given type T provides all the
types that allocating containers of the STL in C++ usually provide.

1 template<typename T>
2 concept containerTypes = requires(T t)
3 { A Testing for various types in T
4 typename T::value_type;
Listing 1.9

5 typename T::size_type;
6 typename T::allocator_type;
7 typename T::iterator;
8 typename T::const_iterator;
9 };
30

Here you can see that a type requirement always starts with typename. Should we
leave the typename out, we are back at a simple requirement.

1.6 Ad hoc constraints

The easiest form is to attach the requires expression built in §1.5 on page 25 to the
requires clause of Add, as Listing 1.10 shows.

1 template<typename... Args>
2 requires requires(Args... args)
3 {
4 (... + args);
5 requires are_same_v<Args...>;

Listing 1.10
6 requires sizeof...(Args) > 1;
7 { (... + args) } noexcept -> same_as<first_arg_t<Args...>>;
8 }
9 auto Add(Args&&... args)
10 {
11 return (... + args);
12 }

The grey part is the code we developed in §1.5 on page 25, the requires expres-
sion. You can see that the first line of the requires expression, where it starts with
requires, has a requires in front of it. So we have requires requires. What we
are looking at here is a so-called ad hoc constraint. The first requires introduced
the requires clause, C2 in Figure 1.1 on page 23, while the second starts the requires
expression. Instead of C2, we can, of course, also attach the requires expression to
the trailing requires clause, which is C5 in Figure 1.1 on page 23.
While an ad hoc constraint is handy, it is also the first sign of a code-smell. We
just spent some time developing the requires expression, but all we can do is to use
it in one place. Yes, I assume copy and paste is out of the question. The requires
expression for Add might be something special that only applies to Add. In that case, it
is fine to use it in an ad hoc constraint, but this isn’t true for most of the requirements.
Always think twice before using an ad hoc constraint.
Chapter 1: Concepts: Predicates for strongly typed generic code 31

template-head

template<typename T, typename U>


concept MyConcept = std::same_as<T, U> &&
(std::is_class_v<T>
|| std::is_enum_v<T>);
concept name
requirements

Figure 1.3: The elements of a concept.

How can we do better? Did you notice that I haven’t shown you Concepts yet?
Only building blocks to concepts and application areas. Now that we have a requires
expression, let’s start creating a Concept with it.

1.7 Defining a concept

We continue with Add and use the requires expression from §1.5 on page 25 for
building our first concept. Figure 1.3 illustrates the components of a concept.
A concept always starts with a template-head. The reason is that concepts are pred-
icates in generic code, which makes them templates. The template-head of a concept
comes with the same power and limitations as that of any other function or class
template. We can use type and non-type template parameters (NTTPs) parameters,
class or typename, or a concept to declare a template type parameter.
After the template-head, Figure 1.3 shows the new keyword concept. This starts
the concept and tells the compiler that this is the definition of a concept and not, for
example, a variable template.
Of course, a concept must have a name. I picked MyConcept, yes I know, a great
name. After the concept name, we see the equal sign followed by requirements. We
assign these requirements to our concept MyConcept. As you can see, these require-
ments can be put together using boolean algebra. Figure 1.3 also shows that we can
use either concepts or type-traits as requirements.
32

With that knowledge, we can look at Listing 1.10 on page 30, which uses the
requires expression for Add and attaches it this time to a concept called Addable.

1 template<typename... Args>
2 concept Addable = requires(Args... args)
3 {
4 (... + args);
5 requires are_same_v<Args...>;
6 requires sizeof...(Args) > 1;
7 { (... + args) } noexcept -> same_as<first_arg_t<Args...>>;

Listing 1.11
8 };
9

10 template<typename... Args>
11 requires Addable<Args...>
12 auto Add(Args&&... args)
13 {
14 return (... + args);
15 }

Once again, the grey part is the requires expression from §1.5 on page 25. Aside
from the concept Addable, Listing 1.11 shows how Add itself changes by using the
concept. We use Addable now in the requires clause of Add. This helps make Add
more readable, which I find a valuable improvement. The other part is that Addable
is now reusable. We use it with other functions then Add with a similar requirement.

1.8 Testing requirements

So far, we have looked at the different requirement kinds, how to apply them, and
how they fit into a requires clause or to a concept. It is time to talk about how to
verify our requirements or concepts. As I previously said, we will spend a lot of time
in the future developing concepts, so we should also be able to test them like usual
code.
The good thing about testing concepts is that we already have all the necessities in
place. Remember, concepts only live at compile time. We have a tool to check things
at compile time with static_assert, no need to check out a testing framework.
Chapter 1: Concepts: Predicates for strongly typed generic code 33

We keep using Add or, better, the concept we created Addable. To test the vari-
ous combinations, a type must be valid for Addable (or, of course, invalid). I prefer
creating a stub that mocks the different types. Such a stub is shown in Listing 1.12.

1 A Class template stub to create the different needed properties


2 template<bool nexcept, bool operatorPlus, bool validReturnType>
3 struct Stub {
4 B Operator plus with controlled noexcept can be enabled
5 Stub& operator+(const Stub& rhs) noexcept(nexcept)
6 requires(operatorPlus && validReturnType)
7 { return *this; }
8

9 C Operator plus with invalid return type

Listing 1.12
10 int operator+(const Stub& rhs) noexcept(nexcept)
11 requires(operatorPlus && not validReturnType)
12 { return {}; }
13 };
14

15 D Create the different stubs from the class template


16 using NoAdd = Stub<true, false, true>;
17 using ValidClass = Stub<true, true, true>;
18 using NotNoexcept = Stub<false, true, true>;
19 using DifferentReturnType = Stub<true, true, false>;

Here Stub is a class template with three NTTPs A . The first one, nexcept, con-
trols whether the implementation of operator+ in Stub is noexcept. The second
parameter, operatorPlus, uses a trailing requires clause on operator+ in Stub
for enabling or disabling the operator. Last, validReturnType decides whether the
return type of operator+ is Stub, and by that, valid according to our requirements
for Addable, or int. The choice for int is arbitrary. All that’s needed is something
different than Stub.
At the bottom of Listing 1.12 at D , you can see more meaningful using aliases for
the different parameter combinations of Stub. For example, I cannot easily recall
what Stub<true, false, true> does, but I understand that ValidClass implies
that this type should be accepted by Addable.
34

Well, that’s it, at least the mocking part. With that we have everything we need to
start writing tests, which Listing 1.13 shows.

1 A Assert that mixed types are not allowed


2 static_assert(not Addable<int, double>);
3

4 B Assert that Add is used with at least two parameters


5 static_assert(not Addable<int>);
6

7 C Assert that type has operator+


8 static_assert(Addable<int, int>);

Listing 1.13
9 static_assert(Addable<ValidClass, ValidClass>);
10 static_assert(not Addable<NoAdd, NoAdd>);
11

12 D Assert that operator+ is noexcept


13 static_assert(not Addable<NotNoexcept, NotNoexcept>);
14

15 E Assert that operator+ returns the same type


16 static_assert(
17 not Addable<DifferentReturnType, DifferentReturnType>);

Here static_assert is used to test all the various combinations of valid and in-
valid types for Addable. Fantastic, isn’t it? All in our favorite language, all without
macros, all without any external testing framework.

1.9 Abbreviated function template with auto as a generic


parameter

We have seen the three places where we can use a concept in a function template to
constrain a template parameter. C++20 opened the door for GP to look more like
regular programming. We can use a concept in the so-called abbreviated function
template syntax. This syntax comes without a template-head, making the function
terse. Instead, we declare what looks like a regular function, using the concept as a
parameter type, together with auto.
Chapter 1: Concepts: Predicates for strongly typed generic code 35

1.9.1 What does such a construct do?

In the background, the compiler creates a function template for us. Each concept pa-
rameter becomes an individual template parameter, to which the associated concept
is applied as a constraint. This makes this syntax a shorthand for writing function
templates. The abbreviated function template syntax makes function templates less
scary and looks more like regular programming. The auto in such a function’s sig-
nature is a sign that we are looking at a template.
The abbreviated function template syntax can be a little terser. The constraining
concept is optional. We can indeed declare a function, with only auto parameters.
This way, C++20 allows us to create a function template in a very comprehensive
way.

1.9.2 Exemplary use case: Requiring a parameter type to be an invocable

The abbreviated syntax, together with Concepts, enables us to write less but more
precise code. Assume a system in which certain operations need to acquire a lock
before performing an operation. An example is a file system operation with multi-
ple processes trying to write data onto the file system. As only one can write at a
time because of control structures that have to be maintained, such a write opera-
tion is often locked by a mutex or a spinlock. Thanks to C++11’s lambdas, we can
write a DoLocked function template that takes a lambda as an argument. In its body,
DoLocked first acquires a global mutex globalOsMutex, using a std::lock_guard
to release the mutex after leaving the scope. Then in the next line, the lambda itself is
executed, safely locked without each user needing to know which mutex to use. Plus,
the scope is limited and, thanks to std::lock_guard, the mutex is automatically
released. Deadlocks should no longer happen.

1 template<typename T>
2 void DoLocked(T&& f)
3 {
Listing 1.14

4 std::lock_guard lck{globalOsMutex};
5

6 f();
7 }
36

I have used this pattern in different variations in many places, but there is one
thing that I have always disliked. Can you guess what? Correct, typename T. It is not
obvious to a user that DoLocked requires some kind of callable, a lambda, a function
object, or a function. Plus, for some reason, in this particular case, the template-head
added some boilerplate code I did not like.
With a combination of the new C++20 features, Concepts and abbreviated func-
tion templates, we can get rid of the entire template-head. As the parameter of this
function, we use the abbreviated syntax together with the concept std::invocable.
The function’s requirements are clearly visible now.

1 void DoLocked(std::invocable auto&& f)


2 {

Listing 1.15
3 std::lock_guard lck{globalOsMutex};
4

5 f();
6 }

This is just one example showing how abbreviated templates reduce the code to
the necessary part. Thanks to Concepts, the type is limited as necessary. I often
claim that, especially for starters, this is much more understandable than the previous
version. Thinking of a bigger picture with a more complex example, this syntax is
useful for experts as well. Clarity is key here.
Even more terse

In earlier proposals of the Concepts feature, the abbreviated function template syntax was even
terser, without auto, using just the concept as type. However, some people claim that new fea-
tures must be expressive and type intensive before users later complain about all the overhead
they have to type. Maybe, in a future standard, we will be able to write the terse syntax, without
auto.

1.10 Using a constexpr function in a concept

When it comes to requirements of Concepts, constexpr functions come to mind,


and the question is, Can we use them, or better yet, their result as a part of a re-
Chapter 1: Concepts: Predicates for strongly typed generic code 37

quirement in a Concept? The answer is yes, but only if the function does not use a
parameter created in a requires expression or if we play tricks. Sounds complicated,
right? It is. Listing 1.16 provides an example.

1 A A helper function with a default parameter


2 constexpr auto GetSize(const auto& t = {})
3 {
4 return t.size(); B Call the size function of the container
5 }

Listing 1.16
6

7 C A concept which uses T.size


8 template<typename T, size_t N>
9 concept SizeCheck = (GetSize<T>() == N);
10

11 D A function that uses SizeCheck


12 void SendOnePing(const SizeCheck<1> auto& cont);

In A we see a constexpr function template or, more precisely, an abbreviated


function template. It takes a single parameter and assumes that the type behind it is
default constructible. In its body, GetSize calls t.size() B . The default parameter
is key here. But let’s first see the full picture.
With SizeCheck in C , we see a concept that uses GetSize and compares its re-
turn value to N. There, we see two interesting things. First, SizeCheck is a concept
taking a type and a NTTP. The order is also important, as we will see later. In its
requirements, SizeCheck calls GetSize, explicitly instantiating it with T, the type
parameter of the concept. The result is compared to N, the NTTP of SizeCheck.
D illustrates a sample usage. The function SendOnePing uses SizeCheck with 1

as NTTP to ensure that whatever container gets passed has a size of exactly 1. I hope
that SendOnePing being an abbreviated function template is not the most interesting
part here, but the way SizeCheck is called. The compiler is smart enough to deduce
the type of the function’s parameter and passes it as the first argument to the concept
SizeCheck. The second parameter is specified explicitly by us. Cool, right?
Listing 1.17 on page 38 shows that SendOnePing can be called with a std::array
of int with a size of one. No other size is allowed by the concept.
38

1 std::array<int, 1> a{};

Listing 1.17
2

3 SendOnePing(a);

The trick we had to play is to make T of the concept a part of the constexpr
function used inside the concept. We cannot use
1 requires(T t) { requires t.size() == N; };

because t is evaluated and the parameter of a requires expression must be uneval-


uated. Hence, the trampoline jump with a default function parameter.
To summarize, we can call and use the result of constexpr functions in concepts
or a nested requirement, but we cannot pass parameters from a requires-expression
to a constexpr function.

1.11 Concepts and constrained auto types

For some years now, at least some of us struggled with placeholder variables with a
definition of the form auto x = something;. One argument I often hear against
using auto instead of a concrete type is that with this syntax, it is hard to know the
variable’s type without a proper Integrated Development Environment (IDE). I agree
with that. Some people at this point tell me and others to use a proper IDE, problem
solved. However, in my experience, this is not entirely solving the problem. Think
about code review, for example. They often take place in either a browser showing
the differences or another diff tool, tending not to provide context. All that said,
there is also a good argument for using auto variables. They help us get the type
right, preventing implicit conversion or loss of precision. Herb Sutter showed years
ago [1] that in a lot of cases, we could put the type at the right, in doing so leave clues.
Let’s look at an example where auto makes our code correct.

1.11.1 Constrained auto variables

Suppose that we have a std::vector v which is filled with a couple of elements. At


some point, we need to know how many elements are in this vector. Getting this
information is easy. We call size() on v. Often, the resulting code looks like this.
Chapter 1: Concepts: Predicates for strongly typed generic code 39

Listing 1.18
1 auto v = std::vector<int>{3, 4, 5};
2 const int size = v.size(); A int is the wrong type

This example uses int to store the size. The code compiles and, in a lot of cases,
works. Despite that, the code is incorrect. A std::vector does not return int in its
size function. The size function usually returns size_t, but this is not guaranteed.
Because of that, there is a size_type definition in the vector that tells us the correct
type. Especially if your code runs on different targets using different compilers and
standard libraries, these little things matter. The correct version of the code is using
the size_type instead of int. To access it, we need to spell out the vector, including
its arguments, making the statement long and probably beginner unfriendly.

1 auto v = std::vector<int>{3, 4, 5};


2

Listing 1.19
3 A Using the
correct type
4 const std::vector<int>::size_type size = v.size();

The alternative so far was to use auto, as shown below, and by that, let the com-
piler deduce the type and keep the code to write and read short. Now the code is
more readable in terms of what it does, but even harder to know what the type is.
Most likely, the deduced type is not a floating-point type, but only with a knowledge
of the STL, can you say that.

Listing 1.20
1 auto v = std::vector<int>{3, 4, 5};
2 const auto size = v.size(); A Let the compiler deduce the type

This is where Concepts can help us use the best of the two worlds. Think about
what we usually need to know in these places. It is not necessarily the precise type
but the interface that type gives us. We expect, and the following code probably re-
quires, the type to be some sort of integral type. Do you remember the abbreviated
function template syntax? There we could prefix auto with a concept to constrain
the parameter’s type. We can do the exact same thing for auto variables in C++20.
40

auto v = std::vector<int>{3, 4, 5};

Listing 1.21
1

2 const std::integral auto size = v.size(); A Limit the type's


properties

Constrained placeholder types allow us to limit the type and its properties without
the need to specify an exact type.

1.11.2 Constrained auto return­type

We can do more than just constraint placeholder variables with Concepts. This syn-
tax applies to return types as well. Annotating an auto return-type has the same
benefit as for auto variables, or instead of typename, a user can see or lookup the
interface definition.

1.12 The power of Concepts: requires instead of enable_if

Concepts are more than just a replacement for SFINAE and a nicer syntax for
enable_if. While these two elements allowed us to write good generic code for
years, Concepts enlarge the application areas. We can use Concepts in more places
than enable_if.

1.12.1 Call method based in requires

One thing that gets pretty easy with Concepts is checking whether an object has a
certain function. In combination with constexpr if, one can conditionally call this
function if it exists. An example is a function template that sends data via the network.
Some objects may have a validation function to do a consistency check. Other objects,
probably simpler types, do not need such a function, and hence do not provide it.
Before Concepts, they would have probably provided a dummy implementation. In
terms of efficiency of run-time and binary size, this did not matter, thanks to state-
of-the-art optimizers. However, for us developers, it means to write and read this
nonsense function. We have maintained these functions over the years, just to do...
nothing. There are solutions using C++11, decltype in the trailing-return type, and
the comma operator to test a method’s existence. The thing is, writing this was a lot
Chapter 1: Concepts: Predicates for strongly typed generic code 41

of boilerplate code and needed a deeper understanding of all these elements and their
combination. With C++20, we can define a concept that has a requires-expression
containing a simple requirement, with the name SupportsValidation and we are
done.

1 template<typename T>
concept SupportsValidation = requires(T t)

Listing 1.22
2

3 {
4 t.validate();
5 };

1.3 C++17: constexpr if

This kind of if statement is evaluated at compile-time. Only one of the branches remains. The
other is discarded at compile-time, depending on the condition. The condition needs to be a
compile-time constant, for example, from a type-trait or a constexpr function.

Instead of applying the concept to a type as a requires-clause, a trailing requires,


or a type-constraint, we can use SupportsValidation inside the function tem-
plate together with constexpr if and call validate on T only, if the method exists.

1 template<typename T>
2 void Send(const T& data)
3 {
4 if constexpr(SupportsValidation<T>) { data.validate(); }
5

6 // actual code sending the data


Listing 1.23

7 }
8

9 class ComplexType {
10 public:
11 void validate() const;
12 };
13

14 class SimpleType {};


42

This allows us to provide types free of dummy functions and code. They are much
cleaner and portable this way.

1.12.2 Conditional copy operations

When we create any wrapper, much like std::optional from C++17 ( Std-
Box 1.4 on page 45), that wrapper should behave as the object wrapped in the
std::optional. A wrapper<T> should behave like T. The standard states that
std:optional shall have a copy constructor if T is copy constructible, and that
it should have a trivial destructor if T is trivially destructible. This makes sense.
If T is not copyable, how can a wrapper like optional copy its contents? Even
if you find a way of doing it, the question is, why should such a wrapper behave
differently? Let’s take only the first requirement and try to implement this using
C++17, optional has a copy constructor if and only if T is copy constructible. This
task is fairly easy. There are even a type-traits std::is_copy_constructible and
std::is_default_destructible to do the job.
We create a class template with a single template parameter T for the type the op-
tional wraps. One way of storing the value is using placement new in an aligned
buffer. As this should not be a complete implementation of optional, let’s ignore
storing the value of T. An optional is default-constructible regardless of the prop-
erties of its wrapped type. Otherwise, an optional would not be optional, as it would
always need a value. For the constrained copy constructor, we need to apply an
enable_if and check whether T is copy constructible and whether the parameter
passed to the copy constructor is of type optional. This is an additional check we
have to do because of the templated version of this method. The resulting code is
shorter than the text needed to explain.

1 template<typename T>
2 class optional {
3 public:
Listing 1.24

4 optional() = default;
5

6 template<
7 typename U,
8 typename = std::enable_if_t<std::is_same_v<U, optional> and
Chapter 1: Concepts: Predicates for strongly typed generic code 43

9 std::is_copy_constructible_v<T>
10 >>
11 optional(const U&);

Listing 1.24
12

13 private:
14 storage_t<T> value;
15 };

After that, we can try out our shiny, admittingly reduced implementation. We
create a struct called NotCopyable. In that struct, we set the copy constructor as
well as the copy assignment operator as deleted. So far, we have looked only at the
copy constructor, but that is fine. The copy assignment operator behaves the same.
With NotCopyable, we can test our implementation. A quick test is to create the
object of optional<NotCopyable> and try to copy construct the second, passing
the first as the argument.

1 A A struct with delete copy operations


2 struct NotCopyable {
3 NotCopyable(const NotCopyable&) = delete;

Listing 1.25
4 NotCopyable& operator=(const NotCopyable&) = delete;
5 };
6

7 optional<NotCopyable> a{};
8 optional<NotCopyable> b = a; B This should fail

That is great! The code compiles! Oh wait, that is not expected, is it? Did we make
a mistake? Yes, one which is, sadly, easy to make. The standard says specifically what
a copy constructor is and how it looks. A copy constructor is never a template. It
follows exactly the syntax T(const T&), that’s it. The question is now, what did we
do, or more specifically, what did we create? We created a conversion constructor.
Looking at the code from a different angle, the intended copy constructor takes a
U. The compiler cannot know that instantiation of this constructor fails for every
type except optional<T>. The correct way to implement this in C++17, and before,
was to derive optional from either a class with a deleted copy constructor and copy
assignment operator or derived from one with both defaulted. We can use std::
44

conditional to achieve this. That way, the copy operations of optional are deleted
by the compiler if a base class has them deleted. Otherwise, they are defaulted.

1 struct copyable {};


2

3 struct notCopyable {
4 notCopyable(const notCopyable&) = delete;
5 notCopyable operator=(const notCopyable&) = delete;
6 };

Listing 1.26
7

8 template<typename T>
9 class optional
10 : public std::conditional_t<std::is_copy_constructible_v<T>,
11 copyable,
12 notCopyable> {
13 public:
14 optional() = default;
15 };

Teach that to some person who is new to C++. We again have a lot of code for a sim-
ple task. How does this look in C++20? Much better. This is one case where the trail-
ing requires-clause shows its powers. In C++20, we can just write a copy constructor,
as we always do. No template is required. The class itself is already a template. But
we can apply the trailing requires to even non-templated methods. This helps us
because a trailing requires-clause doesn’t make the copy constructor anything else.
This method stays a copy constructor. It is even better. We can directly put our re-
quirement, in the form of the type-trait std::is_copy_constructible_v<T>, in
the trailing requires-clause. Absolutely beautiful and so much more readable than
any other previous approach. As another plus, this requires zero additional code,
which often looks unrelated, can be used by colleagues, and needs maintenance.

1 template<typename T>
class optional {
Listing 1.27

3 public:
4 optional() = default;
5
Other documents randomly have
different content
remain freely available for generations to come. In 2001, the Project
Gutenberg Literary Archive Foundation was created to provide a
secure and permanent future for Project Gutenberg™ and future
generations. To learn more about the Project Gutenberg Literary
Archive Foundation and how your efforts and donations can help,
see Sections 3 and 4 and the Foundation information page at
www.gutenberg.org.

Section 3. Information about the Project


Gutenberg Literary Archive Foundation
The Project Gutenberg Literary Archive Foundation is a non-profit
501(c)(3) educational corporation organized under the laws of the
state of Mississippi and granted tax exempt status by the Internal
Revenue Service. The Foundation’s EIN or federal tax identification
number is 64-6221541. Contributions to the Project Gutenberg
Literary Archive Foundation are tax deductible to the full extent
permitted by U.S. federal laws and your state’s laws.

The Foundation’s business office is located at 809 North 1500 West,


Salt Lake City, UT 84116, (801) 596-1887. Email contact links and up
to date contact information can be found at the Foundation’s website
and official page at www.gutenberg.org/contact

Section 4. Information about Donations to


the Project Gutenberg Literary Archive
Foundation
Project Gutenberg™ depends upon and cannot survive without
widespread public support and donations to carry out its mission of
increasing the number of public domain and licensed works that can
be freely distributed in machine-readable form accessible by the
widest array of equipment including outdated equipment. Many
small donations ($1 to $5,000) are particularly important to
maintaining tax exempt status with the IRS.

The Foundation is committed to complying with the laws regulating


charities and charitable donations in all 50 states of the United
States. Compliance requirements are not uniform and it takes a
considerable effort, much paperwork and many fees to meet and
keep up with these requirements. We do not solicit donations in
locations where we have not received written confirmation of
compliance. To SEND DONATIONS or determine the status of
compliance for any particular state visit www.gutenberg.org/donate.

While we cannot and do not solicit contributions from states where


we have not met the solicitation requirements, we know of no
prohibition against accepting unsolicited donations from donors in
such states who approach us with offers to donate.

International donations are gratefully accepted, but we cannot make


any statements concerning tax treatment of donations received from
outside the United States. U.S. laws alone swamp our small staff.

Please check the Project Gutenberg web pages for current donation
methods and addresses. Donations are accepted in a number of
other ways including checks, online payments and credit card
donations. To donate, please visit: www.gutenberg.org/donate.

Section 5. General Information About


Project Gutenberg™ electronic works
Professor Michael S. Hart was the originator of the Project
Gutenberg™ concept of a library of electronic works that could be
freely shared with anyone. For forty years, he produced and
distributed Project Gutenberg™ eBooks with only a loose network of
volunteer support.
Project Gutenberg™ eBooks are often created from several printed
editions, all of which are confirmed as not protected by copyright in
the U.S. unless a copyright notice is included. Thus, we do not
necessarily keep eBooks in compliance with any particular paper
edition.

Most people start at our website which has the main PG search
facility: www.gutenberg.org.

This website includes information about Project Gutenberg™,


including how to make donations to the Project Gutenberg Literary
Archive Foundation, how to help produce our new eBooks, and how
to subscribe to our email newsletter to hear about new eBooks.

You might also like