Home > database >  #include recursion with template
#include recursion with template

Time:02-15

I have problem like there Why can templates only be implemented in the header file? (and there Correct way of structuring CMake based project with Template class) but with include recursion.

Code:

A.h

#pragma once
#include "B.h"

struct A
{
    B b;
    void doThingA() {}
};

B.h

#pragma once
struct A;

struct B
{
    A *a;
    template<typename T>
    void doThingB();
};

#include "A.h"

template<typename T>
void B::doThingB()
{
    a->doThingA();
}

main.cpp

#include "A.h"

int main() {}

Error:

In file included from A.h:2,
                 from main.cpp:1:
B.h: In member function 'void B::doThingB()':
B.h:16:6: warning: invalid use of incomplete type 'struct A'
   16 |     a->doThingA();
      |      ^~
B.h:2:8: note: forward declaration of 'struct A'
    2 | struct A;
      |        ^

B.h includes from A.h but A.h required by template function implementation in B.h does not. I'm also cannot take implementation to an .cpp because of template.

It's possible to solve by using explicit instantiation of templates but I'm wondering for another solution.

CodePudding user response:

When you have types that are this tightly coupled, the simplest solution is to put them in a single header file.

#pragma once

// forward declaration of A
struct A;

// declare B, since A needs it
struct B
{
    A *a;
    template<typename T>
    void doThingB();
};

// now we can declare A
struct A
{
    B b;
    void doThingA() {}
};

// and finally, implement the parts of B that need A
// and need to be in the header
template<typename T>
void B::doThingB()
{
    a->doThingA();
}

If you still want a B.h, then it can be a single line:

#include "A.h"

If you want to split A/B into multiple headers for your own organization, a simple solution is to add compile-time checks to ensure the files aren't included directly.

// A.h
#pragma once

#define A_IMPL

#include "B_impl.h"

#undef A_IMPL
// B.h
#pragma once

#ifndef A_IMPL

#error "B_impl.h can't be included directly; use A.h"

#endif

struct A;

struct B
{
    A *a;
    template<typename T>
    void doThingB();
};

#include "A_impl.h"

template<typename T>
void B::doThingB()
{
    a->doThingA();
}
// A_impl.h
#pragma once

#ifndef A_IMPL

#error "A_impl.h can't be included directly; use A.h"

#endif

struct A
{
    B b;
    void doThingA() {}
};

You can make the #ifdef checks more complicated if you want more of your headers to use the impl files directly, but the public interface remains blissfully unaware.

CodePudding user response:

Based on Stephen's solution I came up with that architecture:

A.h

#pragma once
#define A_H
#include "B.h"

struct A
{
    B b;
    void doThingA() {}
};

#include "B_impl.h"

B.h

#pragma once
struct A;

struct B
{
    A *a;
    template <typename T>
    void doThingB();
};

#ifndef A_H
#include "B_impl.h"
#endif

B_impl.h

#pragma once
#include "A.h"

template <typename T>
void B::doThingB()
{
    a->doThingA();
}

main.cpp

#include "A.h"

int main() {}

So I can include A.h and B.h separately and also it looks cleaner for me.

  • Related