4

Good morning,

I have a templatized class and I want to manipulate objects by a vector of pointers. To use a vector of pointers to a templatized class I need this class to be derived from a non-templatized class, and I did it.

Here's my problem: to call a method of the derived class from a pointer to the base class, I cannot use virtual functions because template functions can't be made virtual. I need to make an explicit cast, which is tedious: once a create a number object with new, in fact, downcast need be made to number*, although the object is known to be number in advance.

I solved this problem in an awkward way: the function myset tests for all the supported values of typeid to get the correct dynamic cast. It is a long series of nested ifs which perform typeid checking.

Besides tediousness, the function only works for calling the 'set' method, and similar functions should be defined for calling other methods. If I could make an automatic casting to the kind of object I'm pointing to, it would all be much simpler.

The cons of the approach are:

  1. the code is repetitive: if I defined another function (such as T get() {return val;}, I would need another myget function with a full set of nested ifs!
  2. the list of supported types must be explicitly defined by nesting if calls
  3. the code may be inefficient

The compiler knows that lst[0] (in the code below) points to a number object, although lst is a vector of element objects from which number<> objects are derived.

Is there a method to automatically downcast a pointer defined as base* to a pointer to the object actually pointed at?

If I had the correct downcasting, then I could define thousands of methods in the number<> class and call them with a propercasting -> function(...) call

Here's the code (it works properly, the core is the definition of "myset")

Thanks in advance, Pietro M.

PS I'm mostly interested in a standard C++ approach without using other libraries except STL, such as Boost.

#include <iostream>
#include <vector>
#include <typeinfo>

using namespace std;

class element
{
public:
    virtual void print() = 0; // print is not templatized and works properly
    //template <class T> virtual set(T v) = 0; // this would solve all my problems, if only it were legal.
};

template <class T>
class number : public element
{
    T val;
public:
    void print() {cout << "number is " << val << endl;}
    void set(T v) {val = v;}
};

// That's the best I can do!
template <class T>
void myset(T v, element *ptr)
{
    // There is a kink in the template: the compiler checks the T type by the value of v, that in this case is an integer:
    // cout << "Type name for the template is: " << typeid(T).name() << endl;
    // cout << "Type name for an integer is:   " << typeid(int).name() << endl;

    if (typeid(*ptr) == typeid(number<double>))
    {
        ((number<double> *) ptr) -> set(7);
        return;
    }
    else if (typeid(*ptr) == typeid(number<float>))
    {
        ((number<float> *) ptr) -> set(7);
        return;
    }
    // add other types... (tedious)
    else
    {
        cout << "type not supported" << endl;
    }
}

int main()
{
    vector <element *> lst; // list of heterogeneous templatized objects
    lst.push_back(new number<float>);
    lst.push_back(new number<double>);

    lst[0] -> print();

    //((number<float> *) lst[0]) -> set(7); it's correct but it requires I know the type when I call it (tedious)

    myset(7, lst[0]); // may be inefficient, but it works (for the types which are explicitly supported)

    // cast_to_what_the_pointer_actually_points_to <lst[0]> -> set(7); // that's what I'd like to do: a downcast function which checks the object type and returns the correct pointer would be able to call any class method...

    lst[0] -> print();
}
pietrom79
  • 41
  • 1
  • 4
  • So, the types do not have a common interface (well, besides `print`). Why do you try to treat them as such? (Alternatively, you may want to use a language with dynamic typing instead of C++) – R. Martinho Fernandes Jul 22 '13 at 10:09
  • I would like them to have a common interface using polymorphism, but I can't define a template virtual void set(T) = 0; in the base class because the compiler doesn't want template virtual functions. I found no way to define a virtual function set inside element, so I hoped I could circumvent this limitation. – pietrom79 Jul 22 '13 at 10:16
  • 1
    No, that doesn't look like what you want. You don't want all subclasses to have functions to accept *any and all types* (that's what a template virtual function would mean, and that's also the reason why it's not allowed). The first step to solve this problem is to identify exactly what you want from this. – R. Martinho Fernandes Jul 22 '13 at 10:18

1 Answers1

3

You're nearly there. You need to have a templated set method that calls a private virtual method with the typeid and a void * pointer to its argument, allowing the override to decide how to handle it:

class element
{
    virtual void set_impl(const std::type_info &, const void *) = 0;
public:
    template <class T> void set(T v) { set_impl(typeid(v), &v); }
};

And an example of how to write set_impl:

template <class T>
class number : public element
{
    T val;
    void set_impl(const std::type_info &ti, const void *pv) {
        if (ti == typeid(T)) {
            val = *static_cast<const T *>(pv);
        } else {
            throw std::invalid_argument("incorrect type to set()");
        }
    }
};

This is similar to the approach taken by Boost.Any.

Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Thank you very much. I tried to make it work but there is an issue. If I call the function set, the compiler will induce the template type from the argument v, not the derived object. If I call lst[0] -> set(7); The template parameter will be int, because '7' is interpreted as such. Thus, the compiler will call the method of number which is not the class pointed by lst[0]. I tried to solve the problem by calling set_impl with typeid(*this), but then v, stored as an integer, was interpreted as float, providing a meaningless result. Anyway, I'm quite close. Thanks! – pietrom79 Jul 22 '13 at 15:02