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.