Question

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?

Was it helpful?

Solution

tl;dr

Compared to static_asserts, concepts are more powerful because:

  • they give you good diagnostic that you wouldn't easily achieve with static_asserts
  • they let you easily overload template functions without std::enable_if (that is impossible only with static_asserts)
  • they let you define static interfaces and reuse them without losing diagnostic (there would be the need for multiple static_asserts in each function)
  • they let you express your intents better and improve readability (which is a big issue with templates)

This can ease the worlds of:

  • templates
  • static polymorphism
  • overloading

and be the building block for interesting paradigms.


What are concepts?

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:

  • allows calls to 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.


Better diagnostic

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_asserts but that would require different static_asserts 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(...);

Concepts overloading

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 Containers and overload it with a version that happens to work better with SequenceContainers:

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.


Cleaner syntax

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.


Static polymorphism

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

  • modifying your classes
  • require a base class

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top