TL; DR
Compared to static_assert s, concepts are more efficient because:
- they give you good diagnostics that are not easy to achieve with
static_asserts - they allow you to easily overload template functions without
std::enable_if (this is not possible only with static_asserts ) - they allow you to define static interfaces and reuse them without losing diagnostics (several
static_asserts will be needed in each function) - they allow you to better express your intentions and improve readability (which is a big problem for templates).
It can make worlds easier:
- Patterns
- static polymorphism
- overload
and be the building block for interesting paradigms.
What are concepts?
Concepts express "classes" (not in the term C ++, but rather as a "group") of types that satisfy certain requirements. As an example, you can see that the Swappable concept expresses a set of types that:
And you can easily see that, for example, std::string , std::vector , std::deque , int , etc ... satisfy this requirement and therefore can be used interchangeably in a function such as:
template<typename Swappable> void func(const Swappable& a, const Swappable& b) { std::swap(a, b); }
Concepts have always existed in C ++ , the actual function that will be added to the (possibly near) future will simply allow you to express and enforce them in the language.
Improved Diagnostics
As for the best diagnosis, we just have to trust the committee at the moment. But they "guarantee" the way out:
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 is true that you can achieve similar output with static_assert , but this will require a different static_assert for each function, and this can become tedious very quickly.
As an example, suppose that you must enforce the requirements outlined in the Container concept in 2 functions that accept a template parameter; you will need to play them in both functions:
template<typename C> void func_a(...) { static_assert(...); static_assert(...);
Otherwise, you will lose the ability to distinguish which requirement has not been met.
Instead of concepts, you can simply define a concept and apply it simply by writing:
template<Container C> void func_a(...); template<Container C> void func_b(...);
Concept overload
Another great feature that is introduced is the ability to overload template functions with template restrictions. Yes, this is also possible with std::enable_if , but we all know how ugly this can be.
As an example, you might have a function that runs on Container and overloads with a version that works better with SequenceContainer s:
template<Container C> int func(C& c); template<SequenceContainer C> int func(C& c);
An alternative, without concepts, would be the following:
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 more ugly and possibly more error prone.
Syntax cleaner
As you saw in the examples above, the syntax is definitely cleaner and more intuitive to concepts. This can reduce the amount of code needed to express constraints, and can improve readability.
As you can see, you can really go to an acceptable level with something like:
static_assert(Concept<T>::value);
but at this point you will lose the excellent diagnosis of various static_assert . With concepts you do not need this compromise.
Static polymorphism
And finally, the concepts have an interesting resemblance to other functional type-type paradigms in Haskell. For example, they can be used to define static interfaces.
For example, consider the classic approach for a (infamous) game object interface:
struct Object {
Then, assuming you have a polymorphic std::vector derived objects, you can do:
for (auto& o : objects) { o.update(); o.draw(); }
Great, but if you don't want to use multiple inheritance systems or entities, you are practically stuck with only one possible interface for each class.
But if you really want static polymorphism (a polymorphism that is not such dynamics after all), you can define an Object concept that requires update and draw member functions (and possibly others).
At this point, you can simply create a free function:
template<Object O> void process(O& o) { o.update(); o.draw(); }
And after that you can define a different interface for your game objects with different requirements. The beauty of this approach is that you can develop as many interfaces as you want,
- class change
- base class required
And they are all checked and applied at compile time.
This is just a silly example (and very simplistic), but the concepts really open up a whole new world for templates in C ++.
If you need more information, you can read this nice article about C ++ concepts and classes like Haskell.