The Barton-Nackman Trick





The Barton-Nackman Trick

In 1994, John J. Barton and Lee R. Nackman presented a template technique that they called restricted template expansion. The technique was motivated in part by the fact that—at the time— function templates could not be overloaded [3] and namespaces were not available in most compilers.

[3] It may be worthwhile to read Section 12.2 on page 183 to understand how function template overloading works in modern C++.

To illustrate this, suppose we have a class template Array for which we want to define the equality operator ==. One possibility is to declare the operator as a member of the class template, but this is not good practice because the first argument (binding to the this pointer) is subject to conversion rules that are different from the second argument. Because operator == is meant to be symmetrical with respect to its arguments, it is preferable to declare it as a namespace scope function. An outline of a natural approach to its implementation may look like the following:

template<typename T> 
class Array { 
  public: 
     
}; 

template<typename T> 
bool operator == (Array<T> const& a, Array<T> const& b) 
{ 
     
} 

However, if function templates cannot be overloaded, this presents a problem: No other operator == template can be declared in that scope, and yet it is likely that such a template would be needed for other class templates. Barton and Nackman resolved this problem by defining the operator in the class as a normal friend function:

template<typename T> 
class Array { 
  public: 
     
    friend bool operator == (Array<T> const& a, 
                             Array<T> const& b) { 
        return ArraysAreEqual(a, b); 
    } 
}; 

Suppose this version of Array is instantiated for type float. The friend operator function is then declared as a result of that instantiation, but note that this function itself is not an instantiation of a function template. It is a normal nontemplate function that gets injected in the global scope as a side effect of the instantiation process. Because it is a nontemplate function, it could be overloaded with other declarations of operator == even before overloading of function templates was added to the language. Barton and Nackman referred to this as restricted template expansion because it avoided the use of a template operator==(T, T) that applied to all types T (in other words, unrestricted expansion).

Because operator == (Array<T> const&, Array<T> const&) is defined inside a class definition, it is implicitly considered to be an inline function, and we therefore decided to delegate the implementation to a function template ArraysAreEqual, which doesn't need to be inline and is unlikely to conflict with another template of the same name.

The Barton-Nackman trick is no longer needed for its original purpose, but it is interesting to study it because it allows us to generate nontemplate functions along with class template instantiations. Because the functions are not generated from function templates, they do not require template argument deduction but are subject to normal overload resolution rules (see Appendix B). In theory, this could mean that additional implicit conversions may be considered when matching the friend function to a specific call site. However, this is of relatively little benefit because in standard C++ (unlike the language at the time Barton and Nackman came up with their idea), the injected friend function is not unconditionally visible in the surrounding scope: It is visible only through ADL. This means that the arguments of the function call must already have the class containing the friend function as an associated class. The friend function would not be found if the arguments were of an unrelated class type that could be converted to the class containing the friend. For example:

class S { 
}; 

template<typename T> 
class Wrapper { 
  private: 
    T object; 
  public: 
    Wrapper(T obj) : object(obj) {  // implicit conversion from 
                                    // T to Wrapper<T> 
    } 
    friend void f(Wrapper<T> const& a) { 
    } 
}; 

int main() 
{ 
    Ss; 
    Wrapper<S> w(s); 
    f(w);  // OK: Wrapper<S> is a class associated with w 
    f(s);  // ERROR: Wrapper<S> is not associated with s 
} 

In this example, the call f(w) is valid because the function f() is a friend declared in Wrapper<S> which is a class associated with the argument w. [4] However, in the call f(s) the friend declaration of function f(Wrapper<S> const&) is not visible because the class Wrapper<S> in which it is defined is not associated with the argument s of type S. Hence, even though there is a valid implicit conversion from type S to type Wrapper<S> (through the constructor of Wrapper<S>), this conversion is never considered because the candidate function f is not found in the first place.

[4] Note that S is also a class associated with w because it is a template argument for the type of w.

In conclusion, there is little advantage to define a friend function in a class template over simply defining an ordinary function template.


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows