Home > Back-end >  How to handle interdependent class templates
How to handle interdependent class templates

Time:01-20

I have two template classes which both depend on each other. As an example consider

template< typename > struct B;

template< typename T > struct A {
    void doSomething() {
        B<T> b{t};

        b->doSomething();

        // do some more
    }

    T t;
};

template< typename T > struct B {
    void doSomething() {
        // do something
    }

    A<T> createA() {
        // Something
    }
};

Based on my understanding of this, two-phase-lookup renders my snippet above ill-formed, because A's implementation requires type B to be complete, but at the time A is implemented here, B is still incomplete.

If these were not template classes, the problem would vanish as we would only require B in A.cpp. Therefore, when B.h includes A.h (because B should actually require A to be complete at the time it declares createA).

However, since these are templates, we have to define everything in header files, which seems to makes this a lot more complicated.

How are such situations typically resolved?

CodePudding user response:

Anything that depends on T is a dependent type, and as such is looked up during the second phase.

You only need to forward-declare A before B, then you can use A<T> safely.

Of course both A and B need to be fully defined at the time you actually instantiate them.

template< typename T > struct A;

template< typename T > struct B {
    void doSomething() {}

    A<T> createA() { return {t}; };   // parsing deferred until 2nd phase

    T t;
};

template< typename T > struct A {
    void doSomething() {}

    T t;
};

int main() {
    A<int> a{5};
    a.doSomething();
    B<int> b{12};
    A<int> c = b.createA();
}

CodePudding user response:

Thanks to @463035818_is_not_a_number's comment I think I found the solution for my problem. Instead of only providing A.hpp and B.hpp, we can also define A_fwd.hpp and B_fwd.hpp.

This allows us to also separate declaration and definition of the template's functions by first declaring the template classes in the respective _fwd.hpp files and then putting the actual definition into the regular *.hpp files (after having included the respective _fwd.hpp).

For my example this could look like this:

A_fwd.hpp

template< typename T > struct A {
    void doSomething();

    T t;
};

B_fwd.hpp

template< typename T > struct B {
    void doSomething();

    A<T> createA();

    T t;
};

A.hpp

#include "A_fwd.pp"

template< typename T > void A<T>::doSomething() {
    B<T> b{t};

    b.doSomething();

    // do some more
}

B.hpp

#include "B_fwd.hpp"

template< typename T > void B<T>::doSomething() {
    // something
}

template< typename T > A<T> B<T>::createA() {
    return { t };
}

Godbolt

While this increases the amount of required typing, it essentially gives us the same rules to play by as for non-template classes, which are implemented as separate declarations (header) and definition (cpp-file).

  • Related