Can C++ modules in Visual Studio 2022 handle forward declarations?
The following code has a circular dependency that I want to break using a forward declaration, but the compiler doesn't seem to recognize that it refers to the same type as the full class declaration, so it doesn't compile. Am I doing something wrong?
main.cpp
import A;
int main()
{
CB cb;
CC cc;
cc.MyFunc3(cb);
return 0;
}
A.ixx
export module A;
export import :B;
export import :C;
A-B.ixx
export module A:B;
class CC;
export class CB {
public:
void MyFunc(const CC& cc) const;
};
A-B.cpp
module A;
import :B;
import std;
import :C;
void CB::MyFunc(const CC& cc)
{
std::cout << "Hello World B" << std::endl;
}
A-C.ixx
export module A:C;
class CB;
export class CC {
public:
void MyFunc1(const CB& cb) const;
};
A-C.cpp
module A;
import :C;
import std;
import :B;
void CC::MyFunc1(const CB& cb)
{
std::cout << "Hello World B" << std::endl;
cb.MyFunc(*this);
}
This is the error:
1>C:\Users\peterB\repositories\hobby projecten\Hexanaut-AI\Hexanaut-AI\Hexanaut-AI\A-C.cpp(7,10): error C2511: 'void CC::MyFunc1(const CB &)': overloaded member function not found in 'CC'
1>C:\Users\peterB\repositories\hobby projecten\Hexanaut-AI\Hexanaut-AI\Hexanaut-AI\A-C.ixx(5,14):
1>see declaration of 'CC'
1>C:\Users\peterB\repositories\hobby projecten\Hexanaut-AI\Hexanaut-AI\Hexanaut-AI\A-C.cpp(10,5): error C2027: use of undefined type 'CB'
1>C:\Users\peterB\repositories\hobby projecten\Hexanaut-AI\Hexanaut-AI\Hexanaut-AI\A-C.ixx(3,7):
1>see declaration of 'CB'
1>C:\Users\peterB\repositories\hobby projecten\Hexanaut-AI\Hexanaut-AI\Hexanaut-AI\A-C.cpp(10,16): error C2671: 'CC::MyFunc1': static member functions do not have 'this' pointers
Relevant compiler options:
/std:c++latest /experimental:module
With 'Build ISO C++23 standard library modules' turned on.
You need to be more consistent about your export
declarations. This includes forward declarations. CC
and CB
are both supposed to be export
ed. This means that the forward declarations of them should also be exported
.
One way to avoid excess repetition is to have a module interface partition for such declarations that all other partitions import:
//A-fwd.ixx
export module A:fwd;
export class CB;
export class CC;
//A-B.ixx
export module A:B;
export import :fwd;
export class CB {...};
The export
in A-B
is not necessary, but it's good as a reminder. A-C.ixx
should also import :fwd
.
Note that my reading of the standard is that it should have given a compile error when compiling the module A
. That file imports A:B
, which declares CC
as non-exported. When it imports A:C
that declares it exported, that should have triggered [module.interface]/6:
A redeclaration of an entity X is implicitly exported if X was introduced by an exported declaration; otherwise it shall not be exported.
That "shall not be exported" translates to "ill-formed if the entity is redeclared as exported". But VC++ didn't do that apparently.
It does give an error eventually, but only when you try to use it. See, it seems to be translating your code such that there are two types named CB
: the exported one and the non-exported one. The object cb
is of the exported type, but the type used by CC::MyFunc3
is the non-exported one. Since they are two different types, and no conversion operator is defined, you get an error.