Associated Requirements
Adding the
Numeric requirement to the
perimeter() concept is correct, but it is not ideal. The requirement that the
length_type of a
Polygon be
Numeric isn't solely a property of
perimeter(): It's a more universal property that polygons only make sense if their length types are proper numeric types. To support this common usage, concepts support
associated requirements, which specify extra requirements on the associated types of a concept within the concept body. Here is the final version of the
Polygon concept, which makes use of associated requirements:
concept Polygon<typename P> {
typename length_type;
requires Numeric<length_type>;
int num_sides(const P&);
length_type side_length(const P&, int index);
}
With this change, you can remove the explicit
Numeric requirement from the
perimeter() algorithm, because the length type of a
Polygon is always numeric. For the final declaration of this
perimeter() algorithm, I've also employed two syntactic shortcuts, which simplify the expression of template requirements and simplify access to associated types:
template<Polygon P>
P::length_type perimeter(const P& poly);
The first shortcut is the use of
Polygon P, which states that
P is a template type parameter whose requirement is
Polygon<P>; it is identical to writing the same requirement within a requires clause. The second shortcut is the use of
P::length_type, which searches for an associated type named
length_type within the requirements placed on
P, and is identical to
Polygon<P>::length_type.
Syntax Remapping with Concept Maps
All the concept maps so far have had empty bodies within their curly braces, and have been used to state (and check) that a type meets the concept requirements. However, concept maps need not be empty: they can contain definitions that specify how a type meets the concept requirements, and can even contain new functions that allow one to map the syntax provided by a type to the syntax expected by the concept. For example, start with a simple rectangle structure:
struct rectangle {
double left, top, right, bottom;
};
You could add appropriate
num_sides() and
side_length() functions to this type to make it look like a
Polygon. But, this expands the interface in a way that might not be desirable if the only purpose is to use the
perimeter() function. Instead, you can write a concept map that makes the rectangle look like a polygon:
concept_map Polygon<rectangle> {
typedef double length_type;
int num_sides(const rectangle&) { return 4; }
double side_length(const rectangle& rect, int index) {
return index % 2? fabs(rect.right – rect.left)
: fabs(rect.bottom – rect.top);
}
}
In this concept map, the
Polygon concept's requirements are satisfied by the body of the concept map itself. The functions provided by the concept map are visible only through the concept itself, and are not part of the public interface of the rectangle structure (which has not changed). Only those constrained templates that operate on
Polygons will see this particular view of the rectangle structure.
Through this syntax-remapping mechanism, an existing type can be adapted to meet an existing concept's requirements without changing either the type or the concept, allowing far greater reuse of generic algorithms than is possible without concept maps.