0

I am trying to work with class inhertiance, and factory methods.

The basic structure is that I have a base class Base, and a derived class Child.

The method ChildFactory allows one to choose different implementations of Child, that still provide the same interface.

All of my classes are templates, since there will be some parameters that can be floats or ints or whatever.

However, I'm having some trouble getting all of the templates to play nicely together. The errors I'm getting are around unknown template names

I have a MWE that should help you get an idea of what I'm talking about, if my explanation is a little off (constructive critism on that appreciated!)

├── child.h
├── child_impl.h
└── main.cpp
main.cpp
#include "child.h"
#include <iostream>

int main()
{
    mwe::Child<float> *child_0;
    mwe::Child<float> *child_1;
    mwe::ChildFactory<float> = child_factory;

    int input_param_1 = 5;
    child_0 = child_factory.get_child(1, input_param_1);
    child_1 = child_factory.get_child(2, input_param_1);
    std::cout << child_0->method1() << "\n";
    std::cout << child_1->method1() << "\n";
    return 0;
}

child.h

#ifndef CHILD_H
#define CHILD_H

#include "child_impl.h"
#include <stdexcept>
#include <iostream>

namespace mwe
{
template<typename T>
    class Base
    {
    public:
        Base(){};
        virtual int method1() = 0;
        virtual T method2() const = 0;

    };

template<typename T>
    class Child : public Base<T>
    {
        public:
            Child(int input_param_1)
                : Base<T>(),
                input_param_1(input_param_1){};

            virtual int method1() = 0;
            virtual T method2() const = 0;

            protected:
           int input_param_1;
     };

template<typename T>
    class ChildFactory
    {
    private:
      Child<T> * _child;
    public:
      Child<T> * get_child(int choice,
                       int input_param_1)
      {
      switch(choice)
        {
        case 1:
          _child = new Child_Imp_0<T>(input_param_1);
        case 2:
          _child = new Child_Imp_1<T>(input_param_1);

        default:
          throw std::invalid_argument("recieved in valid layer type");
        }
      return _child;
    }
};

};
#endif //CHILD_H

child_impl.h

#ifndef CHILD_IMPL_H
#define CHILD_IMPL_H

#include "child.h"

namespace mwe
{
    template<typename T>
    class Child_Imp_0 : public Child<T>
    {
    public:
    Child_Imp_0(int input_param_1)
: Child<T>(input_param_1),
    input_param_2(10 * input_param_1)
      {};

  int method1()
  {
    return 0;
  };

  T method2() const
  {
    return 0;
  };

protected:
  int input_param_2;
};

  template<typename T>
class Child_Imp_1 : public Child<T>
{
public:
Child_Imp_1(int input_param_1)
  : Child<T>(input_param_1),
    input_param_2(100 * input_param_1)
      {};

  int method1()
  {
    return 1;
  };

  T method2() const
  {
    return 0;
  };

protected:
  int input_param_2;
};
}
#endif //CHILD_IMPL_H

Any thought on how this might be made to work?

I have been searching around for related questions, but to no avail thus far. This question and this are related, as well as some other ones. However they seem to be dealing with other complexities that I'm having difficulty applying to my issue.

Alternatively, are there any alternative designs that would offer the same functionality?

Prunus Persica
  • 1,173
  • 9
  • 27

1 Answers1

2

This is one of these rare cases where the errors generated by the compiler are spectacularly unhelpful.

That's because the problem with your code happens during preprocessing, before the compiler even has a chance to start interpreting the code.

Specifically, let's look at what child.h looks like when we replace #include child_impl.h with the contents of the file (since that's all #include does after all):

#ifndef CHILD_H
#define CHILD_H

#ifndef CHILD_IMPL_H
#define CHILD_IMPL_H

#include "child.h"

namespace mwe
{
   template<typename T>
   class Child_Imp_0 : public Child<T> 
   { ... };
}
#endif 

#include <stdexcept>
#include <iostream>

namespace mwe
{
  ...
}
#endif

And now, if we expand the #include child.h:

#ifndef CHILD_H
#define CHILD_H

#ifndef CHILD_IMPL_H
#define CHILD_IMPL_H

#ifndef CHILD_H
// COMPLETELY SKIPPED, since CHILD_H has been defined on the second line!
#endif

namespace mwe
{
   template<typename T>
   class Child_Imp_0 : public Child<T>  // BAM, Child has not been defined yet.
   { ... };
}
#endif 

#include <stdexcept>
#include <iostream>

namespace mwe
{
  ...
}
#endif

In order to solve this, you need to break up that circular dependency somehow. As @MooingDuck said in the comments, moving the Factory to its own dedicated header would do the trick easily.

  • thanks, this conceptually makes sense, and knowing the name of the issue as "circular dependency" is helpful. Hopefully won't make that mistake again! – Prunus Persica Nov 27 '18 at 12:34