The Artima Developer Community
Sponsored Link

The C++ Source
Enforcing Code Feature Requirements in C++
by Scott Meyers
September 23, 2008

<<  Page 3 of 4  >>


Feature constraints and virtual functions

Virtual functions introduce a new issue, one arising from the C++ rule that virtual function overrides in derived classes must declare the same parameter types as their base class counterparts. A derived class override may be invoked through a pointer or reference to a base class, so the override must certainly offer the code features promised by the base class function, but there is no reason why a virtual function in a derived class shouldn't be allowed to offer more features than the corresponding base class function. Unfortunately, straightforward application of the current design fails to allow that:

class Base {
  typedef mpl::vector<ThreadSafe, Reviewed> BaseFeatures; 
  virtual void vf(int x, std::string& s, MakeFeatures<BaseFeatures>::type features);

class Derived: public Base {
  typedef mpl::vector<ThreadSafe, Reviewed, Portable> DerFeatures;                  // note revised  
                                                                                    // def'n compared
                                                                                    // to base class

  virtual void vf(int x, std::string& s, MakeFeatures<DerFeatures>::type features); // doesn't override 
  ...                                                                               // Base::vf!

What's needed is a way for derived classes to satisfy C++'s rule that virtual overrides have the same parameter types as their base class counterparts yet also advertise implementations offering additional code features. (Interestingly, this problem would vanish if C++ allowed contravariant parameter types, because MakeFeatures<BaseFeatures>::type (the base class function's feature set) inherits from MakeFeatures<DerFeatures>::type (the derived class function's feature set).)

Overloading provides an effective solution to this problem. The derived class declares two functions with the same name, one using the same feature set type as the base class, the other using the enhanced feature set the derived class wishes to offer. The implementation of the base class override consists of a simple inline call to the enhanced function. Class Derived above would thus be implemented like this:

class Derived: public Base {
  typedef mpl::vector<ThreadSafe, Reviewed, Portable> DerFeatures;   // as before

  virtual void vf(int x, std::string& s,                             // override base
                  MakeFeatures<BaseFeatures>::type features)         // virtual function  
     // verify feature contravariance
     typedef MakeFeatures<BaseFeatures>::type BaseFeaturesClass; 
     typedef MakeFeatures<DerFeatures>::type DerFeaturesClass; 

     return vf(x, s, MakeFeatures<DerFeatures>::type());             // inline call to
  }                                                                  // enhanced function

  virtual void vf(int x, std::string& s,                             // as before
                  MakeFeatures<DerFeatures>::type features);  


This design offers callers invoking virtual functions through a base class interface the code features advertised by that interface while also allowing callers aware of the derived interface to take advantage of the additional code features provided by the derived class. It is thus analogous to C++'s support for covariant return types on virtual functions.12


In principle, feature checking incurs no runtime cost, because everything happens during compilation. Each feature set parameter, however, could lead to a runtime argument being passed from caller to callee, even though the argument would go unused. Whether this occurs depends on the optimization settings and capabilities of the C++ compiler. If such objects are not optimized away, Table 1 demonstrates that their size could be significant (up to many thousands of bytes per feature set), an artifact of the use of virtual inheritance14 in the current implementation.

Features in
Feature Set
gcc 4.1.1 Visual C++ 9 Comeau 4.3.9
0 64 388 7672
1 32 164 1884
2 16 68 452
3 8 28 108
4 4 12 28
5 4 4 8

Table 1: sizeof(feature set) for different 32-bit compilers given 5 total features.

C++ template metaprogramming has a reputation for causing significant increases in compilation times, and my experience is that this reputation is not undeserved. Test programs of a few dozen lines (excluding header contents) often took 30 seconds or longer to compile, and experiments with more than 5 total features were abandoned due to excessively long compilation times or, in the case of Visual C++, aborted compilations due to internal compiler errors.

The implementation described here was designed only as a proof of concept, however; efficiency was not a concern. It is reasonable to expect that less costly implementations would be devised were that aspect of the problem to become the focus of attention. For example, the current implementation passes feature set parameters by value rather than by pointer or reference-to-const, a decision motivated by the desire to avoid adding unnecessary symbols to the already somewhat cumbersome TMP-based syntax.

<<  Page 3 of 4  >>

Sponsored Links
