Imperfect C++ Practical Solutions for Real-Life Programming By Matthew Wilson | |
Table of Contents | |
Part Four. Cognizant Conversions |
Chapter 23. Template ConstructorsAn 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]
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. |