Search code examples
c++visual-c++c++20c++-modules

Should I break circular dependencies between C++ module fragments using forward declarations?


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.


Solution

  • You need to be more consistent about your export declarations. This includes forward declarations. CC and CB are both supposed to be exported. 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.