10

I have been experimenting with modules implementation as provided by the MSVC lately and I've run into an interesting scenario. I have two classes that have a mutual dependency in their interfaces, which means that I'll have to use forward declarations to get it to compile. The following code shows an example:

Module interface

export module FooBar;

export namespace FooBar {
    class Bar;

    class Foo {
    public:
        Bar createBar();
    };

    class Bar {
    public:
        Foo createFoo();
    };
}

Module implementation

module FooBar;

namespace FooBar {
    Bar Foo::createBar() {
        return Bar();
    }

    Foo Bar::createFoo() {
        return Foo();
    }
}

Now I would like to split these two classes up into their own modules named Foo and Bar. However, each module needs to import the other as their interfaces depend on each other. And according to the modules proposal as it currently stands, circular interface imports are not allowed. This article suggests to use the proclaimed ownership declaration, but it seems that this is not yet implemented in the MSVC implementation of modules.

Therefore, am I correct in assuming that this situation currently cannot be resolved using the current implementation as provided by the MSVC? Or is there some alternative I am missing? In this scenario the situation is pretty trivial, however I encountered this problem while modularizing a library which has many classes that have such dependencies. I realize circular dependencies are often an indication of a bad design, however in certain instances they are unavoidable or difficult to refactor away.

Qub1
  • 1,154
  • 2
  • 14
  • 31
  • 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:54

1 Answers1

3

You can create a third module which exports only forward declarations to each of your classes (could be many classes).
Then you import this module into both (or all) of your modules, where it provides the forward declarations needed to implement each module.

Unfortunately, MSVC has still (today is version 16.7) issues with modules; although this approach works, you get often completely wild error messages; for example, "cannot convert MyClass* to MyClass* - no conversion provided (this example happens when you directly add forward decalarations to the same class into several modules; the compiler considers them different animals).
Another issue is if you forget to import all the modules needed, the error message is either grossly misleading ('no such method in that class'), or the compiler aborts with an internal error.

Don't expect too much until they have completed their work.

Aganju
  • 6,295
  • 1
  • 12
  • 23
  • Yeah I have been experimenting and managed to get a small sample running using module partitions for which support has recently been added to MSVC and GCC. Modules have come quite far since I asked the question but I still regularly run into internal compiler errors when using them so I'm waiting for the implementations to become more stable. – Qub1 Sep 09 '20 at 10:53
  • I have encountered the "cannot convert MyClass* to MyClass*" case when attempting to use non-module header files in `.cpp` translation units with forward declarations of the foreign types in the `.ixx` file. My best solution, so far, is to create a thin header with the forward declarations in it and then `#include` that in the global module section, instead of putting the forward declarations in the `.ixx` file, itself, where they would only be allowed after the `export module ...` line. This feels clunky. Better would be allowing forward decls. in the global module section. – Xharlie Aug 20 '21 at 11:49
  • 1
    This approach doesn't work with the standardized modules (and perhaps not with MSVC's): you can't declare something in one module and define it in another. You can do so with different module _partitions_, though. – Davis Herring Feb 03 '22 at 08:36