4

This is my first post. I have spent hours checking for a solution on my problem, searching link after link on SO, but none descried my problem exactly(the closest i could get were this and this). So, let's get to work!

Description: I must implement a collection of specialized classes, each one able to store a linked list of its type. Also (the tricky part), I must implement a collection manager, in a way that adding more specialized classes to the collection does not affect its code.

Let me explain what I have so far.

class IList {
    public:
    virtual IList& operator+( IList&) = 0;
    virtual void print() = 0;
    virtual int g_Size() const = 0;
//perfect till here

    virtual void Push(const int&) = 0;//needs to be upgraded
    virtual const int& operator[](int index) = 0;//needs to be upgraded
};

template<class T>
class Queue: public IList{
    //internal stuff
public:
    Queue();
    int g_Size() const;

    void print();

    void Push(const T& cv);

    const T& operator[](int index);

    ~Queue();
};//all implementation of Queue<T> is implemented and working, but removed for simplicity

class CIntList : public Queue<int>{
    //stuff here, specialized on int
};

class C_Manager{
    IList * classes[3];//notice the polymorphism, managing the class collection using a pointer to the common(base) interface
public:
    void testing()
    {
        for (int i = 0; i < 3; i++)
            classes[i] = new CIntList(i);
        classes[0]->Push(1); classes[0]->Push(2); classes[1]->Push(1121); classes[2]->Push(12);
        classes[0]->print();
        classes[2]->print();
        int a = classes[0]->operator[](1);
        classes[1]->Push(a + a);
    } //working fine
};

OK, so you`ll maybe ask, what is the question?

I do not want to redeclare the Push and operator[] (or any other function that uses the template as argument) for all of my classes specializations. More exactly, if I want to add, let's say,

class CFloatList: public Queue<float>
{
      //float stuff goes here
};

I must also modify IList to

class IList {
        public:
        virtual IList& operator+( IList&) = 0;
        virtual void print() = 0;
        virtual int g_Size() const = 0;
    //perfect till here

        virtual void Push(const int&) = 0;//used for int
        virtual const int& operator[](int index) = 0;//used for int

    //NEW DECLARATION FOR FLOAT
        virtual void Push(const float&) = 0;//used for float
        virtual const float& operator[](int index) = 0;//used for float

    };

How can I avoid these redeclarations? I need sort of "virtual function templates", but this is not supported in C++.

Is my approach wrong?

Sorry for not highlighting the c++ syntax, this is my first post and I only managed to format it in code blocks. Thank you for your time!

EDIT #1 A BETTER SOLUTION(as suggested by jaggedSpire - many, many thanks)

I have modified IList to

class IList {
    public:
    virtual IList& operator+( IList&) = 0;
    virtual void afis() = 0;
    virtual int g_Size() const = 0;


    //templates
    template<typename T>
    void Push(const T& arg) //WORKS PERFECTLY
    {
        Queue<T>* cast = dynamic_cast<Queue<T>*>(this);
        cast->Push(arg);
    }
    template<typename T>
    const T& operator[](int index) //error
    {
        Queue<T>* cast = dynamic_cast<Queue<T>*>(this);
        return cast->operator[](index);
    }
};

and void C_Manager::testing() to

class C_Manager{
    public:
        void testing()
        {
            IList * a = new CIntList(1);
            a->Push(200);//WORKS PERFECTLY
            int c = a->operator[](0); //ERROR
        }
    };

and it produces these errors

Error   C2783   'const T &IList::operator [](int)': could not deduce template argument for 'T'

Error   C2672   'IList::operator []': no matching overloaded function found

intellisense: no instance of function template "IList::operator[]" matches the argument list

Basically, it complains about every possibly templated function that has a T-related return type. How can I fix this to make my manager truly polymorphic?

Community
  • 1
  • 1
7lym
  • 43
  • 7
  • In my experience, a "manager of managers" is better off implemented with the meta-manager knowing exactly the manager types that it holds. Through these, one can be explicit about type. Otherwise you end up with unions or boost::variant, or abuse of polymorphism. – AndyG Jan 26 '16 at 21:21
  • I would try to get rid of the polymorphic behaviour. – Guillaume Racicot Jan 27 '16 at 06:18

1 Answers1

1

First, let's review your requirements:

  • Have a non-templated polymorphic base class, IList
  • Have a class template, Queue<T> inherit from the base class to implement it.
  • Be able to specialize the Queue<T> however you want
  • Presumably, given you're returning void from push, and const T& from operator[], you want to signal errors with an exception.
  • Pass in a parameter of a specific type to the base class IList, and have the resulting behavior depend on whether the underlying type of Queue<T> matches the type of the given parameter.

This last bit is the key: you're trying to choose behavior of a function based off of both the runtime type of the caller and the static type of the argument. However, which type actually matches the T in the implementing Queue<T> is determined at runtime.

Runtime determination of behavior based off of the runtime types of two objects (because the argument is known at runtime as well as compile time) is what multi-methods are for. C++ doesn't have native multi-method support, but it can be pieced together with dynamic_cast

I picked up on the similarity to your present issue through this answer, which provides a wonderful array of links for more details on implementing (and implementations of) full multi-method functionality in C++.

Now, a brute-force/naive implementation of multi-methods in C++ would require testing the arguments for every possible implementing type from a list of implementing types. This is something you've also indicated you don't want, but not to worry: you won't need to. This is because we only want to test one circumstance, rather than the many required of a typical multi-method situation. We're handed the type of the argument to add at compile time, when we can conveniently use that information to find the type of the only destination type we're interested in.

For a supplied type of T, we want to test whether the type we're dispatching to is really Queue<T>.

To do that, we're going to use the same test used in the simpler multi-method implementations: dynamic_cast. Specifically, we're going to cast the this pointer to the type we're testing for, using the provided argument type as the source for the template argument required.

Be warned: this means that implicit conversion between types won't happen without an explicit template argument. If you pass in a string literal to your std::string container and don't explicitly specify that you want a std::string container, it's going to look for a container that holds character arrays the length of your string literal, and detect none. They're different types, after all.

With that said, let's get to the code. For an interface Parent that is implemented by a variety of Child<T>, you can use this to get T specific behavior from a Child<T> accessible only through a Parent interface:

class Parent{
    public:
    template <typename T>
    void foo(const T& t);

    virtual ~Parent(){}
};

template <typename T>
class Child : public Parent{

    public:
    void foo(const T& t);
};

// must be after the definition of the Child template, 
// because dynamic_cast requires a complete type to target
template <typename T>
void Parent::foo(const T& t){
    // throws on bad conversion like we want
    auto castThis = dynamic_cast<Child<T>&>(*this); 
    // if execution reaches this point, this is a Child<T>
    castThis.foo(t);
}

With:

template<typename T>
void Child<T>::foo(const T& t){
    std::cout << typeid(T).name() << ": " << t << '\n';
}


int main(){
    Parent&& handle = Child<int>();

    try{
        handle.foo<int>(3);
        handle.foo<char>(0);
        handle.foo<std::string>("Hello!");
    }
    catch(std::bad_cast e){
        std::cout << "bad cast caught\n";
    }
}

We get the following output on both g++ 5.2.0 and clang 3.7

i: 3
bad cast caught

Which is what we wanted.

Once you have the simple polymorphic interface presented here, implementing your collection should be easy. I'd go with a wrapper class around a std::vector<std::unique_ptr<Parent>> myself, but that decision is ultimately up to you.


Now, because this wasn't enough of a wall of text, some notes:

  1. Throwing an exception is not good for standard control flow. If you don't actually know whether or not an argument matches the underlying type via some external logic, you want some other form of error handling. dynamic_cast may be used to cast both references and pointers. Casting a reference to an object not of the target type will throw std::bad_cast. Casting a pointer will return a null pointer.
  2. Using the same name for a member function in a derived class as a templated member function calling that member function in the base class works because of the way name lookup works in C++. From this answer:

    The basic algorithm is the compiler will start at the type of the current value and proceed up the hierarchy until it finds a member on the type which has the target name. It will then do overload resolution on only the members of that type with the given name. It does not consider members of the same name on parent types.

So the lookup for foo will start in Child<T>, and since it finds a member function with that name inside Child<T>, it doesn't examine Parent or call the dispatching function again.
3. I would consider why I'm doing this carefully before actually using this sort of workaround.

Community
  • 1
  • 1
jaggedSpire
  • 4,423
  • 2
  • 26
  • 52
  • Thank you very much for this reply. I successfully implemented this idea, but i have a small problem with my templated return types. I described this in my edited question, hope you can take a look at it :) – 7lym Jan 27 '16 at 13:29
  • @7lym It complains because with this pattern it doesn't know which type you expect to be in the container without you explicitly specifying it--there's no information in the call to use. I'd simply change the name of `operator[]` to something more explicit template-friendly, like `at`, and call with `a->at(0)`, though you may want to forward the implementation to `vector::at` for consistency on bounds-checking behavior: `at` in the standard library implies the implementation will throw on out-of-bounds. I typically use `get` for non-throwing indexers, but there's no convention I know of. – jaggedSpire Jan 27 '16 at 13:52
  • Great idea. I think I'll stay with operator[], `a->operator[](0);` works very well. Btw, how can I shortage the above expression? Something like `(*a)[0];` ? – 7lym Jan 27 '16 at 15:19
  • Unfortunately, it's [quite difficult to get explicit template arguments to play nice with any of the operators](http://stackoverflow.com/a/1762137/4892076). They're syntactic sugar, and the syntax for calling member function templates is best for the common case. You can make a helper as illustrated in the above link, change the name of the function to something like `get` or ensure the type may be deduced from the call alone, like what I did [here](http://coliru.stacked-crooked.com/a/20aaabd9420b916b). – jaggedSpire Jan 27 '16 at 15:38
  • I now understand. Thank you for your quick and complete answers! I will mark this answer as accepted. – 7lym Jan 27 '16 at 15:48