Home > Software engineering >  Compiler error when a templated member function needs a forward declared type to be completed
Compiler error when a templated member function needs a forward declared type to be completed

Time:08-09

I have the following code:

// a.h
#ifndef HEADER_A
#define HEADER_A

#include "b.h"

#include <iostream>

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

#endif
// b.h
#ifndef HEADER_B
#define HEADER_B

#include "a.h"

struct A;

struct B {
    A* a = nullptr;

    template <typename T>
    void foo() {
        a->bar();
    }
};

#endif
// main.cpp
#include "a.h"

void A::bar() {
    std::cout << "bar" << std::endl;
}

int main() {
    A a;
    a.b.foo<int>();
}

And when I try to compile it with g -std=c 20 main.cpp -o main.o it works as expected (outputs bar) but gives me the warning:

<source>:10:10: warning: invalid use of incomplete type 'struct A'
   10 |         a->bar();
      |          ^~

However, both MSVC and clang rejects the code when the standard is set to C 20. MSVC is a little bit weirder because it accepts the code without any warning in C 17.

Here are the compiler explorer link for reference: https://godbolt.org/z/cj9Wo61d5 (preprocessors are manually expanded)

I would expect this to work since B::foo doesn't get used until the 2nd line in main(), by which time A should have already been completed.

In my understanding (which might be wrong), the implementation of member templates are only instantiated at the point of use, for one example is that you can create an std::map<std::string, T> with T lacking a default constructor, but only get an error when you are trying to do map["key"].

Is my understanding wrong or am I missing something obvious?

CodePudding user response:

You may the solve circular dependencies and the invalid use of incomplete type 'struct A' by using a template class parameter.

// a.h
#ifndef HEADER_A
#define HEADER_A

#include "b.h"

#include <iostream>

struct A {
    B<A> b;
    void bar();
};

#endif
// b.h
#ifndef HEADER_B
#define HEADER_B

<typename U>
struct B {
    U* a = nullptr;

    template <typename T>
    void foo() {
        a->bar();
    }
};

#endif
// main.cpp
#include "a.h"

void A::bar() {
    std::cout << "bar" << std::endl;
}

int main() {
    A a;
    a.b.foo<int>();
}

CodePudding user response:

Here is another solution I found. It is quite cumbersome but gets the job done.

I need to create a new header called decl_b.h which includes the full definition of struct B but not any of its templated members' implementation:

// decl_b.h
#ifndef HEADER_DECL_B
#define HEADER_DECL_B

struct A;

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

#endif

Then I changed a.h to be the following:

// a.h
#ifndef HEADER_A
#define HEADER_A

#include "decl_b.h"

#include <iostream>

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

#endif

And b.h to only define the templated member function:

#ifndef HEADER_B
#define HEADER_B

#include "decl_b.h"

#include "a.h"

template <typename T>
void B::foo() {
    a->bar();
}

#endif

In main.cpp I included b.h instead of a.h. The effect here is analogous to how we split out declarations and implementations in .h/.cpp files, but now we do the linker's job manually by including the header containing the template function implementations only if it's needed.

  • Related