C++20 Modules “cyclic dependency”
Asked Answered
G

3

4

Im trying to include a module within another module, but i can't compile due to the following error:

"Cannot build the following source files because there is a cyclic dependency between them: Module1.ixx depends on Module2.ixx depends on Module1.ixx."

I want to modClass1_ contain modClass2_ class and modClass2_ contain a pointer to static modClass1_.


Code i tried with success with C++17 Header and Source files (.h and .cpp)

// Class1.h
#pragma once
#include "Class2.h"
class modClass2_;
class modClass1_
{
public:
    modClass1_() {};
    ~modClass1_() {};
    int V = 2;
    int getV() { return V; };
    static modClass2_ mc2;
};
extern modClass1_ mc1;
// Class1.cpp
#include "Class1.h"
modClass1_ mc1;
modClass2_ modClass1_::mc2;
// Class2.h
#pragma once
#include "Class1.h"
class modClass2_
{
public:
    modClass2_() {};
    ~modClass2_() {};
    int V = 1;
    int getV() { return V; };
    int getClass1V();
};
// Class2.cpp
#include "Class2.h"
int modClass2_::getClass1V()
{
    return mc1.V;
}
// Main.cpp
#include "Class1.h"
#include <iostream>
int main()
{
    std::cout << mc1.getV() << "\n"; // gets modClass1_ V directly
    std::cout << mc1.mc2.getClass1V() << "\n"; // gets modClass1_ V through modClass2_ through modClass1_
    std::cout << mc1.mc2.getV() << "\n"; // gets modClass2_ V through modClass1_
}

Code i tried but failed with C++20 Modules (.ixx)

// Module1.ixx
export module Module1;
import Module2;
export class modClass1_
{
public:
    modClass1_() {};
    ~modClass1_() {};
    int getV() { return V; };
    modClass2_ mc2;
    int getModule2V() { return mc2.V; };
    int V = 1;
};
export modClass1_ mc1;
// Module2.ixx
export module Module2;
import Module1;
export class modClass2_
{
public:
    modClass2_() {};
    ~modClass2_() {};
    int getV() { return V; };
    int getModule1V() { return mc1.V; };
    int V = 2;
};

Any help/suggestion will be appreciated.

Environment: Visual Studio 2019 | MSVC-2019 | C++20 | Windows 10 Pro

Galicia answered 7/7, 2021 at 16:13 Comment(6)
Why do Class2.h need to include Class1.h? To declare a pointer variable all you need is a forward declaration.Prodrome
Cyclic dependencies are bad practice. In good code shouldn't exists except for specific cases (for example Tree class and Node which are defined in single translation unit, one is implementation detail of other).Dibbrun
Modules can still have separate interface and implementation translation units.Viviparous
@Some programmer dude, i tried forward declaration, but with modules doesn't compile.Galicia
@Nathan Pierson can you please give me a example how to implement units in these code?Galicia
class1.h has #include "class2.h" and class2.h has #include "class1.h". That's a circularity, and it simply won't work. This has nothing to do with modules.Bianchi
V
4

Just like with header files, you can separate out module interface files from module implementation files. Example:

Module1.ixx:

export module Module1;

import Module2;

export class modClass1_
{
public:
  modClass1_() {};
  ~modClass1_() {};
  int getV() { return V; };
  modClass2_ mc2;
  int getModule2V() { return mc2.V; };
  int V = 1;
};
export modClass1_ mc1;

Module2.ixx:

export module Module2;

export class modClass2_
{
public:
  modClass2_() {};
  ~modClass2_() {};
  int getV() { return V; };
  int getModule1V();
  int V = 2;
};

Module2.cpp:

import Module1;
import Module2;

int modClass2_::getModule1V()
{
  return mc1.V;
}

main.cpp:

#include <iostream>

import Module1;
import Module2;

int main()
{
  // NB: mc1 is a symbol imported from Module1
  std::cout << "mc1.V: " << mc1.V << '\n';
  std::cout << "mc1.getModule2V: " << mc1.getModule2V() << '\n';

  modClass2_ mc2;
  std::cout << "mc2.V: " << mc2.V << '\n';
  std::cout << "mc2.getModule1V: " << mc2.getModule1V() << '\n';
}

Note that modClass2_'s interface doesn't require anything from Module1 and therefore Module2.ixx doesn't have import Module1;. Module2.cpp, where the implementation lives, does.

In my example I've moved as little as possible from Module2.ixx into a Module2.cpp implementation file but in practice you might well want to move more things out of the interface.

Viviparous answered 7/7, 2021 at 16:57 Comment(4)
Can't get away from having two "Module2" files? I wanted to keep one module per file.Galicia
@PedroDuarte I think, you can move Module2.cpp code into module :private; section in Module2.ixxHeins
In Visual Studio 2019 16.11.4 your proposal refuses to compile due to a circular dependency between Module1.ixx and Module2.ixx. I'm not sure exactly what the point of a private module fragment is, beyond what you could get by declining to export from the module, but they still don't allow you to have circular dependencies like this.Viviparous
Module2.cpp needs to have module Module2;, not import Module2;.Peck
B
0

I had a tree data structure split into two modules that needed to reference each other, and posted an answer how to make it work. Copypasting it, here is a desperate solution to break a circular dependency by using a template:

// A_impl.cc

export module A_impl;

export template <typename B> class A_impl {
    public:
        void f(B& b) {}
};
// B.cc

export module B;

import A_impl;

export class B;

typedef A_impl<B> A;

export class B {
    public:
        void f(A& a) {}
};
// A.cc

export module A;

export import A_impl;
import B;

export typedef A_impl<B> A;
// main.cc

import A;
import B;

int main(void) {
    A a;
    B b;

    a.f(b);
    b.f(a);

    return 0;
}

At the moment clang doesn't support module partitions so with that toolchain this seems to be the only way to define A and B in different files (without #include) while placing them in modules. With Visual Studio module partitions may or may not allow a cleaner structure.

Barrelhouse answered 9/12, 2021 at 12:49 Comment(0)
G
0

Tested with gcc, module partitions can solve the problem using forward declarations and internal module linkage. Note: This doesn't setup modules to depend on each other, the entire cyclic dependency is defined in a single module.

// 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

import Cyclic:A;
import Cyclic:B;

import <iostream>;

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

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;
Grogan answered 16/5, 2022 at 4:27 Comment(1)
Please don’t post the same answer, with the same mistakes, on multiple questions.Peck

© 2022 - 2024 — McMap. All rights reserved.