![]() | |
![]() ![]() ![]() |
![]() | Imperfect C++ Practical Solutions for Real-Life Programming By Matthew Wilson |
Table of Contents | |
Chapter 23. Template Constructors |
23.4. Argument ProxiesThe 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]
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.
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! |
![]() | |
![]() ![]() ![]() |