13

I've been banging my head on this problem for days, I read a lot of documentation and posts about new C++20 modules among which this official one, this one and this other one on Stackoverflow, but I really cannot solve this problem.

I'm using MSVC compiler delivered with Visual Studio Preview 16.6.0 2.0. I know it is not a stable release yet, but I'd like to mess around with new features to start learning them.

Basically I wrote a module (myModule) and 2 partitions of this module (mySubmodule1 and mySubmodule2) and I implemented them in two module implementation files (mySubmodule1Impl.cpp and mySubmodule2Impl.cpp).

mySubmodule1 have a dependency on mySubmodule2, and vice-versa. Here is the source:

mySubmodule1.ixx

export module myModule:mySubmodule1;

export namespace myNamespace{

class MyClass2;

class MyClass1{
    public:
    int foo(MyClass2& c);
    int x = 9;
};
}

mySubmodule2.ixx

export module myModule:mySubmodule2;
import :mySubmodule1;

export namespace myNamespace{

class MyClass2 {
    public:
    MyClass2(MyClass1 x);
    int x = 14;
    MyClass1 c;
};
}

mySubmodule1Impl.cpp

module myModule:mySubmodule1;
import :mySubmodule2;

int myNamespace::MyClass1::foo(myNamespace::MyClass2& c) {
    this->x = c.x-14;
    return x;
}

mySubmodule2Impl.cpp

module myModule:mySubmodule2;
import :mySubmodule1;

myNamespace::MyClass2::MyClass2(myNamespace::MyClass1 c) {
    this->x = c.x + 419;
}

myModule.ixx

export module myModule;

export import :mySubmodule1;
export import :mySubmodule2;

As you can see I can forward declare MyClass2 in mySubmodule1, but I cannot forward declare MyClass1 in mySubmodule2, because in MyClass2 I use a concrete object of type MyClass1.

I compile with this line: cl /EHsc /experimental:module /std:c++latest mySubmodule1.ixx mySubmodule2.ixx myModule.ixx mySubmodule1Impl.cpp mySubmodule2Impl.cpp Source.cpp where Source.cpp is just the main.

I get the infamous error C2027: use of undefined type 'myNamespace::MyClass2' in mySubmodule1Impl.cpp and mySubmodule2Impl.cpp at the lines where I use MyClass2. Moreover the compiler tells me to look at the declaration of MyClass2 in mySubmodule1.ixx where there is the forward declaration.

Now, I really do not understand where is my mistake. I checked over and over but the logic of the program seems perfect to me. The order of compilation of the files should define MyClass2 before it is even used in the implementation!

I tried to compile this exact program using the "old" .h and .cpp files instead of modules, and it compiles and run fine. So I guess I'm missing something regarding these new modules.

I checked on the first official proposal of modules (paragraph 10.7.5), and in the first one there was a construct named proclaimed ownership declaration which seemed to be perfect in such cases. Basically it allows you to import an entity owned by another module in the current module, but without importing the module itself. But in later revisions of the proposal there is no sign of it. Abslolutely nothing. And in the "changelog" section of the new proposal it isn't even cited.

Please don't tell me cyclic dependencies are bad. I know often they are bad, but not always. And even if you think they are always bad I'm not asking for a rule of the thumb. I'm asking why my code compiles with "old" .h + .cpp but not with new modules. Why the linker doesn't see the definition of MyClass2.


EDIT 1

Here is the new design suggested in the answer, but it still doesn't work. I get the exact same errors:

mySubmodule1Impl.cpp

module myModule;

int myNamespace::MyClass1::foo(myNamespace::MyClass2& c) {
    this->x = c.x-14;
    return x;
}

mySubmodule2Impl.cpp

module myModule;

myNamespace::MyClass2::MyClass2(myNamespace::MyClass1 c) {
    this->x = c.x + 419;
}

All of the other files are unchanged.

Community
  • 1
  • 1
Lapo
  • 882
  • 1
  • 12
  • 28
  • Some of this code use `mySubmodule` and some `mySubmodule1`. What is it? – T.C. Mar 28 '20 at 01:56
  • 2
    ["A named module shall not contain multiple module partitions with the same _module-partition_."](http://eel.is/c++draft/module.unit#3.sentence-2) I don't think you are doing your partitions right. – T.C. Mar 28 '20 at 01:59
  • @T.C. Sorry, you're right, it is the same. In my code I used mySubmodule but I thought it would be better to name it mySubmodule1 in the example to avoid misunderstandings. I probably forgot to change some occurrences. Now I correct them and all should be ok. – Lapo Mar 28 '20 at 10:20
  • @T.C. Mhhh I don't have two module partitions with the same name. By the way if I remove the cyclic dependency it works, so I don't think the problem is with the names, but with something about the cyclic dependency itself. Thanks! – Lapo Mar 28 '20 at 10:24
  • 1
    Note that **this whole problem is unrelated to using Partitions**. I don't use partitions, and I have the exact same issue with two classes in two modules that reference each other, and therefore _forward declare_ each other. Compiling either one of the module complains that the 'class is incomplete' and refers to the forward declaration - instead of using the full class definition. Sometimes it even complains that the class 'namespace::A' cannot be converted to 'namespace::A' - obviously having internally two instances of it. – Aganju Aug 11 '20 at 18:54
  • I've posted a possible solution under two questions: https://stackoverflow.com/a/70268211/16509 https://stackoverflow.com/a/70290198/16509 – jjrv Dec 09 '21 at 12:53

4 Answers4

10

The immediate problem is that you can’t have an “interface file” and an “implementation file” for a single module partition (as if it were a header file and source file pair). There are interface partitions and implementation partitions, but each must have its own name because each exists to be imported. Of course, it is also one of the purposes of modules to allow a single file where header/source pairs were needed: you can often include the implementation in the same file as the interface but use export and/or inline only with the latter. This does come with the usual header-only downside of causing more frequent downstream rebuilds.

The metaproblem is that there is no circularity here: you’ve already addressed it with the forward declaration of MyClass2. That’s the right thing to do: modules don’t change the basic semantics of C++, so such techniques remain applicable and necessary. You can still divide the classes into two files for the usual organizational reasons, but there’s no need for the method definitions to be in partitions at all (nor in separate module myModule; implementation units, which automatically import all of the interface). The import :mySubmodule1 that remains (in the interface partition mySubmodule2) is then unambiguous and correct.

As for proclaimed-ownership-declarations, they appeared in the Modules TS that didn’t have module partitions, such that cases like this could not be handled otherwise (since you can use a normal forward declaration for an entity from another partition but not another module).

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • Ok, first of all, thanks a lot! I admit I'm now a bit confused, and I have many questions, but in particular 2: **1)** Why, if I remove all of the references to `MyClass1` in `mySubmodule2.ixx` all works fine (even tho I have "interface file" and "implementation file")? **2)** How do I separe the interface of a partition from its implementation? I think this can be very useful design wise, but now I really cannot come up with a solution... I thought partitions were useful to divide logical parts of a module into smaller files, but if I can't separate their implementation they're useless... No? – Lapo Mar 28 '20 at 18:14
  • Ok, after some thinking I think I understood. Basically I have to remove the lines `module; import :mySubmodule;` and replace it with a simple `import myModule` in the `mySubmoduleImpl.cpp` file. And I change in the same way `mySubmodule2Impl.cpp` file. I did it and it works, is this the right way? Basically I have my partitions interfaces files and I implement them in plain .cpp files. Thanks a whole lot, it took me ages to solve this. If you have some references or can elaborate a little bit more on modules, I'll listen to very gladly. My only question is the **1)** in my previous comment. – Lapo Mar 28 '20 at 19:51
  • 3
    @Lapo: Violations of the rules about translation units do not require a diagnostic: the compiler may not examine the other files or not detect collisions because of separate processing. You should put the implementation in normal **implementation units** (with just `module A;`); you’re not (supposed to be) able to define something *from* a module in a translation unit that merely *imports* it. (You already found the original, approved wording, but I did also write [a little bit more on modules](https://stackoverflow.com/a/59097429/8586227).) – Davis Herring Mar 28 '20 at 20:31
  • Ok, thanks a lot, now it is clear. I'll try as soon as possible the correct solution. But can I have more than one implementation unit for the same module? So that I can separate (in different files) the implementation of each different partition interface of the module? – Lapo Mar 28 '20 at 20:46
  • 1
    @Lapo: You can have as many partitions (each with a *different* `…` in its `[export] module foo:…;`) and as many “plain implementation units” (all with the *same* `module foo;`) as you want for one `foo`. (Obviously each of either kind also has a *file* name.) – Davis Herring Mar 29 '20 at 01:18
  • Perfect, thanks again for your clear explaination! Now it works perfectly! The ndr thing can make you lose a ton of time sometimes, I had no clue what the error was, even tho it was very easy to spot if I knew the modules rule better. – Lapo Mar 29 '20 at 12:01
  • 1
    @Lapo: Great; you’re welcome. There is some hope that refinements to implementations and conventions will reduce the possibility of silent misbehavior here, but separate translation is fundamentally hard to make user-friendly. – Davis Herring Mar 29 '20 at 14:02
  • No, man, I'm sorry, but it still doesn't work and I get the exact same error. I noticed that I forgot to uncomment the line where I `import :mySubmodule1` in `mySubmodule2`, (I also commented out the `MyClass1 c;` line). I did this to test if it worked without cyclic dependencies, and it did (both with my old wrong design and with the one you suggested). But as soon as I put those lines in again, I get the exact same errors. I don't know, your suggestions made perfectly sense, but it still doesn't work. If you have another idea please tell me. I edited the question with the new design. – Lapo Mar 29 '20 at 15:10
  • Mhhh no, it's not that. It is just a typo because in my real code I have `mySubmodule` and `MyClass` without 1. I corrected it now, but that's not the issue... – Lapo Mar 29 '20 at 18:50
  • 1
    @Lapo: As far as I can tell, the code is correct as amended. Make sure you aren’t using any old compiled module interface files—I don’t know how those work with MSVC. – Davis Herring Mar 29 '20 at 23:45
  • It doesn't seem I'm using any old compiled files, even because I delete them before every new compilation, just not to be wrong. I'm really out of ideas, I'm for sure missing something, but I don't know what. Of course this can even be a bug, since modules aren't yet stable in MSVC. I'll try with a different compiler as soon as I can. As a last resource I'll open a issue feedback in the VS dev community. If something comes to your mind about this problem, please let me know. Thanks again for your help so far! – Lapo Mar 30 '20 at 07:33
1

Try exporting the forward declarations. e.g.

// A.cc

export module Cyclic:A;

export class B;
export class A {
public:
    char name() { return 'A'; }
    void f(B& b);
};
// B.cc

export module Cyclic:B;

export class A;
export class B {
public:
    char name() { return 'B'; }
    void f(A& a);
};
// A_impl.cc

module Cyclic;

import Cyclic:A;
import Cyclic:B;

import <iostream>;

void A::f(B& b) {
  std::cout << name() << " calling " << b.name() << std::endl;
}
// B_impl.cc

module Cyclic;

import Cyclic:B;
import Cyclic:A;

import <iostream>;

void B::f(A& a) {
  std::cout << name() << " calling " << a.name() << std::endl;
}
// Cyclic.cc

export module Cyclic;
export import :A;
export import :B;
claz78
  • 46
  • 1
  • 4
  • Nothing imports the `_impl` partitions, which is invalid since they’re interface partitions but more importantly means they need not be partitions at all. – Davis Herring May 17 '22 at 15:07
  • the `_impl` partitions can be imported into Cyclic.cc, but since they don't export anything `export module` can be reduced to just `module` to be minimalistic – claz78 May 18 '22 at 07:13
  • Importing them still wouldn’t do anything. – Davis Herring May 18 '22 at 14:49
0

See my answer to this post. You probably need to export your forward declaration to have external rather than module linkage for MyClass2.

Touloudou
  • 2,079
  • 1
  • 17
  • 28
0

Sure. If I could have had the ability to directly reply to posts, the answer makes more sense in context. The link above is meant as post-reponse to Touloudou's last answer with additional references. That solution: export forward declarations from partitions (optionally separating exports into their own partition).

Additionally, at the time that site post was written, cross references across modules were prohibited and gcc support was lagging, which could explain the problems and possible misdirection encountered from other earlier answers, at the time.

pooba
  • 1