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?

5
  • 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. Commented Mar 1, 2013 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)? Commented Mar 1, 2013 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. Commented Mar 1, 2013 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. Commented Mar 1, 2013 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. Commented Mar 1, 2013 at 14:15

3 Answers 3

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> { ... };
Sign up to request clarification or add additional context in comments.

2 Comments

Hmm...interesting! But redefining the enum for any Derived classes requires a template specialization, right? Seems a bit clumsy..
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<Derived>
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.

2 Comments

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)?
@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.
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.

5 Comments

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?
@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.
@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 superset of their Base (they get everything from Base and add to it), so...could you elaborate a bit?
"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.: true, silly me. Obviously been staring too long at the screen :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.