9

Usually in C++ when I need interdependencies between classes, I use forward declarations in the header files and then include both header files in each cpp file.

However this approach breaks when working with templates. Because templates have to be entirely in the header files (not counting the case when I put the code into cpp and enumerate template class A<T>; for each supported T - this is not always feasible, e.g. when T is a lambda).

So is there a way to declare/define interdependent templates in C++?

Code example

template<typename T> struct B;
template<typename T> struct A {
  void RunA(B<T> *pB) {
    // need to do something to B here
  }
};

template<typename T> struct B {
  void RunB(A<T> *pA) {
    // need to do something to A here
  }
};

If I start doing something to B in RunA(), I think, I will get a "missing definition" error because only forward declaration of B is available by the time RunA() is compiled.

Perhaps there is some trick to organize header files e.g. by splitting each header into class definition and method definition files, and then including them in some fancy way. Or maybe something can be done via a third/fourth class. But I can't imagine how specifically to do this.

C++11/14/17 are ok (specifically, it's MSVC++2017, toolset v141).

Serge Rogatch
  • 13,865
  • 7
  • 86
  • 158
  • Instead of listing the year of the compiler, please list the version, since VS15 has the ability to install 3 different compiler versions (all previous ones only had one). – tambre Jul 30 '17 at 18:17
  • @SergeyB., this solves the particular problem for the code example I gave. However, I would like a general guideline like "instead of having a .h and a .cpp file, with templates you need 3rd type of file like .inl , which will contain the function definitions" and then an explanation how to manage includes.... – Serge Rogatch Jul 30 '17 at 18:28
  • Probably you can make the member method a function template and force the type through a `static_assert` like: `template – skypjack Jul 30 '17 at 20:51
  • @skypjack , I would prefer a simple and scalable solution like separate headers proposed by VTT. Though I'm in doubt whether it's the simplest possible solution... 4 headers seems too much. – Serge Rogatch Jul 30 '17 at 22:36
  • @SergeRogatch Well, by the time a third class will enter the cycle, you'll have probably reviewed the mean you gave to the word _scalable_. :-) – skypjack Jul 31 '17 at 05:25

2 Answers2

13

You can utilize fine-grained headers:

//  A.forward.hpp
template<typename T> struct A;

//  A.decl.hpp
#include "A.forward.hpp"
#include "B.forward.hpp"

template<typename T> struct A
{
    void RunA(B<T> *pB);
};

//  A.impl.hpp
#include "A.decl.hpp"
#include "B.hpp"

template<typename T> void A< T >::
RunA(B<T> *pB)
{
    // need to do something to B here
}

//  A.hpp // this one should be included by code using A
#include "A.decl.hpp"
#include "A.impl.hpp"

//  B.forward.hpp
template<typename T> struct B;

//  B.decl.hpp
#include "B.forward.hpp"
#include "A.forward.hpp"

template<typename T> struct B
{
    void RunB(A<T> *pA);
};

//  B.impl.hpp
#include "B.decl.hpp"
#include "A.hpp"

template<typename T> void B< T >::
RunB(A<T> *pA)
{
    // need to do something to A here
}

//  B.hpp // this one should be included by code using B
#include "B.decl.hpp"
#include "B.impl.hpp"

Obviously all of these headers also need some sort of header guards that I omitted here. Important notice: .impl.hpp headers are considered internal and should never be used by the outside code while .forward.hpp, .decl.hpp and .hpp can be used anywhere.

This approach does introduce circular include dependencies, however this does not lead to problems: code structure produced by inclusion of headers ensures that code parts are included in the following order: forward declarations, class definitions, methods definitions.

user7860670
  • 35,849
  • 4
  • 58
  • 84
  • There is a circular inclusion in this design: A.hpp -> A.impl.hpp -> B.hpp -> B.impl.hpp -> A.hpp – Serge Rogatch Jul 30 '17 at 18:41
  • 1
    @SergeRogatch Yes, however A.hpp won't be included second time and this won't cause problems. It will work fine if you include just A.hpp, just B.hpp or both of them in any order. You should try it yourself. Obviously all of these headers also need some sort of header guards that I omitted here. Important notice: .decl.hpp and .impl.hpp headers are considered internal and should never be used by the outside code while .forward.hpp and .hpp can be used anywhere. – user7860670 Jul 30 '17 at 18:43
  • That's worth describing in your answer. Also, is this some standard/best practice? Or have you just come up with this solution? I wonder about how big is the chance that there is a better solution... – Serge Rogatch Jul 30 '17 at 18:51
  • 1
    @SergeRogatch Well, I've been practicing this approach along with [SKU](https://en.wikipedia.org/wiki/Single_Compilation_Unit) at least for the past five years. And it works really great. Typically I also add `.test.hpp` containing unit tests while separation into `.decl.hpp` and `.impl.hpp` is typically not necessary. – user7860670 Jul 30 '17 at 18:55
  • I'm very happy that you've got a chance to present your approach to the world! :) . Could you also describe how this can go without separating into `.decl.hpp` and `.impl.hpp`, or did you mean something else (e.g. when there is no interdependency)? Also, I'm having a hard time imagining how this approach can be extended to 3, 4, and more interdependent classes - could you clarify? – Serge Rogatch Jul 30 '17 at 19:13
  • I personally would only keep the `x.decl.h` and `x.impl.h` files, the latter ones being renamed to solely `x.h`. It appears more appropriate to me to include the declaration only headers inside the impl files, any reason for including the full headers instead? – Aconcagua Sep 08 '19 at 16:25
0

You can separate the declarations and definitions of classes. Therefore you can separate the declarations and definitions of template classes...

You can separate the declarations and definitions of class methods. Therefore you can separate the declarations and definitions of template class methods:

template<typename T> struct B;     // declaration

template<typename T> struct A {     // definition
  void RunA(B<T> *pB);  // declaration
};

template<typename T> struct B {     // definition
  void RunB(A<T> *pA);   // declaration
};

// definition
template<typename T>
void A<T>::RunA(B<T> *pB) {
    // need to do something to B here
  }

// definition    
template<typename T>
void B<T>::RunB(A<T> *pA) {
    // need to do something to A here
  }
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • This is clear. But in practice it's inconvenient to have everything in one file. The question is how to split these declarations/definitions into header files and how to include them then. – Serge Rogatch Jul 30 '17 at 18:34
  • 1
    @SergeRogatch the same way you would with .hpp and .cpp. The norm is to use the extension .ipp for template function definitions. – Richard Hodges Jul 30 '17 at 21:58
  • ipp? Interesting, never saw that in 15 years of practice... Usually .inc instead, sometimes .impl... But like it. – Aconcagua Sep 08 '19 at 16:28