Template code is a little different from ordinary code. In some ways templates lie somewhere between macros and ordinary (nontemplate) declarations. Although this may be an oversimplification, it has consequences not only for the way we write algorithms and data structures using templates but also for the day-to-day logistics of expressing and analyzing programs involving templates.
In this chapter we address some of these practicalities without necessarily delving into the technical details that underlie them. Many of these details are explored in Chapter 14. To keep the discussion simple, we assume that our C++ compilation systems consist of fairly traditional compilers and linkers (C++ systems that don’t fall in this category are rare).
There are several ways to organize template source code. This section presents the most popular approach: the inclusion model.
Most C and C++ programmers organize their nontemplate code largely as follows:
• Classes and other types are entirely placed in header files. Typically, this is a file with a .hpp
(or .H
, .h
, .hh
, .hxx
) filename extension.
• For global (noninline) variables and (noninline) functions, only a declaration is put in a header file, and the definition goes into a file compiled as its own translation unit. Such a CPP file typically is a file with a .cpp
(or .C
, .c
, .cc
, or .cxx
) filename extension.
This works well: It makes the needed type definition easily available throughout the program and avoids duplicate definition errors on variables and functions from the linker.
With these conventions in mind, a common error about which beginning template programmers complain is illustrated by the following (erroneous) little program. As usual for “ordinary code,” we declare the template in a header file:
basics/myfirst.hpp
#ifndef MYFIRST_HPP
#define MYFIRST_HPP
// declaration of template
template<typename T>
void printTypeof (T const&);
#endif //MYFIRST_HPP
printTypeof()
is the declaration of a simple auxiliary function that prints some type information. The implementation of the function is placed in a CPP file:
basics/myfirst.cpp
#include <iostream>
#include <typeinfo>
#include "myfirst.hpp"
// implementation/definition of template
template<typename T>
void printTypeof (T const& x)
{
std::cout << typeid(x).name() << ’\n’;
}
The example uses the typeid
operator to print a string that describes the type of the expression passed to it. It returns an lvalue of the static type std::type_info
, which provides a member function name()
that shows the types of some expressions. The C++ standard doesn’t actually say that name()
must return something meaningful, but on good C++ implementations, you should get a string that gives a good description of the type of the expression passed to typeid
.1
Finally, we use the template in another CPP file, into which our template declaration is #include
d:
basics/myfirstmain.cpp
#include "myfirst.hpp"
// use of the template
int main()
{
double ice = 3.0;
printTypeof(ice); // call function template for type double
}
A C++ compiler will most likely accept this program without any problems, but the linker will probably report an error, implying that there is no definition of the function printTypeof()
.
The reason for this error is that the definition of the function template printTypeof()
has not been instantiated. In order for a template to be instantiated, the compiler must know which definition should be instantiated and for what template arguments it should be instantiated. Unfortunately, in the previous example, these two pieces of information are in files that are compiled separately. Therefore, when our compiler sees the call to printTypeof()
but has no definition in sight to instantiate this function for double
, it just assumes that such a definition is provided elsewhere and creates a reference (for the linker to resolve) to that definition. On the other hand, when the compiler processes the file myfirst.cpp
, it has no indication at that point that it must instantiate the template definition it contains for specific arguments.
The common solution to the previous problem is to use the same approach that we would take with macros or with inline functions: We include the definitions of a template in the header file that declares that template.
That is, instead of providing a file myfirst.cpp
, we rewrite myfirst.hpp
so that it contains all template declarations and template definitions:
basics/myfirst2.hpp
#ifndef MYFIRST_HPP
#define MYFIRST_HPP
#include <iostream>
#include <typeinfo>
// declaration of template
template<typename T>
void printTypeof (T const&);
// implementation/definition of template
template<typename T>
void printTypeof (T const& x)
{
std::cout << typeid(x).name() << ’\n’; }
#endif //MYFIRST_HPP
This way of organizing templates is called the inclusion model. With this in place, you should find that our program now correctly compiles, links, and executes.
There are a few observations we can make at this point. The most notable is that this approach has considerably increased the cost of including the header file myfirst.hpp
. In this example, the cost is not the result of the size of the template definition itself but the result of the fact that we must also include the headers used by the definition of our template—in this case <iostream>
and <typeinfo>
. You may find that this amounts to tens of thousands of lines of code because headers like <iostream>
contain many template definitions of their own.
This is a real problem in practice because it considerably increases the time needed by the compiler to compile significant programs. We will therefore examine some possible ways to approach this problem, including precompiled headers (see Section 9.3 on page 141) and the use of explicit template instantiation (see Section 14.5 on page 260).
Despite this build-time issue, we do recommend following this inclusion model to organize your templates when possible until a better mechanism becomes available. At the time of writing this book in 2017, such a mechanism is in the works: modules, which is introduced in Section 17.11 on page 366. They are a language mechanism that allows the programmer to more logically organize code in such a way that a compiler can separately compile all declarations and then efficiently and selectively import the processed declarations whenever needed.
Another (more subtle) observation about the inclusion approach is that noninline function templates are distinct from inline functions and macros in an important way: They are not expanded at the call site. Instead, when they are instantiated, they create a new copy of a function. Because this is an automatic process, a compiler could end up creating two copies in two different files, and some linkers could issue errors when they find two distinct definitions for the same function. In theory, this should not be a concern of ours: It is a problem for the C++ compilation system to accommodate. In practice, things work well most of the time, and we don’t need to deal with this issue at all. For large projects that create their own library of code, however, problems occasionally show up. A discussion of instantiation schemes in Chapter 14 and a close study of the documentation that came with the C++ translation system (compiler) should help address these problems.
Finally, we need to point out that what applies to the ordinary function template in our example also applies to member functions and static data members of class templates, as well as to member function templates.
Declaring functions to be inline is a common tool to improve the running time of programs. The inline
specifier was meant to be a hint for the implementation that inline substitution of the function body at the point of call is preferred over the usual function call mechanism.
However, an implementation may ignore the hint. Hence, the only guaranteed effect of inline
is to allow a function definition to appear multiple times in a program (usually because it appears in a header file that is included in multiple places).
Like inline functions, function templates can be defined in multiple translation units. This is usually achieved by placing the definition in a header file that is included by multiple CPP files.
This doesn’t mean, however, that function templates use inline substitutions by default. It is entirely up to the compiler whether and when inline substitution of a function template body at the point of call is preferred over the usual function call mechanism. Perhaps surprisingly, compilers are often better than programmers at estimating whether inlining a call would lead to a net performance improvement. As a result, the precise policy of a compiler with respect to inline
varies from compiler to compiler, and even depends on the options selected for a specific compilation.
Nevertheless, with appropriate performance monitoring tools, a programmer may have better information than a compiler and may therefore wish to override compiler decisions (e.g., when tuning software for particular platforms, such as mobiles phones, or particular inputs). Sometimes this is only possible with compiler-specific attributes such as noinline
or always_inline
.
It’s worth pointing out at this point that full specializations of function templates act like ordinary functions in this regard: Their definition can appear only once unless they’re defined inline
(see Section 16.3 on page 338). See also Appendix A for a broader, detailed overview of this topic.
Even without templates, C++ header files can become very large and therefore take a long time to compile. Templates add to this tendency, and the outcry of waiting programmers has in many cases driven vendors to implement a scheme usually known as precompiled headers (PCH). This scheme operates outside the scope of the standard and relies on vendor-specific options. Although we leave the details on how to create and use precompiled header files to the documentation of the various C++ compilation systems that have this feature, it is useful to gain some understanding of how it works.
When a compiler translates a file, it does so starting from the beginning of the file and working through to the end. As it processes each token from the file (which may come from #include
d files), it adapts its internal state, including such things as adding entries to a table of symbols so they may be looked up later. While doing so, the compiler may also generate code in object files.
The precompiled header scheme relies on the fact that code can be organized in such a manner that many files start with the same lines of code. Let’s assume for the sake of argument that every file to be compiled starts with the same N lines of code. We could compile these N lines and save the complete state of the compiler at that point in a precompiled header. Then, for every file in our program, we could reload the saved state and start compilation at line N+1. At this point it is worthwhile to note that reloading the saved state is an operation that can be orders of magnitude faster than actually compiling the first N lines. However, saving the state in the first place is typically more expensive than just compiling the N lines. The increase in cost varies roughly from 20 to 200 percent.
#include <iostream>
The key to making effective use of precompiled headers is to ensure that—as much as possible— files start with a maximum number of common lines of code. In practice this means the files must start with the same #include
directives, which (as mentioned earlier) consume a substantial portion of our build time. Hence, it can be very advantageous to pay attention to the order in which headers are included. For example, the following two files:
#include <vector>
#include <list>
…
#include <list>
#include <vector>
…
inhibit the use of precompiled headers because there is no common initial state in the sources.
Some programmers decide that it is better to #include
some extra unnecessary headers than to pass on an opportunity to accelerate the translation of a file using a precompiled header. This decision can considerably ease the management of the inclusion policy. For example, it is usually relatively straightforward to create a header file named std.hpp
that includes all the standard headers:2
#include <iostream>
#include <string>
#include <vector>
#include <deque>
#include <list>
…
This file can then be precompiled, and every program file that makes use of the standard library can then simply be started as follows:
#include "std.hpp"
…
Normally this would take a while to compile, but given a system with sufficient memory, the pre-compiled header scheme allows it to be processed significantly faster than almost any single standard header would require without precompilation. The standard headers are particularly convenient in this way because they rarely change, and hence the precompiled header for our std.hpp
file can be built once. Otherwise, precompiled headers are typically part of the dependency configuration of a project (e.g., they are updated as needed by the popular make
tool or an integrated development environment’s (IDE) project build tool).
One attractive approach to manage precompiled headers is to create layers of precompiled headers that go from the most widely used and stable headers (e.g., our std.hpp
header) to headers that aren’t expected to change all the time and therefore are still worth precompiling. However, if headers are under heavy development, creating precompiled headers for them can take more time than what is saved by reusing them. A key concept to this approach is that a precompiled header for a more stable layer can be reused to improve the precompilation time of a less stable header. For example, suppose that in addition to our std.hpp
header (which we have precompiled), we also define a core.hpp
header that includes additional facilities that are specific to our project but nonetheless achieve a certain level of stability:
#include "std.hpp"
#include "core_data.hpp
#include "core_algos.hpp"
…
Because this file starts with #include "std.hpp"
, the compiler can load the associated precom-piled header and continue with the next line without recompiling all the standard headers. When the file is completely processed, a new precompiled header can be produced. Applications can then use #include "core.hpp"
to provide access quickly to large amounts of functionality because the compiler can load the latter precompiled header.
Ordinary compilation errors are normally quite succinct and to the point. For example, when a compiler says “class X has no member ’fun’
,” it usually isn’t too hard to figure out what is wrong in our code (e.g., we might have mistyped run
as fun
). Not so with templates. Let’s look at some examples.
Consider the following relatively simple example using the C++ standard library:
basics/errornovel1.cpp
#include <string>
#include <map>
#include <algorithm>
int main()
{
std::map<std::string,double> coll;
…
// find the first nonempty string in coll:
auto pos = std::find_if (coll.begin(), coll.end(),
[] (std::string const& s){
return s != "";
});
}
It contains a fairly small mistake: In the lambda used to find the first matching string in the collection, we check against a given string. However, the elements in a map are key/value pairs, so that we should expect a std::pair<std::string const, double>
.
A version of the popular GNU C++ compiler reports the following error:
1 In file included from /cygdrive/p/gcc/gcc61-include/bits/stl_algobase.h:71:0,
2 from /cygdrive/p/gcc/gcc61-include/bits/char_traits.h:39,
3 from /cygdrive/p/gcc/gcc61-include/string:40,
4 from errornovel1.cpp:1:
5 /cygdrive/p/gcc/gcc61-include/bits/predefined_ops.h: In instantiation of 'bool __gnu_cxx
::__ops::_Iter_pred<_Predicate>::operator()(_Iterator) [with _Iterator = std::_Rb_tree_i
terator<std::pair<const std::__cxx11::basic_string<char>, double> >; _Predicate = main()
::<lambda(const string&)>]':
6 /cygdrive/p/gcc/gcc61-include/bits/stl_algo.h:104:42: required from '_InputIterator
std::__find_if(_InputIterator, _InputIterator, _Predicate, std::input_iterator_tag)
[with _InputIterator = std::_Rb_tree_iterator<std::pair<const std::__cxx11::basic_string
<char>, double> >; _Predicate = __gnu_cxx::__ops::_Iter_pred<main()::<lambda(const
string&)> >]'
7 /cygdrive/p/gcc/gcc61-include/bits/stl_algo.h:161:23: required from '_Iterator std::__
find_if(_Iterator, _Iterator, _Predicate) [with _Iterator = std::_Rb_tree_iterator<std::
pair<const std::__cxx11::basic_string<char>, double> >; _Predicate = __gnu_cxx::__ops::_
Iter_pred<main()::<lambda(const string&)> >]'
8 /cygdrive/p/gcc/gcc61-include/bits/stl_algo.h:3824:28: required from '_IIter std::find
_if(_IIter, _IIter, _Predicate) [with _IIter = std::_Rb_tree_iterator<std::pair<const
std::__cxx11::basic_string<char>, double> >; _Predicate = main()::<lambda(const string&)
>]'
9 errornovel1.cpp:13:29: required from here
10 /cygdrive/p/gcc/gcc61-include/bits/predefined_ops.h:234:11: error: no match for call to
'(main()::<lambda(const string&)>) (std::pair<const std::__cxx11::basic_string<char>,
double>&)'
11 { return bool(_M_pred(*__it)); }
12 ^~~~~~~~~~~~~~~~~~~~
13 /cygdrive/p/gcc/gcc61-include/bits/predefined_ops.h:234:11: note: candidate: bool (*)(
const string&) {aka bool (*)(const std::__cxx11::basic_string<char>&)} <conversion>
14 /cygdrive/p/gcc/gcc61-include/bits/predefined_ops.h:234:11: note: candidate expects 2
arguments, 2 provided
15 errornovel1.cpp:11:52: note: candidate: main()::<lambda(const string&)>
16 [] (std::string const& s) {
17 ^
18 errornovel1.cpp:11:52: note: no known conversion for argument 1 from 'std::pair<const
std::__cxx11::basic_string<char>, double>' to 'const string& {aka const std::__cxx11::
basic_string<char>&}'
A message like this starts looking more like a novel than a diagnostic. It can also be overwhelming to the point of discouraging novice template users. However, with some practice, messages like this become manageable, and the errors are at least relatively easily located.
The first part of this error message says that an error occurred in a function template instance deep inside an internal predefined_ops.h
header, included from errornovel1.cpp
via various other headers. Here and in the following lines, the compiler reports what was instantiated with which arguments. In this case, it all started with the statement ending on line 13 of errornovel1.cpp
, which is:
auto pos = std::find_if (coll.begin(), coll.end(),
[] (std::string const& s) {
return s != "";
});
This caused the instantiation of a find_if
template on line 115 of the stl_algo.h
header, where the code
_IIter std::find_if(_IIter, _IIter, _Predicate)
is instantiated with
_IIter = std::_Rb_tree_iterator<std::pair<const std::__cxx11::basic_string<char>,
double> >
_Predicate = main()::<lambda(const string&)>
The compiler reports all this in case we simply were not expecting all these templates to be instantiated. It allows us to determine the chain of events that caused the instantiations.
However, in our example, we’re willing to believe that all kinds of templates needed to be instantiated, and we just wonder why it didn’t work. This information comes in the last part of the message: The part that says “no match for call
” implies that a function call could not be resolved because the types of the arguments and the parameter types didn’t match. It lists what is called
(main()::<lambda(const string&)>) (std::pair<const std::__cxx11::basic_string<char>,
double>&)
and code that caused this call:
{ return bool(_M_pred(*__it)); }
Furthermore, just after this, the line containing “note: candidate:
” explains that there was a single candidate type expecting a const string&
and that this candidate is defined in line 11 of errornovel1.cpp
as lambda [] (std::string const& s)
combined with a reason why a possible candidate didn’t fit:
no known conversion for argument 1
from ’std::pair<const std::__cxx11::basic_string<char>, double>’
to ’const string& {aka const std::__cxx11::basic_string<char>&}’
which describes the problem we have.
There is no doubt that the error message could be better. The actual problem could be emitted before the history of the instantiation, and instead of using fully expanded template instantiation names like std::__cxx11::basic_string<char>
, using just std::string
might be enough. However, it is also true that all the information in this diagnostic could be useful in some situations. It is therefore not surprising that other compilers provide similar information (although some use the structuring techniques mentioned).
For example, the Visual C++ compiler outputs something like:
1 c:\tools_root\cl\inc\algorithm(166): error C2664: 'bool main::<lambda_b863c1c7cd07048816
f454330789acb4>::operator ()(const std::string &) const': cannot convert argument 1 from
'std::pair<const _Kty,_Ty>' to 'const std::string &'
2 with
3 [
4 _Kty=std::string,
5 _Ty=double
6 ]
7 c:\tools_root\cl\inc\algorithm(166): note: Reason: cannot convert from 'std::pair<const
_Kty,_Ty>' to 'const std::string'
8 with
9 [
10 _Kty=std::string,
11 _Ty=double
12 ]
13 c:\tools_root\cl\inc\algorithm(166): note: No user-defined-conversion operator available
that can perform this conversion, or the operator cannot be called
14 c:\tools_root\cl\inc\algorithm(177): note: see reference to function template instantiat
ion '_InIt std::_Find_if_unchecked<std::_Tree_unchecked_iterator<_Mytree>,_Pr>(_InIt,_In
It,_Pr &)' being compiled
15 with
16 [
17 _InIt=std::_Tree_unchecked_iterator<std::_Tree_val<std::_Tree_simple_types
<std::pair<const std::string,double>>>>,
18 _Mytree=std::_Tree_val<std::_Tree_simple_types<std::pair<const std::string,
double>>>,
19 _Pr=main::<lambda_b863c1c7cd07048816f454330789acb4>
20 ]
21 main.cpp(13): note: see reference to function template instantiation '_InIt std::find_if
<std::_Tree_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<const _Kty,_Ty>>>>
,main::<lambda_b863c1c7cd07048816f454330789acb4>>(_InIt,_InIt,_Pr)' being compiled
22 with
23 [
24 _InIt=std::_Tree_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<
const std::string,double>>>>,
25 _Kty=std::string,
26 _Ty=double,
27 _Pr=main::<lambda_b863c1c7cd07048816f454330789acb4>
28 ]
Here, again, we provide the chain of instantiations with the information telling us what was instantiated by which arguments and where in the code, and we see twice that we
cannot convert from ’std::pair<const _Kty,_Ty>’ to ’const std::string’
with
[
_Kty=std::string,
_Ty=double
]
Missing const
on Some Compilers
Unfortunately, it sometimes happens that generic code is a problem only with some compilers. Consider the following example:
basics/errornovel2.cpp
#include <string>
#include <unordered_set>
class Customer
{
private:
std::string name;
public:
Customer (std::string const& n)
: name(n) {
}
std::string getName() const {
return name;
}
};
int main()
{
// provide our own hash function:
struct MyCustomerHash {
// NOTE: missing const is only an error with g++ and clang:
std::size_t operator() (Customer const& c) {
return std::hash<std::string>()(c.getName());
}
};
// and use it for a hash table of Customers:
std::unordered_set<Customer,MyCustomerHash> coll; …
}
With Visual Studio 2013 or 2015, this code compiles as expected. However, with g++ or clang, the code causes significant error messages. On g++ 6.1, for example, the first error message is as follows:
1 In file included from /cygdrive/p/gcc/gcc61-include/bits/hashtable.h:35:0,
2 from /cygdrive/p/gcc/gcc61-include/unordered_set:47,
3 from errornovel2.cpp:2:
4 /cygdrive/p/gcc/gcc61-include/bits/hashtable_policy.h: In instantiation of 'struct std::
__detail::__is_noexcept_hash<Customer, main()::MyCustomerHash>':
5 /cygdrive/p/gcc/gcc61-include/type_traits:143:12: required from 'struct std::__and_<
std::__is_fast_hash<main()::MyCustomerHash>, std::__detail::__is_noexcept_hash<Customer,
main()::MyCustomerHash> >'
6 /cygdrive/p/gcc/gcc61-include/type_traits:154:38: required from 'struct std::__not_<
std::__and_<std::__is_fast_hash<main()::MyCustomerHash>, std::__detail::__is_noexcept_
hash<Customer, main()::MyCustomerHash> > >'
7 /cygdrive/p/gcc/gcc61-include/bits/unordered_set.h:95:63: required from 'class std::
unordered_set<Customer, main()::MyCustomerHash>'
8 errornovel2.cpp:28:47: required from here
9 /cygdrive/p/gcc/gcc61-include/bits/hashtable_policy.h:85:34: error: no match for call to
'(const main()::MyCustomerHash) (const Customer&)'
10 noexcept(declval<const _Hash&>()(declval<const _Key&>()))>
11 ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
12 errornovel2.cpp:22:17: note: candidate: std::size_t main()::MyCustomerHash::operator()(
const Customer&) <near match>
13 std::size_t operator() (const Customer& c) {
14 ^~~~~~~~
15 errornovel2.cpp:22:17: note: passing 'const main()::MyCustomerHash*' as 'this' argument
discards qualifiers
immediately followed by more than 20 other error messages:
16 In file included from /cygdrive/p/gcc/gcc61-include/bits/move.h:57:0,
18 from /cygdrive/p/gcc/gcc61-include/bits/stl_pair.h:59,
19 from /cygdrive/p/gcc/gcc61-include/bits/stl_algobase.h:64,
20 from /cygdrive/p/gcc/gcc61-include/bits/char_traits.h:39,
21 from /cygdrive/p/gcc/gcc61-include/string:40,
22 from errornovel2.cpp:1:
23 /cygdrive/p/gcc/gcc61-include/type_traits: In instantiation of 'struct std::__not_<std::
__and_<std::__is_fast_hash<main()::MyCustomerHash>, std::__detail::__is_noexcept_hash<
Customer, main()::MyCustomerHash> > >':
24 /cygdrive/p/gcc/gcc61-include/bits/unordered_set.h:95:63: required from 'class std::
unordered_set<Customer, main()::MyCustomerHash>'
25 errornovel2.cpp:28:47: required from here
26 /cygdrive/p/gcc/gcc61-include/type_traits:154:38: error: 'value' is not a member of 'std
::__and_<std::__is_fast_hash<main()::MyCustomerHash>, std::__detail::__is_noexcept_hash<
Customer, main()::MyCustomerHash> >'
27 : public integral_constant<bool, !_Pp::value>
28 ^~~~
29 In file included from /cygdrive/p/gcc/gcc61-include/unordered_set:48:0,
30 from errornovel2.cpp:2:
31 /cygdrive/p/gcc/gcc61-include/bits/unordered_set.h: In instantiation of 'class std::
unordered_set<Customer, main()::MyCustomerHash>':
32 errornovel2.cpp:28:47: required from here
33 /cygdrive/p/gcc/gcc61-include/bits/unordered_set.h:95:63: error: 'value' is not a member
of 'std::__not_<std::__and_<std::__is_fast_hash<main()::MyCustomerHash>, std::__detail::
__is_noexcept_hash<Customer, main()::MyCustomerHash> > >'
34 typedef __uset_hashtable<_Value, _Hash, _Pred, _Alloc> _Hashtable;
35 ^~~~~~~~~~
36 /cygdrive/p/gcc/gcc61-include/bits/unordered_set.h:102:45: error: 'value' is not a member
of 'std::__not_<std::__and_<std::__is_fast_hash<main()::MyCustomerHash>, std::__detail::
__is_noexcept_hash<Customer, main()::MyCustomerHash> > >'
37 typedef typename _Hashtable::key_type key_type;
38 ^~~~~~~~
…
Again, it’s hard to read the error message (even finding the beginning and end of each message is a chore). The essence is that deep in header file hashtable_policy.h
in the instantiation of std::unordered_set<>
required by
std::unordered_set<Customer,MyCustomerHash> coll;
there is no match for the call to
const main()::MyCustomerHash (const Customer&)
in the instantiation of
noexcept(declval<const _Hash&>()(declval<const _Key&>()))>
~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
(declval<const _Hash&>()
is an expression of type main()::MyCustomerHash
). A possible “near match” candidate is
std::size_t main()::MyCustomerHash::operator()(const Customer&)
which is declared as
std::size_t operator() (const Customer& c) {
^~~~~~~~
and the last note says something about the problem:
passing ’const main()::MyCustomerHash*’ as ’this’ argument discards qualifiers
Can you see what the problem is? This implementation of the std::unordered_set
class template requires that the function call operator for the hash object be a const
member function (see also Section 11.1.1 on page 159). When that’s not the case, an error arises deep in the guts of the algorithm.
All other error messages cascade from the first and go away when a const
qualifier is simply added to the hash function operator:
std::size_t operator() (const Customer& c) const {
…
}
Clang 3.9 gives the slightly better hint at the end of the first error message that operator()
of the hash functor is not marked const
:
…
errornovel2.cpp:28:47: note: in instantiation of template class ’std::unordered_set<Customer
, MyCustomerHash, std::equal_to<Customer>, std::allocator<Customer> >’ requested here
std::unordered_set<Customer,MyCustomerHash> coll;
^
errornovel2.cpp:22:17: note: candidate function not viable: ’this’ argument has type ’const
MyCustomerHash’, but method is not marked const
std::size_t operator() (const Customer& c) {
^
Note that clang here mentions default template parameters such as std::allocator<Customer>
, while gcc skips them.
As you can see, it is often helpful to have more than one compiler available to test your code. Not only does it help you write more portable code, but where one compiler produces a particularly inscrutable error message, another might provide more insight.
The organization of source code in header files and CPP files is a practical consequence of various incarnations of the one-definition rule or ODR. An extensive discussion of this rule is presented in Appendix A.
The inclusion model is a pragmatic answer dictated largely by existing practice in C++ compiler implementations. However, the first C++ implementation was different: The inclusion of template definitions was implicit, which created a certain illusion of separation (see Chapter 14 for details on this original model).
The first C++ standard ([C++98]) provided explicit support for the separation model of template compilation via exported templates. The separation model allowed template declarations marked as export
to be declared in headers, while their corresponding definitions were placed in CPP files, much like declarations and definitions for nontemplate code. Unlike the inclusion model, this model was a theoretical model not based on any existing implementation, and the implementation itself proved far more complicated than the C++ standardization committee had anticipated. It took more than five years to see its first implementation published (May 2002), and no other implementations appeared in the years since. To better align the C++ standard with existing practice, the C++ standardization committee removed exported templates from C++11. Readers interested in learning more about the details (and pitfalls) of the separation model are encouraged to read Sections 6.3 and 10.3 of the first edition of this book ([VandevoordeJosuttisTemplates1st]).
It is sometimes tempting to imagine ways of extending the concept of precompiled headers so that more than one header could be loaded for a single compilation. This would in principle allow for a finer grained approach to precompilation. The obstacle here is mainly the preprocessor: Macros in one header file can entirely change the meaning of subsequent header files. However, once a file has been precompiled, macro processing is completed, and it is hardly practical to attempt to patch a precompiled header for the preprocessor effects induced by other headers. A new language feature known as modules (see Section 17.11 on page 366) is expected to be added to C++ in the not too distant future to address this issue (macro definitions cannot leak into module interfaces).
• The inclusion model of templates is the most widely used model for organizing template code. Alternatives are discussed in Chapter 14.
• Only full specializations of function templates need inline
when defined in header files outside classes or structures.
• To take advantage of precompiled headers, be sure to keep the same order for #include
directives.
• Debugging code with templates can be challenging.
1 With some implementations this string is mangled (encoded with types of arguments and names of surrounding scopes to distinguish it from other names), but a demangler is available to turn it into human-readable text.
2 In theory, the standard headers do not actually need to correspond to physical files. In practice, however, they do, and the files are very large.