Concepts in action
The concepts-lite extension for c++ is accepted for the upcoming C++17 standard. Some tutorials are already available and with the current gcc trunk an experimental implementation of this feature in a c++ compiler. We will see some examples of concepts-lite in action.
The task is to write a library that overloads to operator*
in order to scale an
iterable object by a scalar factor, e.g. scale a vector by a double value. In
order to be very generic, we want to write a templated function for the scaling:
This implementation provides a scaling of a vector by a scalar factor from the right. The problem with this implementation is, that we can not distiguish between the vector argument and the scalar argument, since both are unconstrained templates. Passing a scalar as first argument and a vector as second argument is a valid call to the function, but results in a compiler error in the inner algorithm, i.e. in the range-based for-loop, since a scalar is not iterable.
It is possible to write an overload of operator*
for a left-sided multiplication
by using tag-dispatiching, SFINAE techniques, or some other tricks. But we want
to write the code in a natural way. Therefore, we use constraints by the concepts-lite
specification (Technical specification, Tutorial).
What is a vector? Or at least an iterable object? We require, that a range-based
for-loop can be used for the container, i.e. there exist begin()
and end()
(member) functions and the current iterate, i.e. the value-type of a vector, is
multiplicable by a scalar.
What is a scalar, on the other hand? For now we simply require, that it is an
arithmetic type, that fulfills the type-trait std::is_arithmetic
. Later,
we can refine this concept, by explicitly specifying requirements on scalar arguments.
In order to use type-constraints, we have to define concepts:
The first one (Iterable) provides a simplified concept for iterable objects, i.e.
there must exist either a free function begin()
and end()
or member functions
with the same names. Here, nothing is said about what these functions should return.
A more advanced implementation could be a requirement that the result of begin()
and
end()
are Iterators (Input- or Output-Iterators, depending on other requirements).
The second concepts simply redirects to the type-traits implementation of the STL.
The third concepts is a binary concept, since it implements a relation between two
types. Here, we just require, that a multiplication operation between these two types
exist.
Combined with the operator definition above, we can now define vector*scalar
and
scalar*vector
concept-enabled function overloads:
where Value_type
is an alias template leading to the value type of a container.
A possible implementation could be:
So, we can now multiply any iterable object by any arithmetic type, if the value-types are multiplicable, e.g.
Download Source
You can download the source of the final implementation from here.