Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

How to Provide (or Avoid) Points of Customization in Templates


November, 2004: How to Provide (or Avoid) Points of Customization in Templates

Herb Sutter (http://www.gotw.ca/) chairs the ISO C++ Standards committee and is a software architect at Microsoft, where he is responsible for designing C++ language extensions for .NET programming (C++/CLI). His two most recent books are Exceptional C++ Style and C++ Coding Standards.


This column distills a key issue and guidelines from our community's experience with writing and using template libraries (see [1], [2], and [3]). The upshot of this column is pretty simple:

  • When writing a template, provide points of customization knowingly and correctly, and document them clearly.
  • When using a template, know how the template intends for you to customize it for use with your type, and customize it appropriately.

A common problem when writing template libraries is providing unintentional points of customization—that is, points where a caller's code can get "injected" (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 34 of [2] for a particularly gory one.

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 code a customization hook in places where you didn't mean to.

Option 1: Member Function

The first way to provide a point of customization is the usual "implicit interface" approach, where your template simply relies on a type 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 ) {
  // foo is a point of customization
  t.foo();

  // another example: providing a point 
  // of customization to look up a type
  // (usually via typedef)
  typename T::value_type x;
}

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: Document that the type must provide an accessible member function foo that can be called with a given signature (here, with no arguments).

Option 1 has the advantage that users can write the customization function once for their 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.

Option 2: Nonmember Function + ADL

The second option is to use the "implicit interface" method, but with a nonmember function that is looked up via argument-dependent lookup (ADL; that is, 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, as covered in detail in Items 31-34 of [2]. Your template is relying on a type 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 
	 // notation 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 that 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 (for example, 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: Document that the type must provide a nonmember function foo callable with a given signature (here, with no arguments).

Option 2 has similar advantages and applicability as Option 1: Users only need to write the customization function once, thus avoiding writing lots of little adapters for each template library, but the semantics should be reasonably broadly applicable so as to make sense for all those potential uses.

Option 3: Specialization (of Class Templates)

The third option is to use specialization, so that your template is relying on a type 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 ) {
  // S3Traits<>::foo is a point of customization
  typename S3Traits<T>::foo( t );

  // another example: providing a point of
  // customization to look up a type
  // (usually via typedef)
  typename S3Traits<T>::value_type x;
}

In Option 3, making users 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, users have 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.
  • Document the point of customization: Document that users must specialize S3Traits in the template library's namespace for their own type, and document all of S3Traits's members (foo, for example) and their semantics.

Interlude: Some Guidance

Under all options, always clearly document 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 also be customizable for built-in types, use Option 2 or Option 3.

Prefer Option 1 or Option 2 for common operations that are really 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.

Avoid the Pitfall of Unintentional Customization

Earlier, I said that we should program intentionally. Part of that, discussed already, is to know which of the three techniques you're using for a particular point of customization, and to implement it consciously and correctly.

The flip side, however, is to avoid providing points of customization unintentionally, and that's harder because the code you write will, by default, provide a point of customization. In particular, an unqualified function call such as f(x) or a natural use of an operator such as x < y is by default in Option 2 land if any of the arguments' types is related to the template parameter type, and will reach out via ADL into the namespaces of those types and pull in any user-defined functions of that name that might by lying around over there (again, see Item 34 of [2] for a detailed example).

So you have to do something special to turn off such on-by-default points of customization. Here's how:

  • 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 (for instance, 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 ) {
	  // qualification disables ADL:
	  // foo is NOT a point of customization
	  S4Helpers::bar( t );
	
	  // alternative, in conjunction with a
	  // "using namespace S4Helpers;"
	  (bar)( t );
	}

  • 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 (for example, T in the case of template<typename T> class C : T { }; ) or from a type that is built up from one of its template parameters (for instance, X<T> in the case of 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 as just a magical way of forcing all compilers to do what you actually meant:

template<typename T>
class C : X<T> {
  // use base's nested type or typedef
  typename X<T>::SomeType s;
public:
  void f() {
    X<T>::baz();	// call base member function
    this->baz();	// alternative
  }
};

What the C++ Standard Library Does (and Doesn't Do)

The C++ Standard Library generally favors relying on Option 2; for example, ostream_iterators look up operator<<, and accumulate looks up operator+, in your type's namespace. It also uses Option 3 in some places, in particular for traits such as iterator_traits and char_traits 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 going back to fix the current inadequate specification by removing such fuzziness as part of its work on C++0x, the first major revision of the Standard now underway. We know better now. Learn from the past. Don't make the same mistakes, and you will enjoy a robust and predictable experience with C++'s powerful template libraries.

Acknowledgments

Thanks to Dave Abrahams and Howard Hinnant in particular, and the Library Working Group in general, for making this issue visible and driving the work to tighten up the specification of the C++ Standard Library in this area. This article is drawn from the material in Item 65, "Customize intentionally and explicitly," in the new book C++ Coding Standards, by Herb Sutter and Andrei Alexandrescu.

References

  1. [1] Stroustrup, B. The C++ Programming Language, Special Third Edition, Addison-Wesley, 2000. See sections 8.2, 10.3.2, and 11.2.4.
  2. [2] Sutter, H. Exceptional C++, Addison-Wesley, 2000. See Items 31-34.
  3. [3] Sutter, H. More Exceptional C++, Addison-Wesley, 2002. See Items 39-40.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.