2

Suppose classes A and B. Each can construct the other:

// A.hpp
#pragma once
class B;
class A {
  B foo();
};
// A.cpp
#include <A.hpp>
#include <B.hpp>
B foo(){ return B(); }
// B.hpp
#pragma once
class A;
class B {
  A foo();
};
// B.cpp
#include <B.hpp>
#include <A.hpp>
A foo(){ return A(); }

Let's introduce a new type:

// A_or_B.hpp
#pragma once

#include "A.hpp"
#include "B.hpp"
// or
class A;
class B;

using A_or_B = std::variant<A, B>;

Let's return A_or_B from A and B:

// A.hpp
#pragma once

#include "A_or_B.hpp" // woops!  the type for A_or_B is not resolved:
class A {
  A_or_B foo();
};

Justifications for the whoops:

  1. if we use an include in A_or_B.hpp then A_or_B will not be able to include A (since the header guard) - so it will be referencing a non-existant type.
  2. if we use a forward declaration then A_or_B will use uncomplete types and will not be usable in the linking stage

The cyclic problem doesn't occur between A and B because we split the declaration and implementation between hpp and cpp files. contrary to classes - typedefs or using directives can't be split.

what can be done to solve this?

2 Answers2

2

You can forward declare A_or_B and then eventually define it as a class that publicly inherits from the variant type.

// A.hpp
#pragma once
class A_or_B;
class A {
    A_or_B foo();
};

// B.hpp
#pragma once
class A_or_B;
class B {
    A_or_B foo();
};

// A_or_B.hpp
#include "A.hpp"
#include "B.hpp"

class A_or_B : public std::variant<A, B> {
public:
    using base = std::variant<A, B>;
    using base::base;
    using base::operator=;
};
jwezorek
  • 8,592
  • 1
  • 29
  • 46
0

You can define A_or_B as a typedef for std::variant<A, B> without defining A and B. Only to instantiate an object of type A_or_B do you need the definitions.

// A.hpp
#include "A_or_B_fwd.hpp"

class A {
    A_or_B foo();
};

// B.hpp
#include "A_or_B_fwd.hpp"

class B {
    A_or_B foo();
};

// A_or_B_fwd.hpp
#include <variant>

class A;
class B;

using A_or_B = std::variant<A, B>;

// A_or_B.hpp
#include "A.hpp"
#include "B.hpp"
#include "A_or_B_fwd.hpp"

// A.cpp
#include "A.hpp"
#include "A_or_B.hpp"

A_or_B A::foo() { /* ... */ }

// B.cpp analogously

Using "-fwd.hpp"-style headers can be reasonable when forward declarations are more complex than class X; The standard library also does it with <iosfwd>.

chrysante
  • 2,328
  • 4
  • 24