C++20 Concepts
19 Jan 2020A quick syntax-based overview of C++20 Concepts, as they are in the standard (circa January 2020).
TL;DR
With the release of MSVC 16.3 (and some holiday time), I decided to convert my Eric Niebler-inspired templated SFINAE hocus-pocus approximation of concepts into actual concepts. But it’s so hard to find up-to-date docs on how the syntax is actually supposed to be. After some experiements (and reading), here are examples to get you started.
Using Concepts in Functions
Let’s come up with a synthetic problem that SFINAE/Concepts can fix. So, we have a log function for numbers, I guess.
We want to treat integers and floating-point numbers differently I suppose? So let’s have one function, log
, that forwards
the handling to different places.
Example
This doesn’t compile, but this is basically what we want:
SFINAE
This is what you’d write today, or maybe I should say yesterday? Haha nah your workplace won’t catch up for years.
Concepts, Explicit Edition
Here is your first taste of concepts, constraining your function template:
- This (a “requires clause”) is unnecessarily verbose for simple concepts
- You will need to use requires clauses like this for concepts that take multiple types
- My syntax highlighter doesn’t highlight ‘requires’ yet :(
Concepts, Decltype Edition
This won’t make sense until the very next example, but you can also place the requires clause
after the parameter-list. This allows you to do some funky things with decltype
, shown here.
- You need to
std::remove_reference_t
your decltypes for many concepts (some you don’t) - I don’t recommend doing this unless you really have to, it’s hard to read
Concepts, Generic Function Edition
C++20 allows you to write generic functions which take auto
as the parameter type, just like generic lambdas.
You then can omit the template specification. You don’t get a typename, so you have to decltype the argument.
Now the above makes more sense!
Concepts, Useful Edition
This what you want to write most of the time if:
- Your concepts are simple and apply to the one type
- You’re not using Generic Functions
Concepts, Combo Edition
Highlighting the terseness you can achieve with “Constrained auto”.
Putting Things Together
Here’s a slightly more complex example showing things together:
Writing A Concept
Okay so you can use concepts defined in the standard library. Now let’s write a concept, because that’s where the real magic is. And everyone loves magic.
The simplest concept
Concepts are defined by a compile-time boolean expression. So this restricts nothing:
A concept specified using a boolean
Here we create a concept from a standard-library compile-time boolean. This is, in fact, how
std::integral
is (probably) implemented.
Boolean operators fully supported:
You want certain expressions to be valid
We can require the type to be able to do things. To do so we wrap expressions in braces:
You care about the types returned
Using some serious voodoo magic, C++20 will substitute the evaluated result-type of the expression into the return-type-requirement, at the front, which is a little weird, but okay.
You want to inspect types, members, and methods
Prefacing a statement in the requires clause with typename
tells the compiler
you’re about to interrogate the type to ensure it has a sub-type. If it doesn’t,
the concept hasn’t been satisfied.
Invovled Example
Here we use several of our previous ideas to describe something we can new & delete.
Constrained auto, but everywhere
“Constrained auto” means you jam a concept in front of auto
to ensure whatever type
it is conforms to the concept. You can put this damn near anywhere you use auto
.
No but like everywhere:
I hope that helps you get a quick handle on how concepts are crafted and used in C++20.