1

I'm trying to build a class such that its subclasses will have a C++11 enum class attribute, with an associated setter/getter:

class Base { 
  public:
    enum class MyEnum {
        A,
        B
    } my_enum;

    Base() : my_enum(MyEnum::A) {}

    MyEnum get() { return my_enum; }
    void set(const MyEnum &ME) { my_enum = ME; } 
};

class Derived : public Base { 
  public:
    Derived(): Base() {
        my_enum = MyEnum::B;
    }
};

int main(int argc, char *argv[])
{
    Derived Deriv;

    // No problems!
    Deriv.get() == Derived::MyEnum::B;  

    return 0;
}

so far, so good!

However, I would like derived classes to be able to re-define the MyEnum enumeration class, while not having to re-implement the setter/getter/attribute all the time:

// Base as before

class Derived : public Base {
  public:

    enum class MyEnum {
        C,
        D 
    }; // intention: to override Base's "my_enum" attribute

    Derived(): Base() {
        my_enum = MyEnum::C;  
        // ERROR: cannot convert 'Derived::MyEnum' to 'Base::MyEnum' 
    }
};

int main(int argc, char *argv[])
{
    Derived Deriv;

    // ERROR: no match for 'operator==' for types 'Base::MyEnum' and 'Derived::MyEnum'
    Deriv.get() == Derived::MyEnum::C;  

    return 0;
}

I understand what the problem is; I'm just looking for the cleanest way to be able to reuse code for this case.

Preferably only through inheritance (or rather, the functionality should be available to Derived() classes solely by the act of deriving from Base()).

Any suggestions?

Rody Oldenhuis
  • 37,726
  • 7
  • 50
  • 96
  • 1
    This smells dubious. Are you going to switch based on enum? If you replace your `enums` with classes, you'll have a dual inheritance hierarchy. Put the behaviour where it belongs, on the class. – Peter Wood Mar 01 '13 at 11:58
  • @PeterWood: so in other words, "just live with it" (and copy-paste the exact same setter/getter/attribute name into dozens of derived classes, not just for 1 `enum`)? – Rody Oldenhuis Mar 01 '13 at 13:32
  • No. I don't see how you get that from what I said. I strive to avoid setters and getters. This looks like a good case for using the `Strategy` pattern, possibly the `State` pattern if the value will change over the lifetime of the object, and if not that, the `Visitor` pattern. – Peter Wood Mar 01 '13 at 13:47
  • @PeterWood: If that's not what you intended, what *did* you mean by "Put the behaviour where it belongs, on the class"? Thanks for the heads up to those patterns, going to analyze that now. – Rody Oldenhuis Mar 01 '13 at 13:58
  • 1
    Somewhere you would have code which makes a decision what to do, based upon the enumeration. Replace `query state + do something appropriate`, with a (polymorphic) call to `doSomething()` on a `state object`. You put the behaviour on the class. – Peter Wood Mar 01 '13 at 14:15

3 Answers3

1

The compiler is right: although the enumerations Base::MyEnum and Derived::MyEnum are defined in classes connected through inheritance, the enumerations themselves are not considered related. They just happen to have the same unqualified name, which means nothing to the compiler: as far as the compiler is concerned, the two enum types are unrelated.

If you think about the way the enums are implemented under the hood, this makes sense: despite being strongly typed, the enums remain small integral constants. Since the two are unrelated, Base::MyEnum::A would have the same value as Derived::MyEnum::C, and there would be nothing at runtime letting you distinguish between the two values.

Besides dumping all enumeration values into the enum of the base class (which kills opportunities to extend outside your own library) there is little you can do: C++ enumerations do not support inheritance, and are generally not very flexible.

Community
  • 1
  • 1
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • so basically you're saying -- "just live with it" (== copy all the `get/set` boilerplate in all `Derived` classes that need another definition of the `enum`)? – Rody Oldenhuis Mar 01 '13 at 13:03
  • 1
    @RodyOldenhuis Better yet, consider changing the design to avoid `enums` that need extensibility (i.e. "need another definition"). If subclass has a different enum, it's only natural that it should have a different getter and a different setter. After all, you would write a new set of getters/setters for properties of an entirely different type (e.g. `int` instead of `double`), right? `enum class` has the same strongly typed features as built-in types, and likewise, provides no inheritance. Of course you can always go the template route, but the clarity of your design may suffer. – Sergey Kalinichenko Mar 01 '13 at 14:15
1

This is a bad idea and is not going to work.

  • Language level You cannot redefine stuff in C++. You can only hide (not completely) stuff behind new, unrelated stuff with the same name. You can also override a virtual function by giving it a new implementation, while keeping its signature.
  • Design level Your base class defines a contract which derived classes must adhere to. If Base::get() returns a MyEnum {A, B}, then every derived class must have a get() that returns a MyEnum {A, B}. That's what inheritance is all about (and not code reuse, or not just code reuse at any rate).

You may be able to achieve code reuse by making your class into a template instead of relying on inheritance.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • I realize that using inheritance just for the sake of code reuse is a bad idea. Of course, IRL my `Base()` and `Derived()` are a lot more complicated, but in principle follow the basic 'is-a' relationship, justifying inheritance. I was just wondering whether I could use the existing mechanisms/interfaces to weed out some of the repetitive code. So, when relaxing the requirement of "only through inheritance": how would you do it using templates/composition/whatever? – Rody Oldenhuis Mar 01 '13 at 12:47
  • @RodyOldenhuis: the enum "is-a" goes the **opposite way** of the class inheritance. the values of the base class enum is a subset of the derived class enum, while the derived class instances are a subset of the base class instances. this makes it possible (or would make it possible) to set an invalid value for the type of object, while respecting all static typing. thus you have a design level error. a thinko. – Cheers and hth. - Alf Mar 01 '13 at 12:48
  • @Cheersandhth.-Alf: Not sure I understand; the enums in `Base` and `Derived` are completely unrelated and so are neither a subset/superset of the other. Also, `Derived` classes are a *super*set of their `Base` (they get everything from `Base` *and* add to it), so...could you elaborate a bit? – Rody Oldenhuis Mar 01 '13 at 13:00
  • "Derived classes are a super*set of their Base" -- wrong. Every Derived object is also a Base object (every herring is a fish). – n. m. could be an AI Mar 01 '13 at 13:01
  • @n.m.: true, silly me. Obviously been staring too long at the screen :) – Rody Oldenhuis Mar 01 '13 at 13:08
1

You could make Base a template, parameterised by the enum type, and use a traits class to provide a "default" enum type which can be specialized by derived types.

template<typename T>
struct MyEnumTraits
{
    enum class type {
        A,
        B
    };

    static const type default_value = type::A;
};

template<typename T = void>
class Base { 
  public:

    typedef typename MyEnumTraits<T>::type MyEnum;
    MyEnum my_enum;

    Base() : my_enum(MyEnumTraits<T>::default_value) {}

    MyEnum get() { return my_enum; }
    void set(const MyEnum &ME) { my_enum = ME; } 
};

class Derived;

template<>
struct MyEnumTraits<Derived>
{
    enum class type {
        C,
        D 
    };
    static const type default_value = type::C;
};

class Derived : public Base<Derived> {
    // ...

But now different derived types will have different base classes, which is probably not what you want. You could solve that by keeping the non-template Base and moving the getter and setter into an intermediate class template that derivecs from Base and then the derived types derive from that.

class Base { ... };

template<typename T = void> class GetterSetterImpl : public Base { ... };

class Derived : public GetterSetterImpl<Derived> { ... };
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Hmm...interesting! But redefining the `enum` for any `Derived` classes requires a template specialization, right? Seems a bit clumsy.. – Rody Oldenhuis Mar 01 '13 at 12:55
  • Clumsy is in the eye of the beholder ;) You cannot define the enum inside `Derived` if you want the base class to refer to it, because the type `Derived` is not complete at the point where you instantiate `GetterSetterImpl` – Jonathan Wakely Mar 01 '13 at 13:52