Contents
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++.
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.
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.