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 the C++ language, there are not many features that 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 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) is available at https://github.com/Studiofreya/code-samples/tree/master/circular-templates.
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.
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 the code above fail to compile with g++.
Here is the error with g++.
Here is the fixed code where it will compile in both MSVC and g++.
This file will compile in both MSVC and G++, and with MSVC it'll produce identical assembly code for both files.
The clue to solving this problem is to declare both classes before providing the definitions (implementations). It's not possible to split the declaration and definition into separate files, but you can structure them as if they were in separate files. Here both Entity and Component are declared before any implementation code.
In the end, this might or might not be a problem you will encounter. But when you encounter it, you'll know about it and remember there are solutions available.
All code in the post (and build scripts) is available at https://github.com/Studiofreya/code-samples/tree/master/circular-templates.
The problem
In the C++ language, there are not many features that 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 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) is available at https://github.com/Studiofreya/code-samples/tree/master/circular-templates.
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 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 ); ^
Clean code (ideal)
Here is the fixed code where it will compile in both MSVC and g++.
// This file compiles in both Visual Studio 2010 (and newer) and G++ with C++11. 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 ); MessageHandler m_MessageHandler; }; class Entity { public: template<typename MSG> void setComponentHandler( Component & component ); MessageHandler m_MessageHandler; }; // Implementation of circular dependencies template<typename MSG> void Component::setEntityHandler( Entity & entity ) { auto handleMsg = [&] ( const MSG & msg ) { /* do stuff */ }; entity.m_MessageHandler.setHandler<MSG>( handleMsg ); } template<typename MSG> void Entity::setComponentHandler( Component & component ) { auto handleMsg = [&] ( const MSG & msg ) { /* do stuff */ }; component.m_MessageHandler.setHandler<MSG>( handleMsg ); } // Main int main() { Component c; Entity e; e.setComponentHandler<MsgDouble>(c); return 0; }
This file will compile in both MSVC and G++, and with MSVC it'll produce identical assembly code for both files.
The clue to solving this problem is to declare both classes before providing the definitions (implementations). It's not possible to split the declaration and definition into separate files, but you can structure them as if they were in separate files. Here both Entity and Component are declared before any implementation code.
In the end, this might or might not be a problem you will encounter. But when you encounter it, you'll know about it and remember there are solutions available.
All code in the post (and build scripts) is available at https://github.com/Studiofreya/code-samples/tree/master/circular-templates.