I l@ve RuBoard |
![]() ![]() |
2.4 Mapping Integral Constants to TypesA simple template, initially described in Alexandrescu (2000b), can be very helpful to many generic programming idioms. Here it is: template <int v> struct Int2Type { enum { value = v }; }; Int2Type generates a distinct type for each distinct constant integral value passed. This is because different template instantiations are distinct types; thus, Int2Type<0> is different from Int2Type<1>, and so on. In addition, the value that generates the type is "saved" in the enum member value. You can use Int2Type whenever you need to "typify" an integral constant quickly. This way you can select different functions, depending on the result of a compile-time calculation. Effectively, you achieve static dispatching on a constant integral value. Typically, you use Int2Type when both these conditions are satisfied:
For dispatching at runtime, you can use simple if-else statements or the switch statement. The runtime cost is negligible in most cases. However, you often cannot do this. The if-else statement requires both branches to compile successfully, even when the condition tested by if is known at compile time. Confused? Read on. Consider this situation: You design a generic container NiftyContainer, which is templated by the type contained: template <class T> class NiftyContainer { ... }; Say NiftyContainer contains pointers to objects of type T. To duplicate an object contained in NiftyContainer, you want to call either its copy constructor (for nonpolymorphic types) or a Clone() virtual function (for polymorphic types). You get this information from the user in the form of a Boolean template parameter. template <typename T, bool isPolymorphic> class NiftyContainer { ... void DoSomething() { T* pSomeObj = ...; if (isPolymorphic) { T* pNewObj = pSomeObj->Clone(); ... polymorphic algorithm ... } else { T* pNewObj = new T(*pSomeObj); ... nonpolymorphic algorithm ... } } }; The problem is that the compiler won't let you get away with this code. For example, because the polymorphic algorithm uses pObj->Clone(), NiftyContainer::DoSomething does not compile for any type that doesn't define a member function Clone(). True, it is obvious at compile time which branch of the if statement executes. However, that doesn't matter to the compiler—it diligently tries to compile both branches, even if the optimizer will later eliminate the dead code. If you try to call DoSomething for NiftyContainer<int,false>, the compiler stops at the pObj->Clone() call and says, "Huh?" It is also possible for the nonpolymorphic branch of the code to fail to compile. If T is a polymorphic type and the nonpolymorphic code branch attempts new T(*pObj), the code might fail to compile. This might happen if T has disabled its copy constructor (by making it private), as a well-behaved polymorphic class should. It would be nice if the compiler didn't bother about compiling code that's dead anyway, but that's not the case. So what would be a satisfactory solution? As it turns out, there are a number of solutions, and Int2Type provides a particularly clean one. It can transform ad hoc the Boolean value isPolymorphic into two distinct types corresponding to isPolymorphic's true and false values. Then you can use Int2Type<isPolymorphic> with simple overloading, and voilà! template <typename T, bool isPolymorphic> class NiftyContainer { private: void DoSomething(T* pObj, Int2Type<true>) { T* pNewObj = pObj->Clone(); ... polymorphic algorithm ... } void DoSomething(T* pObj, Int2Type<false>) { T* pNewObj = new T(*pObj); ... nonpolymorphic algorithm ... } public: void DoSomething(T* pObj) { DoSomething(pObj, Int2Type<isPolymorphic>()); } }; Int2Type comes in very handy as a means to translate a value into a type. You then pass a temporary variable of that type to an overloaded function. The overloads implement the two needed algorithms. The trick works because the compiler does not compile template functions that are never used—it only checks their syntax. And, of course, you usually perform dispatch at compile time in template code. You will see Int2Type at work in several places in Loki, notably in Chapter 11, Multimethods. There, the template class is a double-dispatch engine, and the bool template parameter provides the option of supporting symmetric dispatch or not. |
I l@ve RuBoard |
![]() ![]() |