Discussion
Team LiB
Previous Section Next Section

Discussion

A common pitfall when writing template libraries is providing unintentional points of customizationthat is, points where a caller's code can get looked up and used inside your template, but you didn't mean for a caller's code to get involved. It's easy to do: Just call another function or operator the normal way (unqualified), and if one of its arguments happens to be of a template parameter type (or a related type) then ADL will pick it up. Examples abound: See Item 58 for an example.

Instead, be intentional: Know the three major ways to provide points of customization in a template, decide which one you want to use at a given point in your template, and code it correctly. Then, check to verify that that you didn't accidentally also code a customization hook in places where you didn't mean to.

The first way to provide a point of customization is the usual "implicit interface" (see Item 64) approach where your template simply relies on a type's having an appropriate member with a given name:



// Option 1: Provide a point of customization by requiring T to provide "foo-ability"


// as a member function with a given name, signature, and semantics.





template<typename T>


void Sample1( T t ) {


 t.foo();                  // foo is a point of customization


 typename T::value_type x; // another example: providing a point of custom-


}                          //   ization to look up a type (usually via typedef)



To implement Option 1, the author of Sample1 must:

  • Call the function with member notation: Just use the natural member syntax.

  • Document the point of customization: The type must provide an accessible member function foo that can be called with given arguments (here, none).

The second option is to use the "implicit interface" method, but with a nonmember function that is looked up via argument-dependent lookup (i.e., it is expected to be in the namespace of the type with which the template is instantiated); this is a major motivation for the language's ADL feature (see Item 57). Your template is relying on a type's having an appropriate nonmember with a given name:



// Option 2: Provide a point of customization by requiring T to provide "foo-ability"


// as a nonmember function, typically looked up by ADL, with a given name, signature,


// and semantics. (This is the only option that doesn't also work to look up a type.)





template<typename T>


void Sample2( T t ) {


  foo( t );            // foo is a point of customization





  cout << t;           // another example: operator<< with operator nota-


}                      //   tion is the same kind of point of customization



To implement Option 2, the author of Sample2 must:

  • Call the function with unqualified nonmember notation (including natural operator notation in the case of operators) and ensure the template itself doesn't have a member function with the same name: It is essential for the template not to qualify the call to foo (e.g., don't write SomeNamespace::foo( t )) or to have its own member function of the same name, because either of those would turn off ADL and thus prevent name lookup from finding the function in the namespace of the type T.

  • Document the point of customization: The type must provide a nonmember function foo that can be called with given arguments (here, none).

Options 1 and 2 have similar advantages and applicability: The user can write the customization function once for his type in a place where other template libraries could also pick it up, thus avoiding writing lots of little adapters, one for each template library. The corresponding drawback is that the semantics would have to be reasonably broadly applicable so as to make sense for all those potential uses. (Note that operators in particular fall into this category; this is another reason for Item 26.)

The third option is to use specialization, so that your template is relying on a type's having specialized (if necessary) another class template you provide:



// Option 3: Provide a point of customization by requiring T to provide "foo-ability"


// by specializing SampleTraits<> and provide a (typically static) function with a


// given name, signature, and semantics.





template<typename T>


void Sample3( T t ) {


  typename S3Traits<T>::foo( t );     // S3Traits<>::foo is a point of customization





  typename S3Traits<T>::value_type x; // another example: providing a point of custom-


}                                     //   ization to look up a type (usually via typedef)



In Option 3, making the user write an adapter ensures that custom code for this library is isolated inside this library. The corresponding drawback is that this can be cumbersome; if several template libraries need the same common functionality, the user has to write multiple adapters, one for each library.

To implement this option, the author of Sample3 must:

  • Provide a default class template in the template's own namespace: Don't use a function template, which can't be partially specialized and leads to overloads and order dependencies. (See also Item 66.)

  • Document the point of customization: The user must specialize S3Traits in the template library's namespace for his own type, and document all of S3Traits's members (e.g., foo) and their semantics.

Under all options, always clearly document also the semantics required of foo, notably any essential actions (postconditions) foo must guarantee, and failure semantics (what happens, including how errors are reported, if the actions don't succeed).

If the point of customization must be customizable also for built-in types, use Option 2 or Option 3.

Prefer Option 1 or Option 2 for common operations that really are services provided by the type. Here's a litmus test: Could other template libraries use this facility too? And are these generally accepted semantics for this name? If so, this option is probably appropriate.

Prefer Option 3 for less-common operations whose meaning can be expected to vary. You can then happily make the same names mean whatever you want in any given namespace, without confusion or collision.

A template with multiple points of customization can choose a different appropriate strategy for each point of customization. The point is that it must consciously choose and document exactly one strategy for each point of customization, document the requirements including expected postconditions and failure semantics, and implement the chosen strategy correctly.

To avoid providing points of customization unintentionally:

  • Put any helper functions your template uses internally into their own nested namespace, and call them with explicit qualification to disable ADL: When you want to call your own helper function and pass an object of the template parameter type, and that call should not be a point of customization (i.e., you always intend your helper to be called, not some other function), prefer to put the helper in a nested namespace and explicitly turn off ADL by qualifying the call or putting the function name in parentheses:

    
    
    template<typename T>
    
    
    void Sample4( T t ) {
    
    
      S4Helpers::bar( t );  // disables ADL: foo is not a point of customization
    
    
      (bar)( t );           // alternative
    
    
    }
    
    
    

  • Avoid depending on dependent names: Informally, a dependent name is a name that somehow mentions a template parameter. Many compilers do not support the "two-phase lookup" for dependent names mandated by the C++ Standard, and this means that template code that uses dependent names will behave differently on different compilers unless it takes care to be explicit when using dependent names. Particular care is required in the presence of dependent base classes, which occur when a class template inherits from one of its template parameters (e.g., T in the case template<typename T> class C : T {};) or from a type that is built up from one of its template parameters (e.g., X<T> in the case template<typename T> class C : X<T> {};).

In short, when referring to any member of a dependent base class, always explicitly qualify with the base class name or with this->, which you can think of just as a magical way of forcing all compilers to do what you actually meant:



template<typename T>


class C : X<T> {


 typename X<T>::SomeType s; // use base's nested type or typedef





public:


  void f() {


    X<T>::baz();             // call base member function


    this->baz();             // alternative


  }


};



The C++ standard library generally favors relying on Option 2 (e.g., <BB>ostream_iterator</BB>s look up <BB>operator<<</BB>, and <BB>accumulate</BB> looks up <BB>operator+</BB>, in your type's namespace). It also uses Option 3 in some places (e.g., <BB>iterator_traits, char_traits</BB>), particularly because those traits must be specializable for built-in types.

Note that, unfortunately, the C++ standard library fails to clearly specify the points of customization of some algorithms. For example, it clearly says that the three-parameter version of accumulate must call a user's operator+ using Option 2. But it doesn't say whether sort must call a user's swap (thereby providing an intentional point of customization using Option 2), whether it may call a user's swap, or whether it calls any swap at all; today, some implementations of sort do pull in a user-defined swap while others don't. This Item's point has only been learned relatively recently, and now the standards committee is fixing the current inadequate specification by removing such fuzziness from the standard. We know better now. Learn from the past. Don't make the same mistakes. (For more options, see Item 66.)

    Team LiB
    Previous Section Next Section