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

cbuchart
  • 10,847
  • 9
  • 53
  • 93
Avner Gidron
  • 411
  • 5
  • 13
  • 1
    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? – bolov Sep 07 '17 at 09:14
  • 1
    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. – Alex C Sep 07 '17 at 11:15
  • 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... – perencia Sep 07 '17 at 12:54
  • 1
    What is the use of sending un unknown type parameter? Parameters are for being used, as stablished this looks like a wrong approach. – Juan Chô Sep 07 '17 at 13:10
  • The usual way to implement double-dispatch with dynamic polymorphism is the visitor pattern https://en.wikipedia.org/wiki/Visitor_pattern – Alessandro Teruzzi Sep 07 '17 at 14:37
  • What are you really trying to do? If the type is unknown, how could you use the argument in any way? – curiousguy Sep 13 '17 at 21:52

4 Answers4

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
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!!!     
}
Nir
  • 1,618
  • 16
  • 24
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);
   }
Oliv
  • 17,610
  • 1
  • 29
  • 72
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;
}
gotocoffee
  • 199
  • 10
  • 6
    `Foo` needs at least one virtual member function (probably the destructor), otherwise `dynamic_cast` will not work. – Angew is no longer proud of SO Sep 07 '17 at 07:09
  • `I want to avoid using a base class for all the classes I pass to the function (something like object in C#).` – perencia Sep 07 '17 at 10:30
  • 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. – AzCopey Sep 07 '17 at 11:18
  • @AzCopey 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. – perencia Sep 07 '17 at 12:49