1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
|
COMMENT(See intro.txt for stuff ISN)
bf(C++) is a strongly typed language: a function tt(add(int lhs, int rhs))
doesn't accept tt(std::string) arguments, even though the actual operations
(tt(lhs + rhs)) are identical for tt(ints) and tt(strings).
Templates were introduced so we could design em(recipes) for the compiler,
allowing it to construct type-safe overloaded versions of functions and
classes while keeping their type-safety.
A basic addition function template adding two values looks like this:
verb( template <typename Type>
Type add(Type const &lhs, Type const &rhs)
{
return lhs + rhs;
}
)
When this function template is called with arguments of types that do not
support tt(operator+) then the compiler notices this, and it will generate an
error. E.g., when calling
verb( add(std::cerr, std::cout);)
the tt(g++) compiler produces some 140 lines of error messages. It notices
that there's no tt(operator+) for tt(std::ostream) objects, and then tells us
what else we might have done (like adding two tt(ints)), and where the
construction of the tt(add) function that should accept tt(std::ostream)
arguments went wrong. In fact, 140 lines of error messages is rather benign.
Getting several hundreds of lines is quite common, and sometimes the
location of the error isn't mentioned at the top but somewhere near the end of
the error message output.
The C2a standard introduced hi(concept) em(concepts) allowing us to specify
em(requirements) for template types. When applying an appropriate concept to
the definition of the tt(add) function template the compiler immediately
pinpoints the error, telling us where and why the error occurred in some 15
instead of 140 lines of error messages.
The reduction of the number of lines of error messages by itself is a
boon. But the fact that concepts allow us to consciously develop our
templates, realizing what the precise requirements are for their use, is at
least as important: it improves the template's documentation, and thus our
understanding of templates.
Concepts may be considered the template's answer to the philosphy that lies
behind a strongly typed language. By applying concepts to templates we can
specify type-requirements rather than using the traditional `shotgun
empiricism' approach where templates are bluntly used, knowing that the
compiler will complain if things are incorrect. In that sense concepts provide
type definitions of types. Concepts have names and can (among other) be used
in template headers where the concept names replace the traditional
tt(typename) keywords.
As an opening illustration, assume that a concept tt(Addable) exists
specifying that tt(operator+) must have been defined for the template's
type. The above function template tt(add) can now be formulated as:
verb( template<Addable Type>
Type add(Type const &lhs, Type const &rhs)
{
return lhs + rhs;
})
From now on every type that is actually passed to tt(add) must be
satisfy the tt(Addable) requirements. Here are two expressions
using tt(add):
verb( add("first"s, "second"s); // (1)
add(map<int, int>{}, map<int, int>{}); // (2))
Expression (1) flawlessly compiles as tt(string) objects can be added;
expression (2) fails with the compiler reporting something like
verb( error: use of function `Type add(const Type&, const Type&)
[with Type = std::unordered_map<int, int>]' with unsatisfied constraints
add(unordered_map<int, int>{}, unordered_map<int, int>{});
note: constraints not satisfied
Type add(const Type&, const Type&)
...
note: the required expression `(lh + rh)' is invalid)
COMMENT(examples/intro1.cc)
The error message's final `note' clearly states the cause of the problem:
you can't add maps.
The difference between the compiler's report using concepts and not using
concepts again is impressive. When using the traditional tt(typename Type)
specification in the template header the compiler produces some 17 kB of error
messages, spread out over more than 200 lines.
In the following sections we cover how concepts are defined, what kind of
requirements can be formulated, and how they can be used in practice.
|