Felix Bertoni
Felix Bertoni

Reputation: 460

C++ Template instancing with mutual reference

Is there a way to allow two or more templates instanciations to mutually refer to each other ?

Example :

/* invalid C++ */
/* we suppose MyTemplate1 and MyTemplate2 are declared */

typedef MyTemplate1<MyInstance2> MyInstance1;
typedef MyTemplate2<MyInstance1> MyInstance2;

I suppose there is none, still asking just in case I missed something.

Adding more precision, I want to achieve such a construction :

/* invalid C++ */
#include <iostream>

template <typename typeT> struct MyStruct1 {
  static void print(unsigned i) {

    std::cout << "MyStruct1 : " << i << std::endl;
    if (i > 0) {
      typeT::print(i - 1);
    }
  }
};

template <typename typeT> struct MyStruct2 {
  static void print(unsigned i) {
    std::cout << "MyStruct2 : " << i << std::endl;
    if (i > 0) {
      typeT::print(i - 1);
    }
  }
};

/* of course this is invalid, since you can't reference MyInstance2
before it is declared */
typedef MyStruct1<MyInstance2> MyInstance1;
typedef MyStruct2<MyInstance1> MyInstance2;

int main() {

  MyInstance1::print(5);
  return 0;
} 

output should be :

MyStruct1 : 5
MyStruct2 : 4
MyStruct1 : 3
MyStruct2 : 2
MyStruct1 : 1
MyStruct2 : 0

Please note I'm not trying to achieve a similar output, but a similar construct, where two (or more) templates instances refer to each other with as few as possible additional code : it shall be easy to do mutual reference instantiation. However, for the implementation code of the two templates, I don't care if they are complicated.

Upvotes: 1

Views: 122

Answers (3)

super
super

Reputation: 12968

Here's a solution that at least gives the correct output. If it's also a viable solution for your use case is not very clear though but maybe it can at least help you clarify your question a bit more.

#include <iostream>

template <template <typename> typename TemplateT> struct TemplateType {
    template <typename typeT>
    static void print(unsigned i) {
        TemplateT<typeT>::print(i);
    }
};

template <typename typeT> struct MyStruct1 {
  static void print(unsigned i) {

    std::cout << "MyStruct1 : " << i << std::endl;
    if (i > 0) {
      typeT::template print<TemplateType<MyStruct1>>(i - 1);
    }
  }
};

template <typename typeT> struct MyStruct2 {
  static void print(unsigned i) {
    std::cout << "MyStruct2 : " << i << std::endl;
    if (i > 0) {
      typeT::template print<TemplateType<MyStruct2>>(i - 1);
    }
  }
};

typedef MyStruct1<TemplateType<MyStruct2>> MyInstance1;

int main() {

  MyInstance1::print(5);
  return 0;
} 

Upvotes: 2

Felix Bertoni
Felix Bertoni

Reputation: 460

I finally found a satisfying construct, which involves using a tierce struct acting as a context to declare subs elements. It isn't forcibly the best solution for anyone, and I will probably have to adapt it a bit more to fit my very need, but here is the code :


#include <iostream>
#include <type_traits>

template <typename K, typename T> struct TypePair {
  typedef K key;
  typedef T type;
};

template <typename Context, typename P0, typename... PN> struct TypeMap {
  template <typename K> struct get {
    typedef typename std::conditional<
        std::is_same<typename P0::key, K>::value,
        typename P0::type::template actual<Context>,
        typename TypeMap<Context, PN...>::template get<K>::type>::type type;
  };
};

struct TypeNotFound {};

template <typename Context, typename P> struct TypeMap<Context, P> {
  template <typename K> struct get {
    typedef
        typename std::conditional<std::is_same<typename P::key, K>::value,
                                  typename P::type::template actual<Context>,
                                  TypeNotFound>::type type;
  };
};

/* defining a context to link all classes together */
template <typename... TN> struct Context {
  template <typename K> struct Access {
    typedef typename TypeMap<Context<TN...>, TN...>::template get<K>::type type;
  };
};

/* templates we want to cross ref, note that context is passed as a parameter*/
template <typename ContextT, typename Id2> struct MyStruct1Actual {
  static void print(unsigned i) {

    std::cout << "MyStruct1 : " << i << std::endl;
    if (i > 0) {
      ContextT::template Access<Id2>::type::print(i - 1);
    }
  }
};

template <typename ContextT, typename Id1> struct MyStruct2Actual {
  static void print(unsigned i) {
    std::cout << "MyStruct2 : " << i << std::endl;
    if (i > 0) {
      ContextT::template Access<Id1>::type::print(i - 1);
    }
  }
};

/* wrappers to not have to always pass context when instancing templates */

template <typename type> struct MyStruct1 {
  template <typename ContextT> using actual = MyStruct1Actual<ContextT, type>;
};

template <typename type> struct MyStruct2 {
  template <typename ContextT> using actual = MyStruct2Actual<ContextT, type>;
};

/* Enum and dummy id, could simply use Enum actually, but using classes a Id
can prove to be more elegant with complex structures, expecially as it could be
used to automatically create pairs instead of having to precise Id */

enum Ids : int { Struct1, Struct2 };

template <Ids id> struct Id {};

// instancing all stuff withing context
// clang-format off
typedef Context<
            TypePair< Id<Struct1>, MyStruct1< Id<Struct2> > >,
            TypePair< Id<Struct2>, MyStruct2< Id<Struct1> > >
        > Ctx;
// clang-format on

typedef Ctx::Access<Id<Struct1>>::type S1;

int main() {
  S1::print(5);
  return 0;
}

Shortening names an giving more meaning than Context or TypePair will be mandatory, but the idea is here.

Upvotes: 0

Yankes
Yankes

Reputation: 2125

One way is to use class forward declaration:


template<typename T> class M
{
    static int foo(int i) { return i ? T::foo(i - 1) : 0; }
};
struct A;
struct B;

struct A : M<B>{};
struct B : M<A>{};

Not same code exactly but you have recursion.

Upvotes: 0

Related Questions