0

I am having troubles with the circular dependency between the classes A,B,C. The user type cfunction from class A points to the static method of C::F1. Here is the code:

File A.h

#ifndef A_H
#define A_H

#include "C.h"
class C;

template <typename T> using cfunction = T(*)(T, T);

template <typename T>
class A{
    public:
        cfunction <T> X;
        A () : X(&C::F1) {}
        A (const cfunction <T> &pX ) : X(pX){};
        virtual ~A() = 0;   
};
#endif

File B.h

#ifndef B_H
#define B_H
#include "A.h"

template <typename T>
class B : public A <T> {
  public:
   B() : A<T>(), b(0.0) {}
   B(const T b_, const cfunction <T> &pX ) : A <T>(pX), b(b_){}
   virtual ~B() {};
};
#endif

Finally, in the method init() of C a shared pointer to A is stored. The method F1 calls F2 with the template parameter F3. Here is the code:

File C.h

#ifndef C_H
#define C_H

#include "A.h"
template <typename T>
class A;

#include <memory>
#include <list>

template <typename T>
using List = std::list<std::shared_ptr<A<T> > >;

//Definition of all projections
class C  {
    public:
      template <typename T, typename Function> static T F2(Function f, const T a, const T b);
      template <typename T> static void init(List<T> l);
      template <typename T> static T F1(const T a, const T b);
      template <typename T> static T F3(const T a, const T b);
};
#include "C.hpp"
#endif

File C.hpp

#ifndef C_HPP
#define C_HPP

#include "B.h"
template <typename T>
class B;

template <typename T, typename Function> 
T C::F2(Function f, const T a, const T b) { return  f(a, b);}

template <typename T> void C::init(List<T> l) {
    auto test = std::make_shared <B < T >> (0.0, F1<T>);
    l.push_back(test);
}

template <typename T> T C::F1(const T a, const T b) {  return F2(F3<T>, a, b);}
template <typename T> T C::F3(const T a, const T b) {return a + b;}

#endif

The main file: main.cpp

#include "C.h"

int main(){
    List <double> l;
    C::init(l);
    return 0;
}

Sorry for the slightly complicated code. A simpler version of the code works well, but this "full" variant strikes. I am not able to fix the problem for g++; compile options: -std=c++11.

Thanks for your help...

justik
  • 4,145
  • 6
  • 32
  • 53
  • 1
    Don't both `#include` and forward-declare (forward-declaring "after the fact" is pointless). Since you don't need the definition of `A` in "C.h", move its `#include` to "C.hpp". Since you need the definition of `B` in "C.hpp", remove its declaration. – molbdnilo Jun 17 '16 at 13:37

2 Answers2

1

OK, so your problem can be solved with a couple of minor adjustments. As you point out, currently you have some circular dependencies, but they can be broken with only 1 fundamental and somewhat slight modification: namely, remove the A default constructor which references C. You don't really need it - as it stands your code doesn't use it. Even if you did, you can just set X member to nullptr and initialize it externally later.

With this removed you can now have a straightforward inclusion order: A.h, B.h, C.h, C.hpp.

There are a couple of other compiler errors I had to fix after that: you seem to be initiating a non-existing B member b for one. Also, even though your A destructor is pure virtual, it needs a definition. Final code below:

EDIT(2): I have now modified this so the default constructor for A is no longer excluded. It is instead simply defined later, in C.h, after the definition of C is available.

A.h:

#ifndef A_H
#define A_H

//#include "C.h"
//class C;

template <typename T> using cfunction = T(*)(T, T); 

template <typename T>
class A{
    public:
        cfunction <T> X;
        //A () : X(&C::F1) {}
        A ();
        A (const cfunction <T> &pX ) : X(pX){};
        virtual ~A() = 0;
};

template <typename T>
A<T>::~A() {}

#endif

B.h:

#ifndef B_H
#define B_H
#include "A.h"

template <typename T>
class B : public A <T> {
  public:
   B() : A<T>() //, b(0.0) 
   {}  
   B(const T b_, const cfunction <T> &pX ) : A <T>(pX) //, b(b_)
   {}  
   virtual ~B() {}; 
};
#endif

C.h:

#ifndef C_H
#define C_H

#include "A.h"
#include "B.h"

//template <typename T>
//class A;

#include <memory>
#include <list>

template <typename T>
using List = std::list<std::shared_ptr<A<T> > >;

//Definition of all projections
class C  {
    public:
      template <typename T, typename Function> static T F2(Function f, const T a, const T b); 
      template <typename T> static void init(List<T> l); 
      template <typename T> static T F1(const T a, const T b); 
      template <typename T> static T F3(const T a, const T b); 
};

template<typename T>
A<T>::A() : X(&C::F1)
{}

#include "C.hpp"
#endif

C.hpp:

#ifndef C_HPP
#define C_HPP

//#include "B.h"
//template <typename T>
//class B;

template <typename T, typename Function> 
T C::F2(Function f, const T a, const T b) { return  f(a, b);}

template <typename T> void C::init(List<T> l) {
    auto test = std::make_shared <B < T >> (0.0, F1<T>);
    l.push_back(test);
}

template <typename T> T C::F1(const T a, const T b) {  return F2(F3<T>, a, b);}
template <typename T> T C::F3(const T a, const T b) {return a + b;} 

#endif
Smeeheey
  • 9,906
  • 23
  • 39
  • @Smeetheey: thanks; however what to do if X(&C::F1) {} is required? This is a simplified example and the above-mentioned initialization is important... – justik Jun 17 '16 at 14:19
  • Explain why it is required? Do you just mean you need a default constructor for `A`? If so I recommend just setting `X` to `nullptr` in said constructor, then adding a function like `void setX(cfunction val) { X = val; }`, then calling it from outside. Otherwise, can you clarify further as to why you need it? – Smeeheey Jun 17 '16 at 14:23
  • It is a relatively complex model, where B is not a single class. There are more than 20 classes derived from A (B is an example). It is uncomfortable to do the initialization "twice". In terms of my question I am also interested how this problem could be solved entirely (not only a simplified version). I spent most of the morning solving the problem :-) – justik Jun 17 '16 at 14:33
  • Actually I have now edited so you can have the original default constructor back, see the edit. The trick is to use a static member variable which is declared in `A` but defined only later, after `C` becomes available – Smeeheey Jun 17 '16 at 14:33
  • @ Smeeheey: Thank you very much for the solution, it is very kind of you. I do not want to waste your time and effort... However, is there any solution without any static variable? – justik Jun 17 '16 at 14:47
  • Sigh... What am I going to do with you? :). OK, OK, yes there is. I'll post it in a sec – Smeeheey Jun 17 '16 at 14:48
  • OK, now I have edited so you have your original constructor back, without any static variables. I should have thought of this earlier, but all you need to do is simply defer the definition of the default constructor of `A` until after `C` is available, which is what the above now does – Smeeheey Jun 17 '16 at 14:52
  • @ Smeeheey: what an interesting idea ! Thanks, it real helped me. Kind regards to London. – justik Jun 17 '16 at 15:04
  • Can I have an accept please? After all it does answer your question as stated – Smeeheey Jun 17 '16 at 15:40
  • Yes, of course, I forgot to push the button:-)... Thanks again. – justik Jun 17 '16 at 16:50
0

B.h needs to include A.h (because it needs to know its specifics)

A.h needs to include C.h (because it needs to know its specifics). By doing that, it does not need to forward-declare C on account of it already knows about it by the including of C.h

But, from your code, C.h does not need to know about the specifics of A and so need not include A.h. Instead, it can simply forward-declare A as it's already doing

On a similar note, C.hpp need not include B as it also does not need to know any of B's specifics. A simple forward declaration (like, again, what you're already doing) should suffice

Anytime you see yourself including a file and forward-declaring a class you got from that file at the same time indicates you have some unwinding to do

Altainia
  • 1,347
  • 1
  • 7
  • 11
  • @ Altainia: Unfortunately, it does not work for me: A.h(#include "C.h"), B.h(#include "A.h), C.h(template class A;), and C.hpp(template class B;). It is not as simple as it looks. – justik Jun 17 '16 at 14:39