How to handle circular dependencies with templates in C++

By , last updated November 12, 2019

Introduction

Although templates in C++ are well known, and more and more people actually do know how to wrap their head around them, there are a couple of less common circumstances which can leave the programmer rather clueless. This is a problem I encountered while tuning the Message-Component-Entity system in our game under development, Burnt Islands.

In particular, this article deals with the error message invalid use of incomplete type template with G++, and how to solve it when building code in G++, which was working perfectly fine with MS Visual C++.

C++ circular dependency with templates

The problem

In the C++ language, there are not many features who are as powerful as templates. But their strength is also contributing to the complexity of templates.

With standard classes and structs, you can hide the implementation of the methods within the class, and you can put those in a separate compilation units (cpp files).

With templates, there is a whole other story. When the compiler sees a template (class and method), it must know the internals of that particular method. This is because templates doesn’t really exist until they are used, and with varying template parameters, the classes and structs are generated, and is in reality completely separate classes for different parameters.

Here is one quirk, circular dependencies with templated classes and classes with templated methods. All code in the post (and build scripts) are available at Studiofreya GitHub.

In this particular case, Visual Studio is sort of stupid. It deliberately accepts code, which it really shouldn’t accept. This isn’t really a problem if you only target the Microsoft stack and environment. If you plan to deploy to other platforms, it’ll be a problem. The best advice is to get up both build systems early in the development, so any problems are caught early.

Code (problematic)

But on with the problem. Consider the following file.

// This file only compiles in Visual Studio 2010 newer

template<typename T>
class Message
{
    public:
        typedef T       type;

        static const int MsgId   = 0;

        explicit Message( const T & t )
            : data(t)
        {
        }

        type        data;
};

typedef Message<int>    MsgInt;
typedef Message<long>   MsgLong;
typedef Message<float>  MsgFloat;
typedef Message<double> MsgDouble;

class MessageHandler
{
    public:

        template<typename MSG, typename T>
        void setHandler( const T & msghandler )
        {
            /* do stuff */
            auto id = MSG::MsgId; // Make sure this is a message type
        }
};

// Forward declaration of classes
class Component;
class Entity;

class Component
{
    public:

        template<typename MSG>
        void setEntityHandler( Entity & entity )
        {
            auto handleMsg = [&] ( const MSG & msg )
            {
                /* do stuff */
            };

            entity.m_MessageHandler.setHandler<MSG>( handleMsg );
        }

        MessageHandler  m_MessageHandler;

};

class Entity
{
    public:

        template<typename MSG>
        void setComponentHandler( Component & component )
        {
            auto handleMsg = [&] ( const MSG & msg )
            {
                /* do stuff */
            };

            component.m_MessageHandler.setHandler<MSG>( handleMsg );
        }

        MessageHandler  m_MessageHandler;
};

int main()
{
    Component c;
    Entity e;

    e.setComponentHandler<MsgDouble>(c);

	return 0;
}

The problem is in this line entity.m_MessageHandler.setHandler( handleMsg ); in the Component class. It uses an undefined class which has been forward declared. Visual Studio is trying to be smart. It can see forwards and there is an implementation (definition) of the Entity class further down below. GCC (g++) is not trying to be smart, and thus will the code above fail to compile with g++.

Here is the error with g++.

g++-4.8.1 -std=c++11 circular-templates.cpp -o circdep
circular-templates.cpp: In member function 'void Component::setEntityHandler(Entity&)':
circular-templates.cpp:55:19: error: invalid use of incomplete type 'class Entity'
             entity.m_MessageHandler.setHandler( handleMsg );
                   ^
circular-templates.cpp:41:7: error: forward declaration of 'class Entity'
 class Entity;
       ^
circular-templates.cpp:55:51: error: expected primary-expression before '>' token
             entity.m_MessageHandler.setHandler( handleMsg );
                                                   ^

Professional Software Developer, doing mostly C++. Connect with Kent on Twitter.