Thursday, May 29, 2008

What is really a POD class?

For a project at hand, I'm using the offsetof() macro with a C++ class, and in an attempt to avoid a warning, I stumbled over the following, to my eyes, strange behavior.

Let's start with the following code:

class MyClass {
public:
  MyClass() { ... }

  size_t get_offset() { return offsetof(MyClass, y); }  // Gives warning
private:
  int x,y,z;
};
When compiling, I get the warning
offsetof.cc: In member function ‘int MyClass::get_offset() const’:
offsetof.cc:14: warning: invalid access to non-static data member ‘MyClass::y’ of NULL object
offsetof.cc:14: warning: (perhaps the ‘offsetof’ macro was used incorrectly)
The source of the problem is that offsetof() macro can only be used with POD structures, i.e., from Chapter 9 paragraph 4 in ISO/IEC 14882: 2003:
A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined copy assignment operator and no user-defined destructor.
Cool, so let's move all the members to a base class and inherit from it instead. That will make the base class a POD, so we can get the offset of the member from there and just add the other stuff in the subclass.
struct Base {
  int x,y,z;
};

class MyClass : public MyBase {
public:
  MyClass() { ... }

  size_t get_offset() { return offsetof(MyBase, y); }
};
... and as a result, I got no warnings! Very nice.

Aw, shoot, I cannot have x, y, and z public, I'd better make them protected so that MyClass can work with them, but they are not available to anybody else (encapsuling the state like a good programmer):

struct Base {
protected:
  int x,y,z;
};

class MyClass : public MyBase {
public:
  MyClass() { ... }

  size_t get_offset() { return offsetof(MyBase, y); }
};
... and then let's just compile it and off we go:
$ g++ -ggdb -Wall -ansi -pedantic    offsetof.cc   -o offsetof
offsetof.cc: In member function ‘int MyClass::get_offset() const’:
offsetof.cc:8: error: ‘int MyBase::y’ is protected
offsetof.cc:16: error: within this context
offsetof.cc:16: warning: invalid access to non-static data member ‘MyBase::y’ of NULL object
offsetof.cc:16: warning: (perhaps the ‘offsetof’ macro was used incorrectly)
Hey! What is going on now! Just making the member variables protected does not make the struct non-POD... or? Well, it turns out that it actually does, let us read that paragraph again (with the boldface emphasis added by me):
A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined copy assignment operator and no user-defined destructor.
So, what is an aggregate class then? Moving on to 8.5.1/1, we have:
An aggregate is an array or a class with no use-declared constructors, no private or protected non-static data members, no base classes, and no virtual functions.
So, making the members protected actually made the base class non-POD. Shoot... now what? Well, that restriction only applies to the MyBase class, not to the way we inherit from that class, so by just using protected inheritance, I will make the member variables protected within MyClass while MyBase is a POD, like this:
struct MyBase {
  int x,y,z;
};

class MyClass : protected MyBase {
public:
  MyClass() { x = 1; y = 2; z = 3; }

  int get_offset() const {
    return offsetof(MyBase, y);
  }

};
This also means that I finally found a good reason for protected inheritance, which is one of the language gadgets I really never have not seen any use for until now.