skip to Main Content

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:UserspeterBrepositorieshobby projectenHexanaut-AIHexanaut-AIHexanaut-AIA-C.cpp(7,10): error C2511: 'void CC::MyFunc1(const CB &)': overloaded member function not found in 'CC'
1>C:UserspeterBrepositorieshobby projectenHexanaut-AIHexanaut-AIHexanaut-AIA-C.ixx(5,14):
1>see declaration of 'CC'
1>C:UserspeterBrepositorieshobby projectenHexanaut-AIHexanaut-AIHexanaut-AIA-C.cpp(10,5): error C2027: use of undefined type 'CB'
1>C:UserspeterBrepositorieshobby projectenHexanaut-AIHexanaut-AIHexanaut-AIA-C.ixx(3,7):
1>see declaration of 'CB'
1>C:UserspeterBrepositorieshobby projectenHexanaut-AIHexanaut-AIHexanaut-AIA-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.

2

Answers


  1. Chosen as BEST ANSWER

    Two key things were missing: exporting the forward declaration but also importing them into the module implementation units. I got it working as well with module implementation units for the partitions. Here is my full solution:

    main.cpp

    import A;
    
    int main()
    {
        CB cb;
        CC cc;
        cc.MyFunc3(cb);
        return 0;
    }
    

    A-fwddecl.ixx

    export module A:fwddecl;
    
    export class CB;
    export class CC;
    

    A.ixx

    export module A;
    
    export import :B;
    export import :C;
    

    A-B.ixx

    export module A:B;
    
    import :fwddecl;
    
    export class CB {
    public:
        void MyFunc(const CC& cc) const;
    };
    

    A-B.cpp

    module A:B;
    import :B;
    
    import std;
    import :C;
    
    void CB::MyFunc(const CC& cc) const
    {
        std::cout << "Hello World B" << std::endl;
    }
    

    A-C.ixx

    export module A:C;
    
    import :fwddecl;
    
    export class CC {
    public:
        void MyFunc1(const CB& cb) const;
    };
    

    A-C.cpp

    module A:C;
    import :C;
    
    import std;
    import :B;
    
    void CC::MyFunc1(const CB& cb) const
    {
        std::cout << "Hello World C" << std::endl;
        cb.MyFunc(*this);
    }
    

    If a partition ever needs something which is declared in the A module itself, just import A; in the module implementation unit of the partition.


  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search