23

I'd like to have a base class that has a constant field (like an unique ID associated with the class that can't be modified after compile time). So far the static const declaration would be just fine. Now, I'd like to inherit this base class and make sure that the children of this class do have the same field, but with their own values. How can I do this?

Let's say, I'd like to have a base class called Base with an ID field that holds the int value of 0. Then, I'd like to have the classes A, B and C, all of them being public children of Base and I'd like to make sure that these children would also have the ID fields with the respective values of 1, 2 and 3 (by 'making sure', I mean something like getting a compiler error if they don't have a ID explicitly declared).

If I could manage to build this scenario, my expectation would be that asking for the ID field of a Base* pointer, I should get different values depending whether the pointer was created as new A(), new B() or new C().

My guess would be to declare ID as virtual static const, which of course doesn't make sense and gives a compiler error.

But then what can I do to achieve the described result? (The only thing that I could imagine would be to declare ID as a virtual function returning an integer and then hard-code the value into the function body, but I'm looking for something more elegant.)

Thank you in advance!

Siska Ádám
  • 417
  • 1
  • 5
  • 11

2 Answers2

21

A static method cannot be virtual, and no data members can be virtual.

But you can hide static fields in derived classes and use a virtual method to return them.

class A
{
public:
    static const int ID = 0;
    virtual int getID() { return A::ID; }
};
class B : A
{
public:
    static const int ID = 1;
    virtual int getID() { return B::ID; }
};

Alternative:

class A
{
public:
    A(int id = 0) : ID(id) {}
    const int ID;
    getID() { return ID; }
};
class B : public A
{
public:
    B() : A(1) {}
};
Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • Yes, this is the only solution that I knew so far. But is there no way to solve this in a more elegant/simple way? Or if this is the only way to go, then my question is rather: what is the reason why the virtual inheritance that works with functions will not work with data members? I understand why actually `static virtual` wouldn't make sense, but I can't really understand why, let's say, a simple `virtual const int` field wouldn't make sense... – Siska Ádám Jun 06 '12 at 14:01
  • @SiskaÁdám because polymorphic behavior is not defined for data members. It's the way the language is designed. And it's a good thing IMO. – Luchian Grigore Jun 06 '12 at 14:03
  • @SiskaÁdám I posted an alternative, but I still find the first one better. – Luchian Grigore Jun 06 '12 at 14:05
  • What's crazy, in C++11 you can even make the virtual function `constexpr`. Which is not terribly useful, since it will fail to compile when you actually make use of virtual inheritance. Though `f();` actually compiles, given `f` is a template function taking an integer... – Damon Jun 06 '12 at 14:08
  • @LuchianGrigore I see. This was the point what I missed probably. However, could you please clarify in a few words why this is a good thing? I just can't think of an example where data member polymorphism could lead to more trouble than function polymorphism. Thanks, Ádám – Siska Ádám Jun 06 '12 at 14:11
  • 1
    @SiskaÁdám usually members denote state. If you use inheritance, you assume that class B is-a class A, and has all its members, plus others, but it has A's members. Not override. They can be different, but not overriden. I can't find an example where this would be helpful other than the one you posted, which is probably not a good design - there are other mechanism that can be used to determine the run-time type of an object (see RTTI). This `ID` thing seems like a hack. Maybe it's just me, because I'm not used to the concept of data member polymorphism, but I honestly can't see any utility. – Luchian Grigore Jun 06 '12 at 14:14
  • @LuchianGrigore Thanks for the help. Actually the ID thing was just a dummy example. In my actual scenario, I have an abstract base class for drawing commands, and the inherited classes will be the actual drawing commands. For efficiency reasons, the coordinates used by the drawing commands are stored separately in an array (having all points one after the other in a memory block I can make transformations faster) and the drawing commands just hold a pointer to their data. The inherited constant what I was dreaming of is the actual number of points required by the different drawing commands. – Siska Ádám Jun 06 '12 at 14:21
  • @SiskaÁdám I think if you need something like this, your design is faulty. But that would be to broad of a question for SO. I suggest you read Head First Design Patterns (no affiliation). – Luchian Grigore Jun 06 '12 at 14:22
  • @Damon: Do you mean *virtual inheritance* or plain inheritance and the use of a *virtual function*? Those are completely different. At any rate, `constexpr` on a virtual function makes sense for those cases where dynamic dispatch is disabled (either the final overrider is known at the place of call at compile time or dynamic dispatch is explicitly disabled) – David Rodríguez - dribeas Jun 06 '12 at 14:50
  • @DavidRodríguez-dribeas: The former is meant, e.g. `A a; [...] f;`. This will (for rather obvious reason) fail to compile (it is not provably a compiletime constant, after all). It's crazy that you can still make the virtual function `constexpr` though, _and it really works as intended_ too, with static dispatch (e.g. `f;`). – Damon Jun 06 '12 at 16:15
  • @Damon: So that is not the former, but the latter: plain inheritance and virtual functions (Virtual inheritance is a whole different beast). AFAIK, your example should compile. Because `a` is in the scope where it is accessed (i.e. the call to `getID()` is not performed through a reference/pointer) that does not require dynamic dispatch, which means that the appropriate override can be detected at compile time and that would allow inlining of the function in C++03, which in turn means that there should be no issue in using that particular call as a `constexpr`. – David Rodríguez - dribeas Jun 06 '12 at 16:32
  • Actually, the standard explicitly forbids 'constexpr' from being used on virtual methods. Some compilers may still allow it, but it is not standard. – Meta Sep 25 '16 at 17:12
0

It would indeed be very useful to have virtual static members in C++. They could easily be added to the language, no new keywords are necessary. The following code for example names shape types for a graphics library:

class Shape {
public:
    static constinit virtual std::string name = delete;
    static constexpr virtual bool closed = delete;
    ...
};

class Circle : public Shape {
public:
    static constinit std::string name override { "circle" };
    static constexpr bool close override { true };
    ...
};

class Line : public Shape {
public:
    static constinit std::string name override { "line" };
    static constexpr bool close override { false };
    ...
};

This declares Shape as an abstract base class, as the construction of Shape::name and Shape::closed are explicitely skipped via = delete.

The space for virtual static members could be allocated in the same VTable, that is already used for virtual function calls. If all virtual static members are constinit (new with C++20) or constexpr, then the VTable can be written to read-only memory, where most compilers write it currently, too. If not, then the VTable has to be placed in read-write memory instead.

In general, virtual static members don't need to be const, they could also be read-write.

Virtual static members could both be accessed with the classname as a prefix (where they will behave like normal static members) or via an object, where the object's VTable pointer will be used to access the right VTable.

As long, as they are not in the standard, they can be emulated using virtual functions, that return a reference to a local static variable:

virtual const std::string& get_name() const {
    static const std::string name { "circle" };
    return name;
}

In case, that a derived class does not override the static member (respectively the virtual getter function), the semantics between the real virtual static members and the emulated virtual static members are a bit different: The real virtual static member between parent and child class would actually refer to different instances of that object, with the constructor called for the parent and each child, which doesn't override the virtual static member. But the emulated getter function would always return a reference to exactly the same object. On read-only virtual static members, this shouldn't make a difference (unless the constructor actually initializes each instance differently), but on read-write virtual static members, updating them would make a difference.

Kai Petzke
  • 2,150
  • 21
  • 29