What is exactly new in c++ concepts? In my understanding they are functionally equal to using static_assert
, but in a 'nice' manner meaning that compiler errors will be more readable (as Bjarne Stroustup said you won't get 10 pages or erros, but just one).
Basically, is it true that everything you can do with concepts you can also achieve using static_assert
?
Is there something I am missing?
static_assert is meant to make compilation fail with the specified message, while traditional assert is meant to end the execution of your program.
What is static assertion? Static assertions are a way to check if a condition is true when the code is compiled. If it isn't, the compiler is required to issue an error message and stop the compiling process. The condition that needs to be checked is a constant expression. Performs compile-time assertion checking.
In short: assert checks its condition at runtime, and static_assert checks its condition at compilation. So if the condition you're asserting is known at compile time, use static_assert . If the condition won't be known until the program runs, use assert .
static_assert is a keyword defined in the <assert. h> header. It is available in the C11 version of C.
Compared to static_assert
s, concepts are more powerful because:
static_asserts
std::enable_if
(that is impossible only with static_asserts
)static_asserts
in each function)This can ease the worlds of:
and be the building block for interesting paradigms.
Concepts express "classes" (not in the C++ term, but rather as a "group") of types that satisfy certain requirements. As an example you can see that the Swappable
concept express the set of types that:
std::swap
And you can easily see that, for example, std::string
, std::vector
, std::deque
, int
etc... satisfy this requirement and can therefore be used interchangeably in a function like:
template<typename Swappable>
void func(const Swappable& a, const Swappable& b) {
std::swap(a, b);
}
Concepts always existed in C++, the actual feature that will be added in the (possibly near) future will just allow you to express and enforce them in the language.
As far as better diagnostic goes, we will just have to trust the committee for now. But the output they "guarantee":
error: no matching function for call to 'sort(list<int>&)'
sort(l);
^
note: template constraints not satisfied because
note: `T' is not a/an `Sortable' type [with T = list<int>] since
note: `declval<T>()[n]' is not valid syntax
is very promising.
It's true that you can achieve a similar output using static_assert
s but that would require different static_assert
s per function and that could get tedious very fast.
As an example, imagine you have to enforce the amount of requirements given by the Container
concept in 2 functions taking a template parameter; you would need to replicate them in both functions:
template<typename C>
void func_a(...) {
static_assert(...);
static_assert(...);
// ...
}
template<typename C>
void func_b(...) {
static_assert(...);
static_assert(...);
// ...
}
Otherwise you would loose the ability to distinguish which requirement was not satisfied.
With concepts instead, you can just define the concept and enforce it by simply writing:
template<Container C>
void func_a(...);
template<Container C>
void func_b(...);
Another great feature that is introduced is the ability to overload template functions on template constraints. Yes, this is also possible with std::enable_if
, but we all know how ugly that can become.
As an example you could have a function that works on Container
s and overload it with a version that happens to work better with SequenceContainer
s:
template<Container C>
int func(C& c);
template<SequenceContainer C>
int func(C& c);
The alternative, without concepts, would be this:
template<typename T>
std::enable_if<
Container<T>::value,
int
> func(T& c);
template<typename T>
std::enable_if<
SequenceContainer<T>::value,
int
> func(T& c);
Definitely uglier and possibly more error prone.
As you have seen in the examples above the syntax is definitely cleaner and more intuitive with concepts. This can reduce the amount of code required to express constraints and can improve readability.
As seen before you can actually get to an acceptable level with something like:
static_assert(Concept<T>::value);
but at that point you would loose the great diagnostic of different static_assert
. With concepts you don't need this tradeoff.
And finally concepts have interesting similarities to other functional paradigms like type classes in Haskell. For example they can be used to define static interfaces.
For example, let's consider the classical approach for an (infamous) game object interface:
struct Object {
// …
virtual update() = 0;
virtual draw() = 0;
virtual ~Object();
};
Then, assuming you have a polymorphic std::vector
of derived objects you can do:
for (auto& o : objects) {
o.update();
o.draw();
}
Great, but unless you want to use multiple inheritance or entity-component-based systems, you are pretty much stuck with only one possible interface per class.
But if you actually want static polymorphism (polymorphism that is not that dynamic after all) you could define an Object
concept that requires update
and draw
member functions (and possibly others).
At that point you can just create a free function:
template<Object O>
void process(O& o) {
o.update();
o.draw();
}
And after that you could define another interface for your game objects with other requirements. The beauty of this approach is that you can develop as many interfaces as you want without
And they are all checked and enforced at compile time.
This is just a stupid example (and a very simplistic one), but concepts really open up a whole new world for templates in C++.
If you want more informations you can read this nice article on C++ concepts vs Haskell type classes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With