Presenting the Problem
Suppose your app needs to interface with a non-C++ module written in C or SQL. To do so, you need to ensure that all objects passed to the non-C++ module have
POD types. Alas, the definition of a POD type is rather evasive:
struct S1 { int x;};
class S2 { public: void func(); };
union S3 { struct { int x, y; } t; char c[4];};
struct S4 : S1, S2 {};
The above are all POD types. However, the following aren't:
struct C1 {
virtual void func(); //has a virtual function
};
struct C2 {
struct T{ int x, y; };
~C2(); //has a destructor
};
struct C3 : virtual S1 {} ; //has a virtual base class
Several programming idioms rely on the distinction between POD and non-POD types. The standard macro
offsetof() (defined in
<csstddef>) is an example. The following expression:
size_t nbytes = offsetof (S, mem);
returns the offset in bytes of the member
mem. According to the C++ standard,
S must be a POD class, struct, or union. Otherwise, the results are undefined. Your task is therefore to write a constraint that automatically distinguishes between POD and non-POD types at compile-time. When the "must be POD type" constraint is violated, the compiler should issue an intelligible error message.
Implementing a Constraint
A constraint is essentially an expression or declaration within a member function of a class template. When the constraint is violated, the said expression triggers a compilation error. The challenging part is to find the right compile-time expression(s) that will fire when the constraint is violated. Familiarity with the C++ standard certainly wouldn't hurt here. Fortunately, the standard states in clause 9.5 that a non-POD object shall not be a member of a union. Eureka! Take advantage of this restriction by creating a union whose sole member is the object you want to test:
template <class T> struct POD_test
{
POD_test()
{
union
{
T t; //T must be a POD type
} u;
}
};
Compilers generate code only for member functions that are actually called, either implicitly or explicitly. Therefore, implementing the constraint inside the constructor or destructor of a class template will guarantee its compile-time evaluation in every instance (later we will see how to improve this design).
To test the code, instantiate as many specializations as you like using diverse template arguments:
//the following three pass compilation
POD_test <int> pi;
POD_test <S1> ps1;
POD_test <S4> ps4;
//these ones fail
POD_test <std::string> pstr;
POD_test <C1> pc1;
POD_test <C2> pc2;
As expected, the compiler issues error messages for the last three instances because their template arguments aren't POD objects.