This is the mail archive of the gcc-bugs@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

c++/8355: befriending a template in another namespace


>Number:         8355
>Category:       c++
>Synopsis:       befriending a template in another namespace
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    unassigned
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Fri Oct 25 08:46:02 PDT 2002
>Closed-Date:
>Last-Modified:
>Originator:     Gabriel Zachmann (from comp.lang.c++.moderated)
>Release:        3.2 and before
>Organization:
>Environment:

>Description:
TITLE: befriending a template in another namespace

(Source: comp.lang.c++.moderated, 6 Oct 2002)

AUTHOR: Herb Sutter (www.gotw.ca)

There have been several answers to this question, but they're not quite
right. This is an interesting question, so let me try to treat in a little
more depth.


Befriending Templates
---------------------

Say we have a function template that does SomethingPrivate() to the objects
it operates on. In particular, consider the boost::checked_delete() function
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
operation in question (here the destructor) happens to be private:

   // Example 1: No friends
   //
   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 dead simple: Just make checked_delete() a friend of Test.
(The only other option is to give up and make Test's destructor public.)

The reason that I'm writing an article about it is because, alas,
befriending a template in another namespace is easier said than done:

   - The Bad News: There are two perfectly good standards-conforming
     ways to do it, and neither one works on all current compilers.

   - The Good News: One of the perfectly good standards-conforming ways
     does work on every current compiler I tried except for gcc.


The Original Attempt
--------------------

Here's the original code:

Stephan Born <stephan.born@bNeOuSsPeAnM.de> wrote [edited]:

   // Example 2: One way to grant friendship (?)
   //
   class Test {
     ~Test() { }
     friend void boost::checked_delete( Test* x );
   };

Alas, this code didn't work on the poster's compiler (VC++ 6.0). In fact, it
fails on quite a few compilers. In brief, Example 2's friend declaration:

   - is technically legal but relies on a dark corner of the language

   - 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)


Why It's Legal but Dark
-----------------------

When declaring friends, there are four options (enumerated in the C++
standard, clause 14.5.3). They boil down to this:

   When you declare a friend without saying the keyword "template" anywhere:

   1. IF the name of the friend looks like the name of a template
      specialization with explicit template arguments (e.g., Name<SomeType>)
        THEN the friend is the indicated specialization of that template

   2. 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

   3. 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

   4. ELSE the name must be unqualified and declare (or redeclare) an
      ordinary (non-template) function.

Clearly #2 and #4 only match nontemplates, so to declare the template
specialization 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 nontemplate function in the same
indicated scope. This dark corner of the friend declaration rules is
sufficiently surprising to people -- 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
legal:

1. Bucket #3 doesn't always work.

As noted above, it's a shorthand for explicitly naming the template
arguments in angle brackets, but the shorthand works only if the name is
qualified and the indicated class or namespace does not also contain a
matching nontemplate function.

In particular, if the namespace has (or later gets) a matching nontemplate
function, that would get chosen instead because the presence of a
nontemplate 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
    reading your code.

For example, consider this very slight variant -- all that I've changed is
to remove the qualification "boost":

   // Variant: Make the name unqualified
   //
   class Test {
     ~Test() { }
     friend void checked_delete( Test* x ); // OUCH: Legal, but not what you
   };                                       // want. More about this later.

If you omit "boost::" (i.e., if the call is unqualified), you fall into a
completely different bucket, namely #4 which cannot match a function
template at all, ever, not even with pretty please. I'll bet you dollars to
donuts that just about everyone on our beautiful planet will agree with me
that it's Pretty 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
current compilers and see what they think. Will the compilers understand the
standard as well as we do (having read the above)? Will at least all the
strongest compilers do what we expect? No, and no, respectively.

Let's try bucket #3 first:

   // Example 2 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
   };

   <richard-dawson-voice>
     Survey SAAAAAYS:
   </richard-dawson-voice>

     Borland 5.5     OK
     Comeau 4.3.01   OK
     EDG 3.0.1       OK
     gcc 2.95.3      ERROR  `boost::checked_delete(Test *)' should have
                            been declared inside `boost'
     gcc 3.1.1       ERROR  `void boost::checked_delete(Test*)' should
                            have been declared inside `boost'
     gcc 3.2         ERROR  `void boost::checked_delete(Test*)' should
                            have been declared inside `boost'
     Intel 6.0.1     OK
     Metrowerks 8.2  ERROR  friend void boost::checked_delete( Test* x );
                            name has not been declared in namespace/class
     MS VC++ 6.0     ERROR  nonexistent function 'boost::checked_delete'
                            specified as friend
     MS VC++ 7.0     OK
     MS VC++ 7.1beta ERROR  'boost::checked_delete' : not a function

For MS VC++ 6.0, the error is what the original poster reported. But you'll
get the same (or similar) error on some pretty strong and conformant
compilers, including Metrowerks 8.2, g++ 3.2, and MS VC++ 7.1. (All of these
versions were released, or went into beta, in the past month or two.)

By the way, it shouldn't surprise us that Comeau, EDG, and Intel all agree,
because they're all based on the EDG C++ language implementation. If we
collapse the list so we count only distinct compiler implementations (i.e.,
all of the ones that use EDG are the same code base), and take the latest of
each, it looks more like this:

     Borland              OK
     EDG-based compilers  OK
     gcc (all versions)   ERROR
     Metrowerks           ERROR
     MS VC++              ERROR

So, in short, most C++ language implementations don't accept this version.

Let's try writing it the other standards-conforming way, for bucket #1:

   // Example 3: 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>( Test* x );
   };

   <richard-dawson-voice>
     Survey SAAAAAYS:
   </richard-dawson-voice>

     Borland 5.5     OK
     Comeau 4.3.01   OK
     EDG 3.0.1       OK
     gcc 2.95.3      ERROR  `boost::checked_delete(Test *)' should have
                            been declared inside `boost'
     gcc 3.1.1       ERROR  `void boost::checked_delete(Test*)' should
                            have been declared inside `boost'
     gcc 3.2         ERROR  `void boost::checked_delete(Test*)' should
                            have been declared inside `boost'
     Intel 6.0.1     OK
     Metrowerks 8.2  OK
     MS VC++ 6.0     ERROR  nonexistent function 'boost::checked_delete'
                            specified as friend
     MS VC++ 7.0     OK
     MS VC++ 7.1beta OK

If we collapse the list so that we count only distinct compiler
implementations (i.e., all of the ones that use EDG are the same code base),
and take the latest of each, it looks more like this:

     Borland              OK
     EDG-based compilers  OK
     gcc (all versions)   ERROR
     Metrowerks           OK
     MS VC++ 7.0 & higher OK

Bucket #1 sure feels safer -- this works on every current compiler except
gcc, and every older compiler except MS VC++ 6.0. (This might not be the
most useful answer for the OP, who is using MS VC++ 6.0 which offers no way
to declare the friend; that is why the options for him are to make the
destructor public, or to add "<Test>" and use MS VC++ 7.x.)


Aside: It's the Namespace That's Confusing Them
-----------------------------------------------

Note that if the function template we're trying to befriend wasn't in a
different namespace, then we could use bucket #1 correctly today on all
these compilers:

   // Example 4: If only checked_delete weren't in a namespace...
   //
   template<typename T> void checked_delete( T* x ) { // no longer in
boost::
     // ... other stuff ...
     delete x;
   }

   class Test {
     friend void checked_delete<Test>( Test* x ); // no longer need
"boost::"
   };

   <richard-dawson-voice>
     Survey SAAAAAYS:
   </richard-dawson-voice>

     Borland 5.5     OK
     Comeau 4.3.01   OK
     EDG 3.0.1       OK
     gcc 2.95.3      OK
     gcc 3.1.1       OK
     gcc 3.2         OK
     Intel 6.0.1     OK
     Metrowerks 8.2  OK
     MS VC++ 6.0     ERROR  (emits a syntax error, just can't handle it)

[...]
>How-To-Repeat:

>Fix:

>Release-Note:
>Audit-Trail:
>Unformatted:


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]