3

I have a parent class and some classes derived from it. I want to 'pair' two derived classes that eac has a pointer to another one.

Code example:

template<typename DerivedClassName>
class Parent {
    // some stuff
    DerivedClassName* prtToPair;
};

template<typename DerivedClassName>
class DerivedA : public Parent<DerivedClassName> {

};


template<typename DerivedClassName>
class DerivedB : public Parent<DerivedClassName> {

};

// compile fails
DerivedA<DerivedB> dA;
DerivedB<DerivedA> dB;

dA.prtToPair = &dB;
dB.prtToPair = &dA;

I know I can do this with virtual function but I try to find a way to use template.

I found a solution from http://qscribble.blogspot.com/2008/06/circular-template-references-in-c.html:

#include <stdio.h>

template<class Combo> struct A
{
  typedef typename Combo::b_t B;
  B* b;
};

template<class Combo> struct B
{
  typedef typename Combo::a_t A;
  A* a;
};

struct MyCombo {
  typedef A<MyCombo> a_t;
  typedef B<MyCombo> b_t;
};

int main(int argc, char* argv[])
{
  A<MyCombo> a;
  B<MyCombo> b;
  a.b = &b;
  b.a = &a;
  return 0;
}

but it only works for two fixed classes A and B. Consider I have many derived classes and I want to 'pair' any two of them, how can I solve this problem?

Update 1. fix a typo in first code block Update 2. I tried following code

template<typename DerivedClassName>
class Parent {
    // some stuff
public:
    DerivedClassName *prtToPair;
};

template<typename DerivedClassName>
class DerivedA : public Parent<DerivedClassName> {
public:
    void func() {
        std::cout << "A" << std::endl;
    }
};


template<typename DerivedClassName>
class DerivedB : public Parent<DerivedClassName> {
public:
    void func() {
        std::cout << "B" << std::endl;
    }
};

int main() {
    DerivedA<DerivedB<void>> A;
    DerivedB<DerivedA<void>> B;

    A.prtToPair = reinterpret_cast<DerivedB<void> *>(&B);
    B.prtToPair = reinterpret_cast<DerivedA<void> *>(&A);

    A.prtToPair->func();
    B.prtToPair->func();

    return 0;
}

It compiled and printed B A. But is this code correc? Does it have any side effect?

A lucky ant
  • 109
  • 1
  • 8
  • Sorry, nevermind. I misunderstood. – walnut Dec 15 '19 at 05:23
  • I tried and still encounter cyclic dependency problem... – A lucky ant Dec 15 '19 at 05:28
  • What is `ParentB`? – Guillaume Racicot Dec 15 '19 at 05:34
  • Sorry for typo. It shoule be `Parent`. – A lucky ant Dec 15 '19 at 05:40
  • `DerivedA` and `DerivedB` are both templated classes. The usage `DerivedA dA;` treats `DerivedB` as if it is not a templated class, and `DerivedB dB` treats `DerivedA` as if it is not a templated class. The fix to `DerivedA dA` will be something like `DerivedA > dA` and the fix to `DerivedB dB` would be something like `DerivedB > dB` where `some_other_type` is an actual type (like `int`, or some `struct` type that is not templated). This means `some_other_type` cannot be `DerivedA` or `DerivedB` – Peter Dec 15 '19 at 05:53
  • Why not have a global object store the pointer to all the combo-ed objects and store the pointer to the global object in each object? – ph3rin Dec 15 '19 at 05:53

2 Answers2

1

DerivedB isn't a class; it's a template. You can't just have a DerivedA<DerivedB>, because that doesn't make sense. What kind of DerivedB is inside DerivedA? If you think that way, you see the problem: the types of your two variables are infinite: one of them is DerivedA<DerivedB<DerivedA<DerivedB<...>>>> and the other is DerivedB<DerivedA<DerivedB<DerivedA<...>>>>. You can't have an infinite type. You must use a wrapper class somewhere to break the cycle. A generic wrapper type for this situation is

template<template<typename> typename F, template<typename> typename... Fs>
struct Fix {
    F<Fix<Fs..., F>> unwrapped;
};

Fix<F1, F2, ..., Fn> represents F1<F2<...<Fn<F1<F2<...>>>>...>>. You can get your two objects as so:

Fix<DerivedA, DerivedB> dA;
Fix<DerivedB, DerivedA> dB;
dA.unwrapped.prtToPair = &dB;
dB.unwrapped.prtToPair = &dA;
HTNW
  • 27,182
  • 1
  • 32
  • 60
  • Awesome! It works but I still have one problem. I added some test code to origianl post. It works but I don't know its validity. Your code works perfectly and only problem is that I have to use `unwrapped` twice. e.g `dA.unwrapped.prtToPair->unwrapped.func(); dB.unwrapped.prtToPair->unwrapped.func();` – A lucky ant Dec 15 '19 at 06:08
  • @gtrslower Your code, as is, is undefined, as @walnut says, due to using the pointer at the wrong type. According to [cppreference](https://en.cppreference.com/w/cpp/language/reinterpret_cast), the pointer will survive the first cast as long as "`DerivedB`'s alignment requirement is not stricter than `DerivedB>`'s". If you cast it back before you use it, it should work on almost all implementations, but I think it's still technically implementation-defined (and potentially undefined on some really weird implementations). – HTNW Dec 15 '19 at 06:19
  • Thank you. Since it's undefined I decide not to use it. – A lucky ant Dec 15 '19 at 06:34
1

Something like the following?

#include <type_traits>

template <typename Combo>
struct Parent {
  // some stuff
  typename Combo::other_type* prtToPair;
};

template <typename Combo>
class DerivedA : public Parent<Combo> {};

template <typename Combo>
class DerivedB : public Parent<Combo> {};

template <template <typename...> class T, template <typename...> class U>
struct Combo {
 private:
  template <typename Combo, bool B>
  struct impl {
    using other_type =
        typename std::conditional_t<B, typename Combo::type2, typename Combo::type1>;
  };

 public:
  using type1 = T<impl<Combo, true>>;
  using type2 = U<impl<Combo, false>>;
};

int main() {
  using C = Combo<DerivedA, DerivedB>;
  using A = typename C::type1;
  using B = typename C::type2;

  A dA;
  B dB;

  dA.prtToPair = &dB;
  dB.prtToPair = &dA;
}

It makes the two types dependent on the Combo they are associated with and the choice of the correct other_type is made part of the implementation of Combo. Note that Combo<DerivedA, DerivedB> and Combo<DerivedB, DerivedA> will now lead to different types, though.


Regarding your edit:

Accessing a value through the pointer returned by reinterpret_cast to an unrelated type or calling a non-static member function using it (as you are doing) causes undefined behavior.

walnut
  • 21,629
  • 4
  • 23
  • 59
  • Thanks. I tried your code but it couldn't compile. error: no type named 'type2' in 'Combo' in `typename Combo::other_type`, error: no type named 'other_type' in 'Combo::impl' in `typename Combo::other_type *prtToPair;` and error: no type named 'other_type' in 'Combo::impl' in `typename Combo::other_type *prtToPair;` – A lucky ant Dec 15 '19 at 06:25
  • @gtrslower I have added a workaround for Clang. I think that Clang was wrong to reject the earlier version, but it doesn't really make much of a difference anyway. You still need to compile it with C++14 or later. (It can be easily adjusted to C++11, though.) – walnut Dec 15 '19 at 06:31
  • Thanks. I tried https://ideone.com/CEJSdo and it can compile with gcc. Waiting for your update :) – A lucky ant Dec 15 '19 at 06:32
  • Sorry for slow response. This new code works perfectly. – A lucky ant Dec 15 '19 at 07:28