C++ template declarations vs. definitions

There's an area of C++ which is surprisingly little known and little understood even by many expert C++ programmers. I have heard of people who have programmed in C++ professionally for over a decade, and they are quite fluent at template programming, yet have surprisingly little knowledge about this subject. More precisely: Template declarations.

In order to clear any possible confusion, let me first explain what "declaration", as opposed to "definition", means in C++ (and some other languages).

Declaration vs. definition

A declaration simply tells that a function or a class with a certain name and signature exists somewhere, but without specifying its implementation. For example, this is a function declaration, very typical in C and C++ code:

int someFunction(double, double);

What that line says is that there exists a function named someFunction which takes two doubles as parameter and returns an int. However, the implementation of that function may be somewhere else, in a completely different compilation unit. (Basically this is an instruction for the compiler that says that whenever this function is called, for the compiler to generate instructions for the linker to put a proper function call in that place.)

The definition of the function would be its implementation. A function can be declared many times in many compilation units, but it must be defined (ie. implemented) only once in one compilation unit (or else the linker will give an error about multiple definitions).

Class declarations vs. definitions is a bit more complicated subject, as there are basically three stages:

  1. A class name can be declared by simply stating that a class with that name exists somewhere, like:
    class SomeClass;

    This is also called an "incomplete type" (eg. by the C++ standard). It is called so because such declaration does not have enough information for the code to create instances of the class nor call any of its member functions. (The only thing the code can do with an incomplete type is to pass pointers and references of that type around.)

  2. A class can be defined, but its member functions only declared. For example:
    class SomeClass
    {
     public:
        void someMemberFunction(int);
    };

    This is a class definition in the sense that it allows the code to create instances of the class and call its member functions. However, the member function someMemberFunction() has only been declared rather than defined (which like with regular function declarations means that its implementation is somewhere else).

  3. Member function definitions. The member functions can be defined (ie. implemented) either directly in the class definition, or separately in a different place similarly to how regular functions are defined.

Template declarations vs. definitions

Quite many C++ programmers, even those who are quite fluent at template programming, have the mistaken notion that template functions and classes can only be defined, not declared. In other words, whenever you write a template function, you must give its implementation right there, and likewise for a class. For example, that you must write code like:

template<typename T>
T min(T v1, T v2)
{
    return v1 < v2 ? v1 : v2;
}

It's surprisingly little known that you can, in fact, write templated function declarations instead. In other words, the following is completely valid:

template<typename T>
T min(T v1, T v2);

Many C++ programmers are surprised that the above even compiles rather than giving an error message outright. (It will give a linker error if the function has not been defined and instantiated somewhere, but the declaration above is completely valid.)

Basically what the code above does is that when it's called, the compiler will create a declaration of the min() function with the specified template parameter type. It's then up to the linker to try to find a definition of this specific function somewhere (which obviously must be provided by the programmer somehow, or else a linker error will be issued, naturally).

Likewise templated class member functions can be declared, without a definition.

Even less known is the fact that template classes can be declared, with no implementation. You can, in fact, write something like:

template<typename T>
class SomeClass;

Since the type T is not actually being used in this declaration for anything, it can be omitted in the same way as function parameter names can be omitted when they are not used (another detail surprisingly few C++ programmers know). In other words, the above is completely equivalent to:

template<typename>
class SomeClass;

If SomeClass is referred to ("instantiated" so to speak) with a type, the compiler will then create a class declaration (rather than a definition) of that class with the specified template type. This declaration will work in the exact same way as regular (non-template) class declarations.

Practical usage example 1: Friend template classes

One practical situation where templated class declarations can be used (again a "trick" very little known even by expert C++ programmers) is to declare friend classes, when those classes are template classes. Few C++ programmers know how to do that, but it's rather simple, really. All that one has to say is:

class A
{
    template<typename>
    friend class B;
    ...
};

What the above code is saying is that if there's a template class B (which takes one template type parameter), it's a friend of class A (whatever that template parameter might be). Whenever any class of type B does anything to an object of type A, the template declaration above will kick in and make that B a friend class of A (regardless of which template parameter type B was instantiated with).

(We could give the template parameter in the friend class declaration above a name, but as commented earlier, since this name is not used for anything, we can simply omit it.)

A practical situation where one wants to do the above is when dealing with intrusive smart pointers (or other similar constructs).

An intrusive smart pointer requires for a reference count to exist in the object being managed, and for a way to increment and decrement that count. Since it would be needlessly laborious (and bad object-oriented design) to add such members to all the classes being managed, a common solution is to create a base class from which all classes with a reference counter inherit. In other words, when you write a class which will be managed with such an intrusive smart pointer, you do it like:

class MyClass: public ReferenceCountable
{ ... };

The ReferenceCountable base class (which contains a reference count member variable) is provided by the same library that provides the intrusive smart pointer itself.

Of course there's a design conundrum with respect to ReferenceCountable: How should the intrusive smart pointer be able to access the reference count? Should the reference count be a public member? That would be quite ugly design-wise. Should there be some public member functions to increment and decrement the counter? Slightly better, but it's still a dubious design because the reference counter is basically a private detail of the intrusive smart pointer library, yet the public functions allow for outside code to tamper with the count variable.

The optimal solution would be, naturally, for the reference count to be a private member of the ReferenceCountable class, and for the intrusive smart pointer to be the only other class allowed to access it. This can be done with a friend statement. However, since the intrusive smart pointer is a template class by necessity, most C++ programmers don't know how to do that. However, as stated, it's rather simple (let's assume that the smart pointer class is named "IntrusivePtr"):

class ReferenceCountable
{
    template<typename> friend class IntrusivePtr;
    size_t referenceCount;

 public:
    ReferenceCountable(): referenceCount(1) {}
    ReferenceCountable(const ReferenceCountable&) {}
    ReferenceCountable& operator=(const ReferenceCountable&) { return *this; }
};

Now the template class IntrusivePtr will be able to access the private reference count, but no other classes can.

(Note the empty copy constructor and assignment operator implementations. This is not a mistake nor an error, but in fact such a base class must be implemented like that. I'll leave it as an exercise to the reader to figure out why. Suffice to say that approximately 99% of intrusive smart pointer implementations out there, even ones made by expert C++ programmers, omit these, causing malfunction in certain situations.)

Practical usage example 2: "Manual" export templates

The C++98 standard defines so-called "export templates", which would allow template functions and template class member functions to simply be declared, while having their implementation in a separate compilation unit, which would then be automatically instantiated by the linker for each used type. Because compilers have not implemented this feature (even though it would be quite useful) as it's deemed way too laborious to implement, the next C++ standard will deprecate the feature.

Not all is lost, though. Export templates can still be "emulated" in a slightly more limited way by making use of template declarations, as described earlier. As commented in the beginning, template functions can be declared and this will not cause any error as long as the function is then defined and instantiated somewhere where the linker can find it.

So let's take the earlier example of the min() function which was only declared but not defined (eg. in a header file):

template<typename T>
T min(T v1, T v2);

Once we have made such a declaration, we can call that function from our code. It's only at the linking stage that the linker will complain that it cannot find the definition. But we can provide such a definition somewhere, and in fact, do so in a way which "emulates" pretty much what export templates would do (even though there are limitations).

What we can do is to write the implementation of the above function in a compilation unit and then use explicit template instantiation to create an instance of that function. The explicit instantiation has to be done for each type that is being used with the function. For example, if the min() function above is being called with the types int and double, we would do the following (in some source file somewhere):

template<typename T>
T min(T v1, T v2)
{
    return v1 < v2 ? v1 : v2;
}

template int min(int, int);
template double min(double, double);

Explicit template instantiation is a special syntax that C++ provides to explicitly instantiate a template function or class with a certain type. In the most basic form this happens simply by writing the keyword 'template' followed by the function declaration with the desired type substituted for the template parameters, as done above.

If a template function doesn't return nor take any parameter of the template type, explicit instantiation can still be done with the syntax:

template void someTemplateFunction<int>();

The same idea works when explicitly instantiating template classes:

template class SomeTemplateClass<int>;

What the min() example above does is that it first defines the function itself, and then explicitly instantiates it with the types int and double. If these were the two types which were used when calling the function somewhere else, the linker will then find these instantiations, and the linking will succeed.

As stated, this feature can be used to "emulate" export templates. Basically the only difference (and drawback) is that the template has to always be manually instantiated for each used type. There's no way to automatize this (which is what export templates would do, if supported by the compiler).

This can be done with individual template functions, individual member functions of a template class, and even entire template classes.