AlwaysLearning
AlwaysLearning

Reputation: 8011

Breaking template circular dependencies by using template template parameters

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

Answers (1)

Marco A.
Marco A.

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

Related Questions