Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

 

...

Introduction

The classical use of templates is well illustrated by examples such as vector in the C++ STL. The entire definition of the template must be available to the compiler in order to handle code that instantiates LSST types such as std::vector<DataProperty>.

The case of many of the LSST framework classes is quite different. We have a templated class Image<typename T>, but we do not expect users to instantiate a wild and wooly zoo of Images; a type such as Image<DataProperty> makes no sense (and won't compile). As a consequence, we can move the definitions of much of the implementation of these classes out of the header files, which produces at least three desirable effects:

  • Compile times are shorter.
  • Implicit dependencies are (sometimes greatly) reduced, as much less of the supporting structure for the classes is exposed to user code that includes e.g. lsst/fw/Image.h.
  • Self-contained (even shared) libraries can be built.

For true container classes like the STL that require full definitions, we will still place the definitions in separate files, but these implementation files will be included in the header files. In the absence of compiler options this leads to code duplication, but we will live with it until the overhead, similar to that of inline functions, proves excessive.

Explicit Instantiation

For cases such as Image we will use explicit instantiation. A complete example is here, and consists of three source files (Foo.h, Foo.cc and main.cc) and an SConstruct file.

Foo.h looks something like:

Code Block
languagecpp
titleFoo.h
template<typename T>
class Foo {
public:
    explicit Foo();
    explicit Foo(T x);
    ~Foo();

    T getVal() const;
    void setVal(T val);
private:
    T _val;
};

(it actually has more to it; see below). You'll notice that the class is defined, but none of its members are.

Going to main.c, you'll see

Code Block
languagecpp
titlemain.c
#include <iostream> #include "Foo.h" 
int main() {
    Foo<int> i;
    Foo<float> x(1.234);

    std::cout << "i = " << i.getVal() << ", " << x.getVal() << std::endl;    

    i.setVal(10);
    std::cout << "i = " << i.getVal() << std::endl;
}

 

Foo without any knowledge of its implementation. This is a Good Thing; but how was it achieved? Look at Foo.cc:

Code Block
languagecpp
titleFoo.cc
/// Implementation of templated class Foo #include "Foo.h" 
template<typename T>
Foo<T>::Foo() : _val(0) {}

template<typename T>
Foo<T>::Foo(T val) : _val(val) {}

template<typename T>
Foo<T>::~Foo() {}

template<typename T>
T Foo<T>::getVal() const { return _val; }

template<typename T>
void Foo<T>::setVal(T val) { _val = val; }
// // Explicit instantiations // template class Foo<float>;
template class Foo<int>;

 

Here are the missing method definitions, along with the lines

Code Block
languagecpp
template class Foo<float>;
template class Foo<int>;

which tell the compiler to generate the classes Foo<float> and Foo<int>; exactly the classes that main.cc uses.

More Explicit Instantiation

If you look at Foo.h you'll see that there's more code than discussed above. The main features are that an inline member of Foo needs to be fully specified in Foo.h (how else could the compiler inline it?), and also an example of explicit instantiation of a function, rather than a class. There should be no surprises.

What about extern template and -no-implicit-templates?

These code examples didn't use either extern template or -no-implicit-templates; why not?

The former, extern template is not (yet) in the C++ standard (but see C++ proposal N1987), while the latter is a g++ command line flag, so what would we gain by using them?

The example C++ in the previous section didn't give the compiler a choice; even if it wanted to instantiate Foo<int> in main.cc it simply didn't know enough. However, if we'd included the full template implementation in Foo.h, matters would have been different; exactly what happens isn't specified by the C++ standard as you cannot tell what the compiler decided, but many compilers (including g++) generate a definition of the class and its members in each object file, and then use the linker to remove duplicates.

It is this behaviour that is modified by extern template and -no-implicit-templates; they instruct the compiler not to instantiate Foo<int> even if it could.

How do I know what it did? Well, the most direct way (on your average unix box) is to say

Code Block
languagebash
nm main.o | c++filt | grep 'Foo(float)'

which results in

Code Block
languagetext
U Foo<float>::Foo(float)

...

Code Block
languagecpp
W Foo<float>::Foo(float)

unless you add an extern template Foo<float> or compile with -no-implicit-templates.

Legalities

Careful reading of Stroustrup implies that this technique is legal, and "Effective C++" states as much (but I trust them less).

The relevant passage is Section 13.7 (p. 351), which is talking about the use of the export keyword. We're not using export (see WhyNotExport), but the compiler doesn't know that when it compiles out.h:

Code Block
languagecpp
// out.h:
   template<class T> void out(const T& t);

// user1.c:
   #include "out.h"
   // use out()

I therefore deduce that header files that declare templates without definitions are legal.