|
|
|
Sponsored Link •
|
|
Advertisement
|
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 {
public:
typedef mpl::vector<ThreadSafe, Reviewed> BaseFeatures;
virtual void vf(int x, std::string& s, MakeFeatures<BaseFeatures>::type features);
...
};
class Derived: public Base {
public:
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 {
public:
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;
BOOST_MPL_ASSERT((boost::is_base_of<DerFeaturesClass,BaseFeaturesClass>));
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 Object |
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.
|
Sponsored Links
|