Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 23.  Template Constructors


23.4. Argument Proxies

The first solution, which can be made to work fully but is largely more horrible than the problem, uses what I call argument proxies. There are several types of argument proxies, corresponding to the various permutations of const/non-const, volatile/non-volatile and pointer/reference/value, although not all of them need to be defined. I'll demonstrate by showing just two of them.

We can make our bolt-in work with all four parameter types by providing only by-value template constructor arguments and using them with argument proxy classes. The two argument proxies we'd need for the example are the reference_proxy and the const_reference_proxy. The definition of reference_proxy is shown in Listing 23.5. const_reference_proxy is identical save that all references are const.[4]

[4] I don't doubt that I could generalize these various classes into a single definition, as they're pretty ancient in C++ template terms. But it's so rarely used, and so undeserving of any glorification, that I just haven't got around to it.

Listing 23.5.


template <typename A>


class reference_proxy


{


public:


  explicit reference_proxy(A &a)


    : m_a(a)


  {}


  reference_proxy(reference_proxy<A> const &rhs)


    : m_a(rhs.m_a)


  {}


// Accessors


public:


  operator A&() const


  {


    return m_a;


  }


// Members


private:


  A &m_a;


// Not to be implemented


private:


  . . . // Prevent copy assignment



There's no rocket science here. The important part of each of the templates is that the implicit conversion operator is const in all cases—so that it can be called on the temporary instance that eventuates inside the template constructor calls—and yet returns the full "type" of the type, that is, a non-const reference for reference_proxy and a non-const pointer for pointer_proxy. They could be used in our previous examples as follows:



String_t          s2(const_reference_proxy<std::string>(ss));





StringModifier_t  s1(reference_proxy<std::string>(ss));



No extra copy is made in the first case, and the non-const reference is passed through to the StringModifier_t instance in the second case. It works 100 percent correctly.

It is also possible to use forwarding functions, as we saw with the array_proxy in sections 14.4 and 14.6, as in:



template <typename A>


inline const_reference_proxy<A> const_ref_proxy(A &a)


{


  return const_reference_proxy<A>(a);


}





String_t          s2(const_ref_proxy(ss));





StringModifier_t  s1(ref_proxy(ss));



The Boost libraries define a similar set of components, whose forwarding functions are called ref and cref. I prefer the longer names shown here for the same reasons that the C++ casts (see section 19.2) have long and ugly names: they stand out clearly to the reader, are easily grep-able, and engender conscience-pricking discomfort when using them. This latter reason is especially important for argument proxies since they are a measure for dealing with a part of the language that is fundamentally flawed.

The problem is all too evident: The burden is on the client code to use them correctly in order to obviate the problems caused by the (broken?) template constructor mechanism.[5] This would be at least partially reasonable if the client code would otherwise always fail to compile. But as we've seen, this is only so for some cases. In others, compilers merrily generate code that is either inefficient or erroneous.

[5] This is analogous to the situation with destruction/finalization in Java and .NET, whereby the onus is on the user of a class to remember to clean up its resources. Is that object oriented? I don't think so!

I do not recommend you employ these techniques for any client-facing code, or, rather, to rely on users of your templates to be familiar with the technique and rigorous in its application. It's largely impractical. The reason I've deigned to discuss them—beyond the pedagogical—is that there are limited circumstances in which they've proved useful, even essential, when using a template within the implementation of some library code, well away from any client code. Use with caution!


      Previous section   Next section