Consider this program:

MyType1.ixx:

export module MyType1;

namespace MyNamespace {
    class MyType2;

    export class MyType1 {
    public:
        MyType1(MyType2* MyType2);
        void Print();
    private:
        MyType2* MyType2Instance{};
    };
}

Module1.cpp:

module MyType1;

import <iostream>;

import MyType2;

namespace MyNamespace {
    MyType1::MyType1(MyType2* MyType2) {
        MyType2->Print();
    }

    void MyType1::Print() {
        std::cout << "Hello MyType1\n";
        MyType2Instance->Print();
    };
}

MyType2.ixx:

export module MyType2;

import <iostream>;

namespace MyNamespace {
    export class MyType2 {
    public:
        void Print() {
            std::cout << "Hello MyType2\n";
        }
    };
}

Main.cpp:

import MyType1;
import MyType2;

using namespace MyNamespace;

void main() {
    MyType2 MyType2{};
    MyType1 MyType1(&MyType2);
    MyType1.Print();
    MyType2.Print();
}

Doing this gets me an error in Visual Studio: Error C2665 'MyNamespace::MyType1::MyType1': no overloaded function could convert all the argument types.

enter image description here

One solution is to also export the forward-declared class MyType2 in MyType1.ixx:

export module MyType1;

namespace MyNamespace {
    export class MyType2;

    export class MyType1 {
    public:
        MyType1(MyType2* MyType2);
    private:
        MyType2* MyType2Intance{};
    };
}

The output: enter image description here

Another solution is to forward-declare the MyType2 class before the export module MyType1 line:

namespace MyNamespace {
    class MyType2;
}

export module MyType1;

namespace MyNamespace {
    export class MyType1 {
    public:
        MyType1(MyType2* MyType2);
    private:
        MyType2* MyType2Intance{};
    };
}

The question is why does this work and how does this work, and why a more brain-friendly and intuitive solution of just forward-declaring the class inside the module doesn't work?

1

There are 1 best solutions below

4
On

What you're trying to do doesn't really make sense. And the fact that you have decided to name your modules the same as your classes makes it extremely difficult to explain what's going on (please stop doing this; modules should not be single-classes in size. There's no point in making modules that small).

In any case, the principle issue is that, if you put a declaration in a module, the module system thinks that you mean it. By declaring MyNamespace::MyType2 inside of the module MyType1, that means that the module system thinks that... MyNamespace::MyType2 is part of MyType1.

Therefore, when it gets around to compiling the MyType2 module, when it finds a class named MyNamespace::MyType2, it believes that this is a different class from the one local to the MyType1 module.

Because it is.

Therefore, when your main function creates MyNamespace::MyType2, this is of the type from the MyType2 module (since nobody outside of MyType1 has access to the MyType1's non-exported stuff). Since this is a different type from the one used by MyNamespace::MyType1, the compiler cannot convert pointers to two unrelated type.

What you have is that the module MyType1 has a dependency on the module MyType2. It should import MyType2 in its primary module interface. And since you can't use MyType1 without having a MyType2, it should probably export import it.

Or better yet... don't make these into separate modules. They can be in separate module files, but they should all be part of the same module.