Sutter's Mill: Befriending Templates

Let's say we have a function template that does SomethingPrivate() to the ob
jects it operates on. In particular, consider the boost::checked_delete() fu
nction template, which deletes the object it's given — among other things,
it invokes the object's destructor:
namespace boost {
  template<typename T> void checked_delete( T* x ) {
牋?// ... other stuff ...
    delete x;
  }
}
Now, say you want to use this function template with a class where the opera
tion in question (here the destructor) happens to be private:
class Test {
  ~Test() { }               // private!
};
Test* t = new Test;
boost::checked_delete( t ); // ERROR:
  // Test's destructor is private,
  // so checked_delete can't call it.
The solution is simple: just make checked_delete() a friend of Test. (The on
ly other option is to give up and make Test's destructor public.) What could
 be easier? And indeed, in the Standard C++ language there are two legal and
 easy ways to do it.
This article exists as a reality check: befriending a template in another na
mespace is easier said (in the Standard) than done (using real-world compile
rs that don't quite get the Standard right).
In sum, I have some good news, some bad news, and then some good news again:

The Good News: there are two perfectly good standards-conforming ways to do
it, and the syntax is natural and unsurprising.
The Bad News: neither standard syntax works on all current compilers. Even s
ome of the strongest and most-conformant compilers don't let you write one o
r both of the legal, sanctioned, standards-conforming, and low-cholesterol m
ethods that you should be able to use.
The Good News (reprise): one of the perfectly good standards-conforming ways
 does work on every current compiler I tried except for gcc.
Let's investigate.
The Original Attempt
This article was prompted by a question on Usenet by Stephan Born, who wante
d to do the above. His problem was that, when he tried to write the friend d
eclaration to make a specialization of boost::checked_delete() a friend of h
is class Test, the code wouldn't work on his compiler (Microsoft Visual C++
6.0).
Here's his original code:
// Example 1: One way to grant friendship
//
class Test {
  ~Test() { }
  friend void boost::checked_delete( Test* x );
};
Alas, not only does this code not work on the poster's compiler, it in fact
fails on quite a few compilers. In brief, Example 1's friend declaration:
Is legal according to the Standard, but relies on a dark corner of the langu
age.
Is rejected by many current compilers, including very good ones.
Is easily fixed to not rely on dark corners and work on all but one current
compiler (gcc).
I am about to delve into explaining the four ways that the C++ language lets
 you declare friends. It's easy. I'm also going to have some fun showing you
 what real compilers do and finish with a guideline for how to write the mos
t portable code.
Why It's Legal but Dark
When declaring friends, there are four options (enumerated in the C++ Standa
rd, clause 14.5.3). They boil down to the following.
When you declare a friend without saying the keyword template anywhere:
IF the name of the friend looks like the name of a template specialization w
ith explicit template arguments (e.g., Name<SomeType>)
THEN the friend is the indicated specialization of that template.
ELSE IF the name of the friend is qualified with a class or namespace name (
e.g., Some::Name)
AND that class or namespace contains a matching non-template function
THEN the friend is that function.
ELSE IF the name of the friend is qualified with a class or namespace name (
e.g., Some::Name)
AND that class or namespace contains a matching function template (deducing
appropriate template parameters)
THEN the friend is that function template specialization.
ELSE the name must be unqualified and declare (or redeclare) an ordinary (no
n-template) function.
Clearly #2 and #4 only match non-templates, so to declare the template speci
alization as a friend we have two choices: write something that puts us into
 bucket #1, or write something that puts us into bucket #3. In our example,
the options are:
// The original code, legal because it
// falls into bucket #3
//
friend void boost::checked_delete( Test* x );
or
// Adding "<Test>", legal because it
// falls into bucket #1
//
friend void boost::checked_delete<Test>( Test* x );
The first is shorthand for the second ... but only if the name is qualified
(here by boost::), and there's no matching non-template function in the same
 indicated scope. Even though both are legal, the first makes use of a dark
corner of the friend declaration rules that is sufficiently surprising to pe
ople — and to most current compilers! — that I will propose no fewer than
three reasons to avoid using it.
Why To Avoid Bucket #3
There are several reasons to avoid bucket #3, even though it's technically l
egal:
1. Bucket #3 doesn't always work.
As noted above, it's a shorthand for explicitly naming the template argument
s in angle brackets, but the shorthand works only if the name is qualified a
nd the indicated class or namespace does not also contain a matching non-tem
plate function.
In particular, if the namespace has (or later gets) a matching non-template
function, that would get chosen instead because the presence of a non-templa
te function means bucket #2 preempts #3. Kind of subtle and surprising, isn'
t it? Kind of easy to mistake, isn't it? Let's avoid such subtleties.
2. Bucket #3 is a really edgy case, fragile and surprising to most people re
ading your code.
For example, consider this very slight variant — all that I've changed is t
o remove the qualification boost::.
// Variant: Make the name unqualified,
// and it means something very different
//
class Test {
  ~Test() { }
  friend void checked_delete( Test* x );
};
If you omit boost:: (i.e., if the call is unqualified), you fall into a comp
letely different bucket, namely #4, which cannot match a function template a
t all, ever, not even with pretty please. I'll bet you dollars to donuts tha
t just about everyone on our beautiful planet will agree with me that it's P
retty Surprising that just omitting a namespace name changes the meaning of
the friend declaration so drastically. Let's avoid such edgy constructs.
3. Bucket #3 is a really edgy case, fragile and surprising to most compilers
 reading your code.
Let's try the two options, bucket #1 and bucket #3, on a wide range of curre
nt compilers and see what they think. Will the compilers understand the Stan
dard as well as we do (having read the above)? Will at least all the stronge
st compilers do what we expect? No, and no, respectively.
Let's try bucket #3 first:
// Example 1 again
//
namespace boost {
  template<typename T> void checked_delete( T* x ) {
    // ... other stuff ...
    delete x;
  }
}
class Test {
  ~Test() { }
  friend void boost::checked_delete( Test* x ); // the original code
};
int main() {
  boost::checked_delete( new Test );
}
Try the above on your own compiler and then compare with our results. If you
've ever watched the game show "Family Feud" on television, you can now imag
ine Richard Dawson's voice saying: "Survey saaaaays" (see Table 1).
In this case, the survey says that this syntax is not well recognized on act
ual compilers. By the way, it shouldn't surprise us that Comeau, EDG, and In
tel all agree, because they're all based on the EDG C++ language implementat
ion; of the five distinct C++ language implementations tested here, three do
n't accept this version (gcc, Metrowerks, Microsoft) and two do (Borland, ED
G).
Let's try writing it the other standards-conforming way, for bucket #1:
// Example 2: The other way to declare friendship
//
namespace boost {
  template<typename T> void checked_delete( T* x ) {
    // ... other stuff ...
    delete x;
  }
}
class Test {
  ~Test() { }
  friend void boost::checked_delete<>( Test* x );
};
int main() {
  boost::checked_delete( new Test );
}
Or, equivalently, we could have spelled out:
  friend void boost::checked_delete<Test>( Test* x );
Either way, when we twist our compilers' tails, our survey says that this is
 noticeably better supported (see Table 2).
Bucket #1 sure feels safer — Example 2 works on every current compiler exce
pt gcc, and every older compiler except Microsoft Visual C++ 6.0.
Aside: It's the Namespace That's Confusing Them
Note that if the function template we're trying to befriend wasn't in a diff
erent namespace, then we could use bucket #1 correctly today on nearly all t
hese compilers:
// Example 3: If only checked_delete
// weren't in a namespace...
//
// No longer in boost::
template<typename T> void checked_delete( T* x ) {
  // ... other stuff ...
  delete x;
}
class Test {
  // No longer need "boost:"
  friend void checked_delete<Test>( Test* x );
};
int main() {
  checked_delete( new Test );
}
Survey says ... (see Table 3). So the problem on most compilers that can't h
andle Example 1 is specifically declaring friendship for a function template
 specialization in another namespace. (Whew. Say that three times fast.) Ala
s, the poster's compiler, Microsoft Visual C++ 6.0, can't handle even this s
impler case.
Two Non-Workarounds
When this question arose on Usenet, some responses suggested writing a using
-declaration (or equivalently a using-directive) and making the friend decla
ration unqualified:
namespace boost {
  template<typename T> void checked_delete( T* x ) {
    // ... other stuff ...
    delete x;
 }
}
using boost::checked_delete;
class Test {
  ~Test() { }
  // NOT the template specialization!
  friend void checked_delete( Test* x );
};
The above friend declaration falls into bucket #4 above: "4. ELSE the name m
ust be unqualified and declare (or redeclare) an ordinary (non-template) fun
ction." This is actually declaring a brand-new ordinary non-template functio
n at the enclosing namespace scope called ::checked_delete( Test * ).
If you try the above code, many of these compilers will reject it saying tha
t checked_delete() hasn't been defined, and all of them will reject it if yo
u actually try to make use of the friendship and put a private member access
 call into the boost::checked_delete() template.
Finally, one expert suggested changing it slightly — using the "using" but
also using the template syntax "<>":
namespace boost {
  template<typename T> void checked_delete( T* x ) {
    // ... other stuff ...
    delete x;
 }
}
using boost::checked_delete;
class Test {
  ~Test() { }
  friend void checked_delete<>( Test* x ); // legal?
};
The above is probably not legal C++ — the Standard is not clear that this i
s legal, there's an open issue in the standards committee to decide whether
or not this ought to be legal, there is sentiment that it should not be lega
l, and in the real world virtually all current compilers that I tried reject
 it. Why do people feel that it should not be legal? For consistency, becaus
e using exists to make it easier to use names — to call functions and to us
e type names in variable or parameter declarations. Declarations are differe
nt: just as you must declare a template specialization in the template's ori
ginal namespace (you can't do it in another namespace "through a using"), so
 you should only be able to declare a template specialization as a friend na
ming the template's original namespace (not "through a using").
Summary
To befriend a function template specialization, you can choose one of two sy
ntaxes:
  // From Example 1
  friend void boost::checked_delete ( Test* x );
  // From Example 2: add <> or <Test>
  friend void boost::checked_delete<>( Test* x );
This article has demonstrated a pretty high portability price to pay in prac
tice for not just writing "<>" or "<Test>" as in Example 2.
Guideline: Say what you mean, be explicit. When you befriend a function temp
late specialization, always explicitly add at least the "<>" template syntax
. For example:
namespace boost {
  template<typename T> void checked_delete( T* x );
}
class Test {
  friend void boost::checked_delete ( Test* x ); // BAD
  friend void boost::checked_delete<>( Test* x ); // GOOD
};
If your compiler doesn't allow either of these legal alternatives for the fr
iend declaration yet, however, you'll have to make the necessary function(s)
 public — but add a comment saying why and make a note to change it back to
 private as soon as you upgrade your compiler [1].
Acknowledgment
Thanks to John Potter for comments on drafts of this material.
Note
[1] There are other workarounds, but they're all much more cumbersome. For e
xample, you could create a proxy class inside namespace boost and befriend t
hat.
About the Author
Herb Sutter (<www.gotw.ca>) is convener of the ISO C++ standards committee,
author of the acclaimed books Exceptional C++ and More Exceptional C++, and
one of the instructors of The C++ Seminar (<www.gotw.ca/cpp_seminar>). In ad
dition to his independent writing and consulting, he is also C++ community l
iaison for Microsoft.
Copyright ?2002 CMP Media LLC, Privacy Policy
Site comments: webmaster@cuj.com
SDMG Web Sites: Byte.com, C/C++ Users Journal, Dr. Dobb's Journal, MSDN Maga
zine, New Architect, SD Expo, SD Magazine, Sys Admin, The Perl Journal, Unix
Review.com, Windows Developer Network

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值