C++ passing unknown type to a virtual function
Asked Answered
A

4

17

I'm writing in C++ and I want to pass an unknown type (known only in run time) to a pure virtual function:

virtual void DoSomething(??? data);

where DoSomething is an implementation of a pure virtual function in a derived class.

I planned to use templates but as it turn out virtual function and templates don't work together: Can a C++ class member function template be virtual?

I want to avoid using a base class for all the classes I pass to the function (something like object in C#).

Thanks in advance

Anallise answered 7/9, 2017 at 6:49 Comment(6)
can you please be more specific? How do you use data inside the function? What are the requirements of the type for data? E.g. there are a only few known classes, or you plan to accept any class that has a specific method etc?Polythene
Your question needs to be narrowed down a bit. Do you know the range of types, do you want them to be automatically deduced? The simplest answer would be "use void*", a better answer could be the one of Story Teller. It all depends on your usecase.Librettist
I'm curious, looking at the answers, about if there's any possibility to achieve the same result without having to do any casting on doSomething. Something like encapsulating the type in some way without having to make doSomething a template, and retrieving that type with a decltype, i.e. Kind of a virtual factory method...Millar
What is the use of sending un unknown type parameter? Parameters are for being used, as stablished this looks like a wrong approach.Oxyacetylene
The usual way to implement double-dispatch with dynamic polymorphism is the visitor pattern en.wikipedia.org/wiki/Visitor_patternUnfolded
What are you really trying to do? If the type is unknown, how could you use the argument in any way?Toadstool
Y
18

You need type erasure. An example of this is the general purpose boost::any(and std::any in C++17).

virtual void DoSomething(boost::any const& data);

And then each sub-class can attempt the safe any_cast in order to get the data it expects.

void DoSomething(boost::any const& data) {
  auto p = any_cast<std::string>(&data);

  if(p) {
    // do something with the string pointer we extracted
  }
}

You can of course roll out your own type erasing abstraction if the range of behaviors you seek is more constrained.

Yapok answered 7/9, 2017 at 6:51 Comment(0)
I
3

If you do not want to use boost/C++17 any, consider deriving the parameter of 'doSometing' function from a base class, and do dynamic cast to the right class object. In this case you can check in runtime that you got a valid pointer.

class param{
public:
    virtual ~param(){};
};

template <typename T>
struct specificParam:param{
    specificParam(T p):param(p){}
    T param;
};


class Foo
{
public:
    virtual void doSomething(param* data) = 0;
};

template <typename T>
class Bar : public Foo
{
public:
    virtual void doSomething(param* data){
        specificParam<T> *p = dynamic_cast<specificParam<T> *>(data);

        if (p != nullptr){
            std::cout<<"Bar got:" << p->param << "\n";
        }
        else {
            std::cout<<"Bar: parameter type error.\n";
        }
    }
};

int main(){
  Bar<char>   obj1;
  Bar<int>    obj2;
  Bar<float>  obj3;

  specificParam<char>   t1('a');
  specificParam<int>    t2(1);
  specificParam<float>  t3(2.2);

  obj1.doSomething(&t1); //Bar got:a
  obj2.doSomething(&t2); //Bar got:1
  obj3.doSomething(&t3); //Bar got:2.2

  // trying to access int object with float parameter
  obj2.doSomething(&t3); //Bar: parameter type error.
}

The simplest (but unsafe!) way would be to use void* pointer + static cast

class Foo
{
public:
    virtual void doSomething(void* data) = 0;
};

template <typename T>
class Bar:public Foo
{
public:
    virtual void doSomething(void* data){
        T* pData = static_cast<T*>(data);
        std::cout<<"Bar1 got:" << *pData << "\n";
    }
};

int main(){

  Bar<char>  obj1;
  Bar<int>   obj2;
  Bar<float> obj3;

  char  c = 'a';
  int   i = 1;
  float f = 2.2;

  obj1.doSomething(&c); // Bar1 got:a
  obj2.doSomething(&i); // Bar1 got:1
  obj3.doSomething(&f); // Bar1 got:2.2

  //obj2.doSomething(&c); // Very bad!!!     
}
Indecorous answered 7/9, 2017 at 8:11 Comment(0)
Z
1

Type-erasure is not the only possibility.

You may be interested to use the visitor pattern: take as argument an std::variant and visit it with a lambda containing the template code you wanted to implement:

virtual void doSomething(std::variant<int,float/*,...*/> data)
   {
   visit([=](auto v){/*...*/;},data);
   }
Zoo answered 7/9, 2017 at 10:26 Comment(0)
W
0

something like that?:

class Foo
{
    virtual ~Foo() = 0;
};

template <typename T>
class Bar : public Foo
{
    T object;
}

...

virtual void DoSomething(Foo* data)
{
    Bar<int>* temp = dynamic_cast<Bar<int>*>(data);
    if (temp)
         std::count<<temp->object;
}
Wimsatt answered 7/9, 2017 at 7:8 Comment(4)
Foo needs at least one virtual member function (probably the destructor), otherwise dynamic_cast will not work.Ironworker
I want to avoid using a base class for all the classes I pass to the function (something like object in C#).Millar
This doesn't require a base class for each object passed into the function, only for the wrapper Bar. While this is technically the 'passed in object', it is unlikely to be what the OP meant.Barely
@Barely From a practical point of view I consider the wrapping and the derivation to be the same mechanism in this case, as doSomething has to downcast the received object; but I see your point.Millar

© 2022 - 2024 — McMap. All rights reserved.