C++ concepts and programming by design

C++ concepts are an upcoming feature that is being proposed for C++17. While it’s still under work, the general idea behind concepts is easier to talk about, and is language-agnostic.

What are C++ concepts?

From the above, Stroustraup writes:

“A concept is a compile-time predicate.”

Concepts, at least as described here, are a way of describing semi-generic sets of types that impose requirements on their members. In a sense, a concept is a category of types.

The following is an example pulled from the link above:

template<Associative_container C>
Iterator_type<C> find(C&& assoc, const Key_type<C>& key)
{
  return assoc.find(key);
}

Usually, ensuring that types match up with a certain “category” of types is hard in C++; inheritance is one solution, but forcing a type hierarchy can be awkward and add unneeded complexity to code, especially if there is more than one “category” to satisfy. Another solution would be to use something like std::enable_if<>() and other compile-time, template-based tricks. As we can see here, the idea of performing a search on an Associative_container such that the algorithm requires a Container value and a constant reference to a Key-like type and return an Iterator for the Container is quite naturally expressed once we can express categories of types in our code.

What’s the big idea?

How can we perform logic based on categories rather than types?

This semi-generic abstraction over types is very crucial, if we think about various applications. It allows us to more closely model and abstract our programs in terms of design concepts.

Often times, we consider concepts over types rather than just types:

  • Addition is a function that adds two numbers together.
    • The concept of a number can be expressed as a union of many different types: the natural numbers, the integers, real numbers, imaginary numbers, quaternions, etc.
  • The concept of a “string” or “number” or even “linear container” can be split among various implementations that are encoded as different types with a common interface. Forcing inheritance is an invasive move for maintenance.
    • Allowing for multiple implementations to subscribe to a common interface is the name of the game in many different languages; classically, Java has its interface mechanism, but whose inclusion or opt-in scheme is similar to that of inheritance. C++ concepts are opt-in by inspection, with the checking done automatically.
  • Many types can be move-constructable but not copy-constructable. The various permutations of move/non-movable, copy/non-copyable can be tedious to figure out.
    • The concepts of MoveConstructable and CopyConstructable can simplify things by turning checking for common features/common interface into a simple type check and expression evaluation at compile-time, with less errors.
  • Categories are more generic than types; substitution is easier over when we can substitute for a class of types rather than just one.
    • For example, finding a substitute for a grain for breakfast is much harder if you need to find a substitute specifically for quinoa.
      • If you have no concept of what a “grain” is, how do you know without having to declare a large inheritance tree that quinoa is a grain?
        • What if something can serve as a substitute for a grain but isn’t actually a grain? Is duck typing okay for inheritance when a duck isn’t a grain?

You’re stupid and you write so bad. What’s your point?

Constraints violations are diagnosed at the point of use, just like type errors. C++98 (and C++11) template instantiation errors are reported in terms of implementation details (at instantiation time), whereas constraints errors are expressed in terms of the programmer’s intent stated as requirements. This is a fundamental difference. The diagnostics explain which constraints were not satisfied and the specific reasons for those failures.

  • Concepts, most importantly, are considered as a way to solve the extremely dense compilation error problem with templates.
    • Any C++ programmer who has had to deal with compilation errors of libraries like Boost or even an object as “basic” as `std::vector<T>` understands that template diagnostics can be terrifying and soul-wrenching to debug, as having to look deep into the implementation or online documentation whenever something doesn’t compile is a major slowdown when trying to fix errors and continue with development.
  • People want to program with their own concepts in C++, not just with specific types or with the pre-defined concepts of C++.
    • Stroustraup mentions that an implementation of concepts was created to test concepts out, even using concepts with the implementation of the standard library. I’d bet that understanding that your object can’t be copied because you’re using std::unique_ptr<Node<T>> instead of Node<T>* would be sped up if the compiler would just tell you that std::unique_ptr<Node<T>> is not a Copyable type instead of telling you it couldn’t match the copy constructor types or the function was deleted.
  • The concept, or, rather, the semi-generic interface is a more efficient abstraction than inheritance for “category of types.”
    • Concepts are closer to the concept of a “category of types with similar functionality” than inheritance, as a concept is not a type. As per math, a set is not the same as its elements.

 

 

Is Asio Boost dependent?

You know, it’s really strange that Asio itself provides non-Boost examples that require Boost.Date_Time in order to run them. Even more so, not being able to compile the examples even with the ASIO_STANDALONE flag makes me think that Asio doesn’t care about its own independence.

Boost is quite a large library. Downloading the entire thing can take quite awhile. It makes me wonder that if somebody were to make any libraries based on Boost, would it be okay? And what about dealing with Boost-like libraries that become standard libraries and then their compatibility?

It’s not too bad, it’s just a mild inconvenience with maybe some future noticeable effects.