Forward-declaring Templates and Enums

Contents

In my last post, I wrote about forward declarations for normal classes. Today, I give you some information about forward-declaring templates and enums.

Forward-declaring Enums

As I wrote in the last post, the compiler does not always need to know the definition of a class. It needs one if we use one of its members or the base class, or if it has to know how large objects of that class are. One should think that the same applies to enums, but that’s not the case. Forward-declaring plain old enums is not possible.

The good news is, that we can provide forward declarations for scoped enums aka. enum classes. We also can forward declare enums with explicit underlying types. Both features have been introduced in C++11:

enum OldEnum;                    //ERROR
enum WithUnderlyingType : short; //OK
enum class Scoped;               //OK
enum class ScopedWithType : int; //OK

The actual definitions of the enums obviously have to match the declarations. Scoped enums that are not explicitly declared or defined with an underlying type, the underlying type is int. That means it does not matter whether the definition of Scoped explicitly adds int, and whether the definition of ScopedWithType does not mention it.

Forward-declaring class templates

Forward-declaring class templates is as easy as a normal class declaration:

template <typename T, typename U> class X;

It is also possible to provide forward declarations for specializations of those class templates:

template <typename U> class X<int, U>;
template <> class X<int, int>;

Using incomplete types in templates

When we instantiate a class template that is parametrized with one of our types, the question arises whether it is sufficient to only have a forward declaration of our type. Let’s, for example, take this class definition:

class MyClass {
  //...
  std::shared_ptr<MyOtherClass> pOther; 
};

Is a forward declaration of MyOtherClass OK, or do we have to #include the full definition? The answer depends on the class template, in this case, shared_ptr. As we recall, a forward declaration of shared_ptr is not enough here, because the compiler needs to know the size. That depends on the implementation of shared_ptr and whether it contains or inherits from MyOtherClass.

It may not be much of a surprise, that shared_ptr only stores a pointer to its argument type, so a forward declaration of MyOtherClass should be OK. Except for the fact that shared_ptr defines functions that use the argument type. That means, that wherever we trigger the instantiation of one of those functions, MyOtherClass needs to be defined as well.

At first glance, that may seem OK since we usually only use the member functions of class members in the source file. However, one of those member functions is the destructor. If MyClass does not explicitly define a destructor, the compiler will do that for us. The destructor will also call the destructor of pOther, which contains a call to the destructor of MyOtherClass.

Whether and where we need the definition of MyOtherClass therefore depends on where we or the compiler define the destructor and special member functions.

Rule of thumb: use fully-defined types in templates

One of the points in using smart pointers is the Rule of Zero. We don’t want to care about destructors and the like. Another point about using abstractions like class templates is, we should not need to know the exact implementation details. At least not enough to figure out whether the implementation needs us to define the template argument or whether just forward-declaring it is enough.

And, even if we know the implementation details of such a template, we should not depend on that knowledge. What happens it the template implementation changes and suddenly needs the definition of its argument? Every class that only provides a forward declaration will break.

Bottom-line is that, in general, it is better to #include the definition of our template arguments. Only in the rare case where we need to micromanage our compile-time dependencies, we can try to use a forward declaration instead.

Forward-declaring library classes

With all I’ve written about forward declarations, it might be tempting to provide forward declarations for classes and other entities provided by libraries. For example, if I only declare a function that takes a string, why should I have to #include <string> and all the stuff that comes with it?

namespace std {
  class string;
}

Do not do this! It’s simply wrong. std::string is not a class, but a typedef to std::basic_string<char>. And no, you can’t simply add a forward declaration to template <class C> class basic_string<CharT>; because that’s not all there is to it, either.

There surely are things in other libraries that are easier to provide forward declarations for, right? Don’t be tempted to forward declare those classes and templates, either. Libraries change, classes become type aliases and vice versa. Those changes will then break your code in nasty ways.

If, however, you happen to be on the other side and write libraries yourself, consider to provide headers that contain forwarding declarations for your classes and templates. An example is the standard header <iosfwd> that provides forward declarations for things related to iostreams and the like.

Previous Post
Next Post
Posted in

7 Comments


  1. Thanks for this post, it is an interesting topic that I am also currently looking into to reduce the compile time of our code base.
    Please note that having a shared_ptr (where MyOtherClass is forward-declared) wont force you to move the destructor of MyClass into the Cpp-file. The destructor of shared_ptr does work with incomplete types, as it is a (in common implementations) virtual function call to a deleter that was created when constructing a shared_ptr from a MyOtherClass-Pointer. So only when using a construction-function (e.g. reset(T) or the constructor of shared_ptr taking a T) the definition of MyOtherClass needs to be present.

    Reply

  2. it’s a great feature of C++11 that we can forward declare enums.
    But I think it was available in MSVC, as an extension even before C++11

    Reply

  3. What strategies do you suggest for avoiding writing all your template code in a ‘public’ header?

    In cases where you know you’ll be using the templated code with just a few specific types, can you not hive that off into a separate library with pre-instantiated instances of the classes just with the specific types? Your code can then link to the concrete classes instead?

    If the code is genuinely used as generic in your code, this isn’t necessarily possible, but seems we over-pollute our headers when we put the templated class implementation direct into our public headers….

    Reply

    1. What you suggest is possible, although it reduces the usefulness of templates. It depends largely on the use case I’d say.

      Reply

  4. Why do you make distinct the difference between your code and ‘library’ code. Your code could also change its implementation?
    I notice that the Google guidelines suggest never to forward declare individual classes, for the ‘library’ reason unless you do so via a ‘libfwd’ header.

    Reply

    1. Thanks for the comment! Our own code is under our control, and we know better how likely we are to make changes that would break forward declarations.
      Having said that, it can of course be a good idea to have forwarding headers for our own common classes, too.

      Reply

      1. As an individual, yes perhaps, but as part of a larger team I am wary of the cons of this approach.

        Perhaps the header speedup for builds could be achieved in other ways?
        e.g. https://www.viva64.com/en/b/0549/ comparing single threaded, parallel, unity and precompiled header approaches

        Reply

Leave a Reply

Your email address will not be published. Required fields are marked *