Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Overloading" pure virtual function with different set of arguments

Consider following code sample

#include <iostream>
using namespace std;

class Color
{
public:
    virtual void mixColors(Color &anotherColor) = 0;
};

class RGB : public Color
{
public:
    void mixColors(RGB &anotherColor);
};

void RGB::mixColors(RGB &kol)
{
    return RGB(0xABCDEF);
}

I perfectly know why this code is not working (mixColors() in RGB is not implementing pure virtual function, because it has different set of arguments). However I would like to ask if is there another approach to solve this problem. Let's say I would like to mix colors, but using different algorithm for different color classes. I would appreciate any help.

like image 584
stil Avatar asked May 30 '14 12:05

stil


3 Answers

In a language where inheritance entails subtyping, such as C++, you cannot make a member function argument "more specific" in a derived class. (At least not without breaking type safety.) For a full technical explanation, see here.

To understand this in more concrete terms, notice that, in your Color class, you are asserting the existence of a member function with signature:

virtual void mixColors(Color &anotherColor) = 0;

This means that any color can be mixed with any other color (not necessarily of the same class), and that the concrete implementation of this mixing procedure only depends on the class of the first color. Which is just plain wrong.

The simplest solution to your probem is to simply use function overloading:

// I am assuming RGB and CMYK are cheap to pass by value, which seems reasonable.
// If this is not true, you can always pass them by const reference.

RGB mix_colors(RGB rgb1, RGB rgb2) { ... }

CMYK mix_colors(CMYK cmyk1, CMYK cmyk2) { ... }

Or, assuming you really want to mutate one of the colors, instead of producing a new color object:

class RGB
{
    // ...
public:
    RGB & mix_colors(RGB);  // return *this at the end
};

class CMYK
{
    // ...
public:
    CMYK & mix_colors(CMYK);  // return *this at the end
};

There is a downside to using overloading instead of virtual member functions, however: overloads must be resolved at compile time, while virtual member functions can be dynamically dispatched. Sadly, if you need to perform runtime dispatch on the colors you want to mix, you are kind of screwed, because C++ has nothing like Haskell's type classes or Common Lisp's multimethods. You can encode multiple dispatch using the visitor pattern, but this is decidedly not pretty.

like image 82
pyon Avatar answered Oct 17 '22 16:10

pyon


Why do you need a virtual method here anyway?

If mixing an RGB color makes only sense if the argument is another RGB color, then why should there be a generatal mixColor(Color) method.

If you really need it, you could override and perform a dynamic cast:

class RGB : public Color
{
public:
    void mixColors(RGB &anotherColor);
    void mixColors(Color &c) override { return mixColors(dynamic_cast<RGB&>(c)); }
};

void RGB::mixColors(RGB &kol)
{
    return RGB(0xABCDEF);
}

This way, you will get an exception at runtime if you try to mix an RGB with a color of a different class.

like image 20
gexicide Avatar answered Oct 17 '22 15:10

gexicide


You don't want a pure virtual function in this case. A pure virtual function is the concept of a function that has to be explicitly overridden, no matter what. What you want is a normal virtual function.

You want to convey the concept that a color can always be mixed. However you don't want to guarantee that such function is always implemented. That is best expressed with a normal virtual function which throws an exception:

class Color
{
public:
    virtual ~Color(void) = 0;
    virtual auto mix_colors(Color& color) -> Color&
    {
        throw std::logic_error("Cannot mix unrelated color schemes");
    }
};
Color::~Color(void)
{ /* */ }

If you utterly dislike exceptions, you can just print out to std::cerr or something similiar. You can also write your own exception and catch it.

Now you can create different color schemes and overload this inherited virtual member function:

class HSL; class HSV; class RGB;
class RGB: public Color
{ /* */ };
class HSV: public Color
{
public:
    auto mix_colors(HSL& color) -> HSV&
    {
        std::cout << "Mixing HSV with HSL" << '\n';
        return *new HSV{};
    }
};
class HSL: public Color
{
public:
    auto mix_colors(HSV& color) -> HSL&
    {
        std::cout << "Mixing HSL with HSV" << '\n';
        return *new HSL{};
    }
};

Here I have three color schemes RGB, HSV and HSL. Of course they can be all mixed with each other, but right now I only implemented the conversion member function from HSL to HSV and vice versa.

Now I can it like this:

int main(void)
{
    RGB rgb{};
    HSV hsv{};
    HSL hsl{};

    HSV new_hsv{hsv.mix_colors(hsl)};  // Mixing HSV with HSL
    HSL new_hsl{hsl.mix_colors(hsv)};  // Mixing HSL with HSV
    RGB new_rgb{rgb.mix_colors(hsv)};  // terminating with uncaught exception of type std::logic_error: Cannot mix unrelated color schemes Abort trap: 6
}
like image 2
hgiesel Avatar answered Oct 17 '22 15:10

hgiesel