Reputation: 8011
If my understanding is correct, the following is a classical circular dependency between template classes:
template <class MyB>
struct A {
MyB *b_;
};
template <class MyA>
struct B {
MyA *a_;
};
If we want to instantiate A
with B
and B
with A
, then we can't begin with either one, since we would have to write: A<B<A<B<...>>>
(infinite).
I think that template template parameters provide a solution. The following code compiles (with gcc
version 4.8.2):
template <class MyB>
struct A {
MyB *b_;
};
template <template <class> class MyA>
struct B {
MyA<B> *a_;
};
int main() {
using MyB = B<A>;
using MyA = A<MyB>;
MyA a;
MyB b;
a.b_ = &b; b.a_ = &a;
return 0;
}
Am I missing the essence of the problem?
UPDATE: I just ran into this post which proposes essentially the same solution.
Upvotes: 3
Views: 140
Reputation: 43662
I would try to design my code in order to avoid those circular dependencies. Anyway if prompted with a compelling reason not to, there are several ways of solve this issue:
As you noted, using a template template parameter solves the problem by breaking the cycle
template <class MyB>
struct A {
MyB *b_;
};
template <template <class> class MyA>
struct B {
MyA<B> *a_;
};
int main() {
using MyB = B<A>;
using MyA = A<MyB>;
MyA a;
MyB b;
a.b_ = &b; b.a_ = &a;
return 0;
}
Another solution could be to encapsulate the types you need into an external struct/class
template<class Common> struct A
{
typedef typename Common::BT B;
B* b;
};
template<class Common> struct B
{
typedef typename Common::AT A;
A* a;
};
struct Common {
using AT = A<Common>;
using BT = B<Common>;
};
int main() {
A<Common> a;
B<Common> b;
return 0;
}
Depending on what the rest of your code does, in a simple case like this you might get away with variadic templates
template <class MyA>
struct B;
template <typename ...MyB>
struct A {
B<A<>> *b_;
};
template <>
struct A<> {};
template <class MyA>
struct B {
A<B<MyA>> *a_;
};
int main() {
using BoA = B<A<>>;
using AoBoA = A<B<A<>>>;
BoA obj1;
AoBoA obj2;
obj1.a_ = &obj2;
obj2.b_ = &obj1;
return 0;
}
Finally it's worth to be noted that exposing a common base class and using a CRTP-like approach could probably be the cleaner way to achieve this (you might even gain points for clarity and readability).
Upvotes: 2