2

I would like to do something that probably is not possible in Cpp, but I could not find a post about this specifically.

I want to have a derived class specify the type of a void* parameter on a virtual function.

I have a base class called interface with a send function.

// pure virtual
class Interface{
    virtual bool Send(const void*)=0;

};

struct Packet{
    DataType data;
};

class SpecificInterface{
     bool Send(const DataType*);
}

Is there a way to make something like this work? The intent is that SpecificInterface::Send implements Interface::Send. Allowing SpecificInterface to not be a pure virtual while restricting the void* to a specific packet type.

Otherwise I know I could take a void* parameter and static_cast it into the Packet* type; however, I do not want others to send a pointer type that cannot be cast to Packet*.

Let me know if this is not clear

Robert Andrzejuk
  • 5,076
  • 2
  • 22
  • 31
Czarking
  • 143
  • 1
  • 10
  • 2
    No, it can't work this way. Assume you have some `SpecificInterface& foo` and want to call `Send`. How should the compiler know what class `foo` actually points? When implementing an interface you can't just change the signature or functionality of the interface, that would defy the point of having interfaces. – Lukas-T Jul 14 '20 at 18:06
  • 1
    The important thing to remember is that a `SpecificInterface` is still an `Interface`, and anything that an `Interface` can send must also be something that a `SpecificInterface` can send. See the [Liskov substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle). – alter_igel Jul 14 '20 at 18:09
  • @AdrianMole yes thanks – Czarking Jul 14 '20 at 18:15
  • 1
    @alterigel That is a good point maybe if I need this level of specification then SpecificInterface is not really an Interface. Or at least in the way I have defined Interface. – Czarking Jul 14 '20 at 18:16
  • What should happen if we do `Interface& i = something(); int x = 5; i.send(&x);`? – user253751 Jul 14 '20 at 19:21

2 Answers2

5

When you want to override a virtual function, the number of arguments and the types of the arguments must exactly match the declaration in the base class. You'll have to use:

class SpecificInterface{
     bool Send(const void* ptr)
     {
        cont DataType* dataTypePtr = static_cast<const DataType*>(ptr);
        // Now use dataTypePtr any way you wish
     }
};

Please note that use of such code is dangerous. If ptr does not really point to a DataType object, your program will have undefined behavior.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • My understanding was something like this being better. The downside being the crash if ptr is not the right type. Are you saying reinterpret is better because I have a chance to handle the memory in case of wrong pointer type? https://stackoverflow.com/questions/310451/should-i-use-static-cast-or-reinterpret-cast-when-casting-a-void-to-whatever – Czarking Jul 14 '20 at 18:15
  • @Czarking, no, I am not saying that. `reinterpret_cast` is a coercion. If `ptr` does not point to a `DataType` object, your program will most likely crash and burn. – R Sahu Jul 14 '20 at 18:19
  • Ok why not use static_cast then? – Czarking Jul 14 '20 at 18:21
  • @Czarking, You can. When it comes to casting a `void*`, neither `static_cast` nor `reinterpret_cast` provide any safefy. You are on your own at that point. – R Sahu Jul 14 '20 at 18:24
4

@RSahu is correct, of course. You could still use a virtual method to do about the same thing:

class Interface {
    virtual bool send(const void*) = 0;
};

struct Packet {
    DataType data;
};

class SpecificInterface {
    bool send(cont void*) override { 
        send(static_cast<DataType*>(data));
    }
    bool send(cont DataType*); // code which actually does something
};

However - I recommend against your whole approach to begin with - it is massively unsafe, since the validity of the type is never checked! It's a source of many potential bugs. More often than not, you can avoid doing this. Here are a few things you might try instead:

  1. std::any - a class which doesn't offer you compile-time type safety, but at least checks types at run-time. You would have a send(const std::any& data) virtual function, and inside it you would call std::any_cast<DataType>(data) to get a DataType or std::any_cast<DataType>(&data) to get a DataType *.

  2. Probably even better - the Curiously-recurring template pattern (CRTP):

    template <typename T>
    class Interface {
         virtual bool send(T*) = 0;
    }; 
    
    class SpecificInterface : Interface<DataType> {
        bool send(cont DataType*) override;
    }
    
einpoklum
  • 118,144
  • 57
  • 340
  • 684