Alternatives for Partial Template Function Specialisation

Alternatives for Partial Template Function Specialisation

By Oliver Wigley

Overload, 10(50):, August 2002


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.

Original Solution

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 Widget s 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>());

First Alternative

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.

Second Alternative

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);

Footnotes & References

[Alexandrescu] Andrei Alexandrescu, Modern C++ Design, Addison-Wesley, 2001 (section 2.5)

[Stroustrup] Bjarne Stroustrup, The C++ Programming Language 3rd Ed., Addison-Wesley, 1997 (Section 18.4)



[ 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





Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.