Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Part Four.  Cognizant Conversions


Chapter 23. Template Constructors

An obvious disadvantage of bolt-ins, veneers, and other templates that derive from their parameterizing type(s), is that they hide that type's constructors. Consider the hypothetical classes in Listing 23.1.

Listing 23.1.


class Double


{


public:


  typedef double  value_type;


public:


  Double();


  explicit Double(double d);


public:


  double GetValue() const;


  . . .


};





template <typename T>


class Wrapper


  : public T


{


  . . .


};





typedef Wrapper<Double>  Double_t;





Double_t  d1;


Double_t  d2(12.34); // Error!



Wrapper has effectively hidden the second constructor of Double, so the attempt to nondefault construct it is an error.

The method of solving this problem for Microsoft's Active Template Library (ATL) is to avoid it. CComObject has a single constructor with the following signature, as do most of its brethren (some others have a default constructor only):



CComObject(void *pv = NULL);



This constructor is used to pass all kinds of things into the (your) bolted class, including interface pointers, pointers to containing classes, and so on. Recently this restrictiveness caused me to have to write two specific classes and two new templates just to create a component that could utilize an STL container held in the component's containing object; frustrating to say the least! There should be a better approach.[1]

[1] To be fair to the implementers of ATL, much of the initialization of an ATL object is done outside its constructor because ATL is designed to be independent of a C/C++ standard library and, therefore, exceptions. As is often the case in the real world, you can't have everything.

The answer to the problem in general would be to use template constructors. For our example, we could add the following template constructor:

Listing 23.2.


template <typename T>


class Wrapper


  : public T


{


  . . .


public:


  Wrapper()


  {}


  template <typename T1>


  explicit Wrapper(T1 t1)


    :  base_class_type(t1)


  {}


  . . .



The first thing to note is that we've had to add a default constructor. If we didn't do so, then the first object in our example program would be illegal. As we saw in section 2.2, whenever you define a nondefault constructor in the absence of a default constructor, the default constructor is effectively hidden. I should also point out that template constructors are never used to generate copy constructors [Dewh2003], so if you have a bolt-in that allocates resources you need to be careful about its definition in this regard, and either provide a copy constructor and copy assignment operator, or hide them (see section 2.2).

The main play is in the template constructor, which seems quite straightforward. The constructor simply takes a single argument of its template type T1. For Wrapper<Double> the constructor passed the double value through to Double's constructor. What could be simpler?

Alas, that's the best we can expect. As soon as we want to handle class types, or references, the fun starts.


      Previous section   Next section