Friend lookup exception from template-id?

262 views Asked by At

Consider the following clause in [namespace.memdef]/3:

If the name in a friend declaration is neither qualified nor a template-id and the declaration is a function or an elaborated-type-specifier, the lookup to determine whether the entity has been previously declared shall not consider any scopes outside the innermost enclosing namespace.

Is there a reason for the exception for template-id along with the qualified name? For that matter, is there a reason for the lookup of an unqualified name that isn't a template-id to be restricted to the innermost enclosing namespace? Is there a specific problem or use-case that this clause solves?

1

There are 1 answers

1
dyp On

Why doesn't the restriction apply to qualified names and template-ids?

Qualified names and template-ids cannot introduce new members into the enclosing namespace, this is what the note in [namespace.memdef]p3 tries to say:

[ Note: The other forms of friend declarations cannot declare a new member of the innermost enclosing namespace and thus follow the usual lookup rules. — end note ]

Therefore, no such restriction is necessary for qualified names and template-ids.

Note that template-ids lack the declaration of the template-parameters, and qualified-ids could introduce names into distant, unrelated namespaces.


Why is there a restriction at all?

This part of the answer is still incomplete, but represents the current state of "research". Feel free to contribute.

The restriction has (probably?) been introduced due to N0783 - Namespace Issues and Proposed Resolutions which "attempts to clarify a number of namespace issues that are currently either undefined or incompletely specified".

This paper from 1995 contains two enlightening discussions of issues related to declarations of entities introduced via friend-declarations. Bear in mind that the name lookup rules back then were different:

  • Argument-dependent lookup had not yet been introduced(*)
  • Names introduced via friend-declarations are not found via pure unqualified lookup (no ADL) according to current rules, see [namespace.memdef]p3 and CWG 1477. The examples from N0878 suggest that those names could be found via pure unqualified lookup at that time.

(*) The best I could find was N0878 from March 1996, which says "A change was recently made to the working paper to add the “Koenig lookup rule”"

First, the example from N0783 for functions:

void f(char);

namespace A {
    class B {
        friend void f(char);   // ::f(char) is a friend
        friend void f(int);    // A::f(int) is a friend

        void bf();
    };
    void B::bf()
    {
        f(1);  // calls A::f(int);
        f('x');  // also calls A::f(int) because ::f is hidden
    }
}

The second friend declaration must introduce a new function. N0783 tries to specify in which scope this declaration is introduced. It suggests

All friend declarations for a given name must declare entities in one particular scope.

as a general rule, to avoid the surprises of situations such as the above.

So the question is, which scope do they declare entities in? There are two possibilities, either

  1. When looking for a previous declaration of the function, look until the nearest enclosing namespace is reached, or
  2. When looking for a previous declaration, look in all enclosing scopes for the name of the function that was declared. If a previous use of the name is found, the declaration is injected into that scope. If no previous use of the name is found the friend is injected into the nearest enclosing namespace scope.

Rule #2 would mean that the presence of any function called f in an enclosing scope, whether or not the types match, would be enough to cause a friend declaration to inject into that scope.

I believe that rule #2 is clearly unacceptable. A friend declaration in a namespace would be affected by any global declaration of that name. Consider what this would mean for operator functions! The presence of any operator+ function in the global scope would force all friend operator+ operators to appear in the global scope too! The presence of a template in the global scope would have the same effect.

For class types:

namespace N {
    class A { void f(); };
}

using namespace N;

namespace M {
    class B {
        friend class A;  // Without this rule
                         // makes N::A a friend
        B();
    };
    class A { void f(); };
}

void N::A::f() { M::B  b; }  // A friend under current rules

void M::A::f() { M::B  b; }  // A friend under proposed rules

Both examples are not as interesting under the current rules because names introduced via friend declarations are only found via ADL. It is possible this restriction is a historical artefact. More "research" is required to follow the development of this restriction after the introduction of ADL.