Motivation behind C++ Concepts
C++ 20 introduced concepts. What are they? Why should I care about them? How do I use them? Concepts are a powerful tool to help you write generic code with restrictions evaluated at compile time. What does that mean? Let's say I make a library and I want to create a function that allows my users to pass in a singular integer, float, or string into it. However, I don't want to let them pass in a boolean. Is there a way to accomplish this by writing only one function? YES! We can do this with C++ templates and concepts. (Thanks C++ 20) Ok let's start at the very beginning... templates templates Okay, let' say I'm making a library that accepts an integer, float, or string. One way to do this would be like this with function overloading: #include void function(int v){ std::cout The output here is: function: hi function: 2 diff function: true So both the std::enable_ifs are used to make a void return type if their condition is true for the templated type. We use type_traits in C++ to build our condition. (std::is_same and std::is_constructible) If T is an int or can be constructed to a string, the first function is given a void return type. However, the second function is given an invalid return type. As a result, the function gets thrown away by the compiler. Hence, with std::enable_if we can have multiple functions such that if type substitution fails with one function, there may be another function that can be used. Not only that, by using std::enable_if you can allow partial specialized functions. Like (T and std::vector) coexisting. BOOM. SFINAE solved. Okay, cool, we can restrict types with std::enable_if. But even this looks ugly. Our enable if conditions look long. Is there a better way we could fix this? Concepts In C++ 20, concepts were introduced. Concepts are so cool. Let's solve the problem we had earlier with concepts. Things get more concise. #include #include #include template concept ValidType = std::same_as || std::is_constructible_v; template void function(T v){ std::cout

C++ 20 introduced concepts. What are they? Why should I care about them? How do I use them?
Concepts are a powerful tool to help you write generic code with restrictions evaluated at compile time.
What does that mean?
Let's say I make a library and I want to create a function that allows my users to pass in a singular integer, float, or string into it.
However, I don't want to let them pass in a boolean.
Is there a way to accomplish this by writing only one function?
YES!
We can do this with C++ templates and concepts. (Thanks C++ 20)
Ok let's start at the very beginning... templates
templates
Okay, let' say I'm making a library that accepts an integer, float, or string.
One way to do this would be like this with function overloading:
#include
void function(int v){
std::cout << "function: " << v << std::endl;
}
void function(double v){
std::cout << "function: " << v << std::endl;
}
void function(std::string v){
std::cout << "function: " << v << std::endl;
}
int main(int argc, char** argv){
function("hi");
function(2);
}
Yes all the functions do the same thing and this compiles.
But why do I have to write it 3 times???
You don't!!!
Let's use templates to simplify this to one.
#include
template
void function(T v){
std::cout << "function: " << v << std::endl;
}
int main(int argc, char** argv){
function("hi");
function(2);
}
Nice so we have a program that works and compiles.
The compiler sees that we call function for "hi" and 2
.
So when compiling, it automatically creates two variations of the function. One accepts the integer and another accepts the string.
If I introduced a third type, let's say a double, it would then compile a double version of function.
Super cool!
I can write multiple versions of my function with less code using templates.
I do want to note there is one other way you could do this. In C++ 17 and after, there are type erasure types. (std::variant
and std::any
)
These types can hold multiple types of variables at once. The types that they hold are determined at runtime as opposed to compile time.
Long story short, you can do this:
#include
#include
void function(std::variant v){
if(std::holds_alternative(v)){
std::cout << "function: " << std::get(v) << std::endl;
}else{
std::cout << "function: " << std::get(v) << std::endl;
}
}
int main(int argc, char** argv){
function("hi");
function(2);
}
In my opinion, this looks ugly/verbose. Also there is an extra if statement in there.
As a result, I prefer using a template for this case.
The good thing about a variant is it can restrict the types allowed into the function unlike a general template.
For instance, it only let in an integer and string type into function
.
Okay, great! Is there a way we can do this with templates?
The answer is yes!
We can look at ways to do this shortly.
Just before we get there, I want to show you another limitation of templates.
std::enable_if
C++ has std::enable_if
to activate certain functions if a condition is true.
std::enable_if
is often used with templates.
std::enable_if
is also a templated type as well. The first template argument is the condition, and the second is the type to use if the condition is true.
In C++, this introduces a concept called SFINAE (Substitution Failure is Not an Error).
Let's see an example of this:
#include
#include
template
typename std::enable_if::value ||
std::is_constructible::value, void>::type
function(T v){
std::cout << "function: " << v << std::endl;
}
template
typename std::enable_if::value ||
std::is_constructible::value), void>::type
function(T v){
std::cout << "diff function: " << v << std::endl;
}
int main(int argc, char** argv){
function("hi");
function(2);
function(true);
}
The output here is:
function: hi
function: 2
diff function: true
So both the std::enable_if
s are used to make a void
return type if their condition is true for the templated type.
We use type_traits
in C++ to build our condition. (std::is_same
and std::is_constructible
)
If T
is an int or can be constructed to a string, the first function is given a void return type.
However, the second function is given an invalid return type. As a result, the function gets thrown away by the compiler.
Hence, with std::enable_if
we can have multiple functions such that if type substitution fails with one function, there may be another function that can be used.
Not only that, by using std::enable_if
you can allow partial specialized functions. Like (T
and std::vector
) coexisting.
BOOM. SFINAE solved.
Okay, cool, we can restrict types with std::enable_if
.
But even this looks ugly. Our enable if conditions look long.
Is there a better way we could fix this?
Concepts
In C++ 20, concepts were introduced.
Concepts are so cool.
Let's solve the problem we had earlier with concepts. Things get more concise.
#include
#include
#include
template
concept ValidType = std::same_as || std::is_constructible_v;
template
void function(T v){
std::cout << "function: " << v << std::endl;
}
template
void function(T v){
std::cout << "diff function: " << v << std::endl;
}
int main(int argc, char** argv){
function("hi");
function(2);
function(true);
}
This is kind of cool and way cleaner. Shouldn't 2
work for both functions?
Concepts won't throw an error here.
Instead, the compiler picks the most restrictive option, which is the first function.
You can chain multiple concepts together with Concept1 || Concept2
or Concept1 && Concept2
.
You can create concepts with other concepts:
template
concept IsInt = std::same_as;
template
concept IsString = std::is_constructible_v;
template
concept ValidType = IsInt || IsString;
And all of this is done at compile time!
This is the modern form of the C++ SFINAE concept.
Concepts go hand in hand with the requires
keyword.
I don't want to make this blog tooooo long, but if you would like me to explain requires
in more detail, drop a comment! Or read more here.
The Conclusion
- Concepts are cool and clean and powerful
-
std::enable_if
is noice but it can get very verbose -
std::variant
can get messy with type based if statements and runtime running - Generic templates are great! But they cannot handle partial specialization or type restriction.
Try using concepts. They are easy and make you feel great for using modern C++.
Peace
-absterdabster