Imperfect C++ Practical Solutions for Real-Life Programming By Matthew Wilson | |

Table of Contents | |

Chapter 18. Typedefs |

## 18.4. True TypedefsIf we look at typedefs as a transfer of type from one type name to another, we can see that we want to allow two-way transfer for contextual typedefs, but only a one-way transfer for conceptual type definitions. What we would like would be to have two distinct keywords to represent this concept. I would suggest that the alias int IntType; int i1; IntType i2; i1 = i2; // int and IntType are fully interchangeable, and i2 = i1; // they are in fact the same type The typedef int IntType; int i1; IntType i2; i1 = i2; // Invalid! Cannot convert IntType to int i2 = i1; // Invalid! Cannot convert int to IntType But this is the real world:
In the spirit of the imperfect practitioner, I applied tenet #4—Never give up!—and after many attempts around the problem arrived at the solution, in the form of the ## Listing 18.6.template< typename T , typename U> class true_typedef { public: typedef T &reference; typedef T const &const_reference; // Construction public: true_typedef() : m_value(T()) {} explicit true_typedef(T const &value) : m_value(value) {} true_typedef(true_typedef const &rhs) : m_value(rhs.m_value) {} true_typedef const &operator =(true_typedef const &rhs) { m_value = rhs.m_value; return *this; } // Accessors public: const_reference base_type_value() const { return m_value; } reference base_type_value() { return m_value; } // Members private: T m_value; // Not to be implemented private: // Not provided, as the syntax is less ambiguous when // assignment from an explicit temporary is made true_typedef const &operator =(T const &value); }; As you can see, it's pretty simple. It contains a single member of the primary parameterizing type acmelib_gen_opaque(AddressFamily_u) acmelib_gen_opaque(SocketType_u) acmelib_gen_opaque(Protocol_u) typedef true_typedef< int , AddressFamily_u> AddressFamily; typedef true_typedef< int , SocketType_u> SocketType; typedef true_typedef< int , Protocol_u> Protocol; The opaque type generator macro Now when we attempt to construct a We can now also overload on logical type, irrespective of any commonality of the underlying types, so the You're probably asking how easy True Typedefs are to use. By looking at the template definition given earlier, it is clear that to access the value we need to call ## Listing 18.7.template< typename T, typename U> true_typedef<T, U> const operator ++(true_typedef<T, U> &v, int) { true_typedef<T, U> r(v); v.base_type_value()++; return r; } template< typename T, typename U> bool operator <=( true_typedef<T, U> const &lhs, T const &rhs) { return lhs.base_type_value() <= rhs; } template< typename T, typename U> true_typedef<T, U> operator ~(true_typedef<T, U> const &v) { return true_typedef<T, U>(~v.base_type_value()); } template< typename T, typename U> true_typedef<T, U> const &operator <<=( true_typedef<T, U> &v , true_typedef<T, U> const &rhs) { v.base_type_value() <<= rhs.base_type_value(); return v; } This means that instances of True Typedefs that are based on fundamental types can be used in almost all expressions that their fundamental types can. For example, true_typedef<int, . . .> i1 = 1000; int i2 = 1001; true_typedef<int, . . .> i3 = i2; i1 <<= 2; i1 = ~i3; i3 = i2 + i3; Naturally this is not possible for class types, unless they have those operators defined, but access to the base type value is pretty straightforward, via Both True Typedefs can also be used for underlying types other than the fundamental types, including class types. typedef true_typedef<std::string, . . .> Forename; typedef true_typedef<std::string, . . .> Surname; bool lookup_programmer( Forename const &fn , Surname const &sn , int &iq); Forename fn("Archie"); Surname sn("Goodwin"); int iq; fn = sn; // Error – types are different if(lookup_programmer(sn, fn, iq)) // Error: fn and sn reversed { printf("%s %s: %d\n", sn.base_type_value().c_str() , sn.base_type_value().c_str() , iq); // and would be disappointing if it did work ... } The True Typedefs concept completely answers the problems of weak conceptual type definitions: It prevents types with identical base types from being implicitly interconvertible, and it facilitates overloading of logically distinct types. Furthermore, it does this without sacrificing any efficiency, since the implementation is light, and all methods are inline. (I've not yet found a compiler that generates any differences in performance between True Typedef code and the plain typedef equivalents. This is so even for nontrivial base types.) Let's now have a recap of the problem of implicit integer conversion in the serialization component in section 13.2.1. Using True Typedefs, we can rewrite the ## Listing 18.8.// serialdefs.h acmelib_gen_opaque(sint8_u) acmelib_gen_opaque(uint8_u) acmelib_gen_opaque(sint16_u) acmelib_gen_opaque(uint16_u) acmelib_gen_opaque(sint32_u) acmelib_gen_opaque(uint32_u) acmelib_gen_opaque(sint64_u) acmelib_gen_opaque(uint64_u) typedef true_typedef<int8_t, sint8_u> sint8_type; typedef true_typedef<uint8_t, uint8_u> uint8_type; typedef true_typedef<int16_t, sint16_u> sint16_type; typedef true_typedef<uint16_t, uint16_u> uint16_type; typedef true_typedef<int32_t, sint32_u> sint32_type; typedef true_typedef<uint32_t, uint32_u> uint32_type; typedef true_typedef<int64_t, sint64_u> sint64_type; typedef true_typedef<uint64_t, uint64_u> uint64_type; // Serializer.h class Serializer { . . . // Operations public: void Write(sint8_type i); void Write(uint8_type i); void Write(sint16_type i); void Write(uint16_type i); void Write(sint32_type i); void Write(uint32_type i); void Write(sint64_type i); void Write(uint64_type i); // No need to define any other (proscribed) methods . . . }; void fn() { Serializer s; sint8_type i8(0); // Must use initialisation syntax ... uint64_type ui64(0); // ... rather than assignment syntax. int i = 0; s.Write(si8); s.Write(ui64); s.Write(i); // Error, plain and simple – no ambiguity s.Write(0); // ERROR: Ambiguous call uint64_t ui = ui64.base_type_value(); // Must use method } Now we have 100 percent type enforcement. Admittedly using the integer true typedefs is a bit inconvenient—in that you have to use initialization syntax to construct one from a base type variable or literal, and |