Whilst template classes can be partially and wholly specialised, template functions cannot. Alexandrescu [Alexandrescu] presents a technique to simulate partial template function specialisation so that a uniform interface is preserved, and calls to the template function(s) can be made in a generic way. Key to the solution is a mapper type which the client code must use in the function calls. This extra 'type-to-type' mapper rather clutters the interface with what you might call an implementation detail. Here is a look at the original proposal, followed by some possible alternatives.
Overloaded template functions are at the heart of the 'specialisation'. A template struct is introduced (Type2Type) which serves as an identifier to facilitate function lookup:
//from [Alexandrescu]: template <typename T> struct Type2Type { typedef T OriginalType; }; template <class T,class U> T* Create(const U& arg, Type2Type<T>) { return new T(arg); } template <class U> Widget* Create(const U& arg, Type2Type<Widget>) { return new Widget(arg, -1); }
Create() 'new's instances of Widgets or instances of specific Widget-derived things. The Widget class constructors have two arguments - the second being an int which should be set to -1, and the derived classes' constructors all have just a single argument - hence the need for specialisation. The flavour of Type2Type which is passed to Create() determines which overload to use. The overload for the Widget class requires Type2Type<Widget>, and the completely generic version accepts Type2Type instances of Widget-derived types[1].
//test code #include <assert.h> struct WidgConfig {int i;}; struct Widget { Widget(const WidgConfig& setup, int a) { assert(-1==a); } }; struct SpecialWidget : Widget { SpecialWidget( const WidgConfig& setup) : Widget(setup,-1) {} }; WidgConfig cfg; SpecialWidget* psw = Create(cfg, Type2Type<SpecialWidget>()); Widget* wi = Create(cfg, Type2Type<Widget>());
Alexandrescu initially suggests overloading by passing dummy objects of the appropriate Widget type rather than the Type2Type struct, but then dismisses it as it requires the construction of potentially superfluous objects, incurring the overhead of that construction - and there's also the overhead of a pass-by-value to consider:
//from [Alexandrescu]: template <class T, class U> T* Create (const U& arg, T/*dummy*/) { return new T(arg); } template <class U> Widget* Create (const U& arg, Widget/*dummy*/) { return new Widget(arg, -1); }
If Create() is our only mechanism for getting instances of T or Widget, then we will have difficulty calling it the first time when we don't yet have an instance to pass. With a little tweaking, however, this does offer a feasible solution without the problem of superfluous object creation. Pointer types could be used to overload the function instead:
template <class T, class U> T* Create (const U& arg, T*/*dummy*/) { return new T(arg); } template <class U> Widget* Create (const U& arg, Widget*/*dummy*/) { return new Widget(arg, -1); }
Pass a pointer of the appropriate type and the correct function is called. No extra constructors or copy constructors are now being called and the template function is effectively specialised. Overloaded lookup can go ahead courtesy of an uncharacteristically welcome NULL pointer, so there's no need to worry about supplying a Widget instance that we don't have yet:
SpecialWidget* pSwi = Create(cfg, reinterpret_cast<SpecialWidget*>(0)); Widget* pWid = Create(cfg, reinterpret_cast<Widget*>(0));
As an extra, this approach offers new possibilities as we now have the option to pass a valid object, which can be exploited by either overload. Imagine the Widget class as a Window class:
template <class U> Window* Create (const U& arg, Window* parent) { if(parent) { Window* child = new Window(arg, -1, parent); return child; } else return new Window(arg, -1); } // [2]
This does offer an alternative style with extended flexibility, and doesn't require the Type2Type type.
Whilst template functions cannot be partially specialised, we can partially specialise template classes[3]. It could help to make use of the functor [Stroustrup] idiom:
template <class T, class U> struct Create { T* operator()(const U& args) { return new T(args); } }; template <class T> struct Create <Widget, T> { Widget* operator()(const T& args) { return new Widget(args, -1); } }; # WidgConfig cfg; SpecialWidget* psw = Create<SpecialWidget,WidgConfig>()(cfg); Widget* pw = Create<Widget,WidgConfig>()(cfg);
Although the function calls to Create might look rather esoteric, this does also offer a generic interface and dispels the need for extra types to be defined just to solve the original problem:
template <class T, class U> struct Create { T* operator()(const U& args) { return new T(args); } }; template <class T> struct Create <Widget, T> { Widget* operator()(const T& args) { return new Widget(args, -1); } }; SpecialWidget* psw = Create<SpecialWidget,WidgConfig>()(cfg); Widget* pw = Create<Widget,WidgConfig>()(cfg);
[1] Microsoft Visual C++ 6.0 considers the call to Create(cfg, Type2Type<Widget>) to be ambiguous, so the code does not port. Explicit template arguments are needed to coax it in the right direction, but consistency in the interface is broken:
SpecialWidget* psw = Create<SpecialWidget, WidgConfig>(cfg, Type2Type<SpecialWidget>()); Widget* wi = Create<WidgConfig>(cfg, Type2Type<Widget>());
See MSDN Knowledge Base article Q240869 for bug description. Also, if we try to specify explicit template arguments for the other overload as well: Create<SpecialWidget, WidgConfig>(cfg, Type2Type<SpecialWidget>()), then VC++ is unable to match the overload, generating compiler bug C2665. Strangely, it actually matches the lookup when you call it with Create<SpecialWidget>(cfg, Type2Type<SpecialWidget>()).
[2] We still need to help Microsoft Visual C++ 6.0 to know which function we want to call, by providing explicit template arguments, so a generic and portable style is lost.
[3] Microsoft Visual C++ 6.0 lacks support for partial specialisation of template classes. See MSDN Knowledge Base article Q240866. In that case each possible variation (i.e. constructor) for the Widget class would have to be fully specialised with a Create class of its own:
#ifdef __MSVC__ template <> struct Create <Widget, WidgConfig> { Widget* operator()( const WidgConfig& args) { return new Widget(args, -1); } }; // and any other complete // specialisations needed... #else // Create <Widget, T> implementation // as before #endif
Overload Journal #50 - Aug 2002 + Programming Topics
Browse in : |
All
> Journals
> Overload
> 50
(7)
All > Topics > Programming (877) Any of these categories - All of these categories |