friend declarations

307 views
Skip to first unread message

Belloc

unread,
Jun 25, 2015, 10:03:33 AM6/25/15
to std-dis...@isocpp.org
First, let's look at these two "unfriendly" notes:

[basic.scope.declarative]/4:

[ Note: These restrictions apply to the declarative region into which a name is introduced, which is not necessarily the same as the region in which the declaration occurs. In particular, elaborated-type-specifiers (7.1.6.3) and friend declarations (11.3) may introduce a (possibly not visible) name into an enclosing namespace; these restrictions apply to that region. Local extern declarations (3.5) may introduce a name into the declarative region where the declaration appears and also introduce a (possibly not visible) name into an enclosing namespace; these restrictions apply to both regions. — end note ]

[basic.scope.pdecl]/11:

[ Note: Friend declarations refer to functions or classes that are members of the nearest enclosing namespace, but they do not introduce new names into that namespace (7.3.1.2). Function declarations at block scope and variable declarations with the extern specifier at block scope refer to declarations that are members of an enclosing namespace, but they do not introduce new names into that scope. —end note ]

Now take a look at [namespace.memdef]/3:

Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class, function, class template or function template97 the friend is a member of the innermost enclosing namespace. The friend declaration does not by itself make the name visible to unqualified lookup (3.4.1) or qualified lookup (3.4.3). [ Note: The name of the friend will be visible in its namespace if a matching declaration is provided at namespace scope (either before or after the class definition granting friendship). — end note ] If a friend function or function template is called, its name may be found by the name lookup that considers functions from namespaces and classes associated with the types of the function arguments (3.4.2). 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.

The last highlighted sentence above is superfluous, as there is no lookup for the name declared in a friend declaration in this case. What would be the purpose of this lookup anyway?

The words first declared, highlighted above are also confusing to the reader, as they may give the impression that if a name is declared before the innermost enclosing namespace it will still be a friend of the class containing the friend declaration. For instance, consider the example below, obtained from Section 19.4.1 Finding Friends, in the new book, "The C++ Programming Language", 4th edition, by B.Stroustrup. The author was probably misled by these words, as the class C1 and the function f1 will not become friends of the class N::C. In my humble opinion. if the words "first declares" were replaced by the expression "first declares in its namespace", the sentence would be much clearer.

class C1 { }; // will become friend of N::C
void f1();    // will become friend of N::C
namespace N {
   
class C2 { }; // will become friend of C
   
void f2() { } // will become friend of C
   
class C {
       
int x;
   
public:
       
friend class C1; // OK (previously defined)
       
friend void f1();

       
friend class C3;
       
friend void f3();  // OK (defined in enclosing namespace)
       
friend class C4;   // First declared in N and assumed to be in N
       
friend void f4();
   
};

   
class C3 {};                // friend of C
   
void f3() { C x; x.x = 1; } // OK: friend of C
} // namespace N

class C4 { };                  // not friend of N::C
void f4() { N::C x; x.x = 1; } // error : x is private and f4() is not a friend of N::C


Ville Voutilainen

unread,
Jun 25, 2015, 10:19:05 AM6/25/15
to std-dis...@isocpp.org
On 25 June 2015 at 17:03, Belloc <jabe...@gmail.com> wrote:
> Now take a look at [namespace.memdef]/3:
> Every name first declared in a namespace is a member of that namespace. If a
> friend declaration in a non-local class first declares a class, function,
> class template or function template97 the friend is a member of the
> innermost enclosing namespace. The friend declaration does not by itself
> make the name visible to unqualified lookup (3.4.1) or qualified lookup
> (3.4.3). [ Note: The name of the friend will be visible in its namespace if
> a matching declaration is provided at namespace scope (either before or
> after the class definition granting friendship). — end note ] If a friend
> function or function template is called, its name may be found by the name
> lookup that considers functions from namespaces and classes associated with
> the types of the function arguments (3.4.2). 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.
>
> The last highlighted sentence above is superfluous, as there is no lookup
> for the name declared in a friend declaration in this case. What would be
> the purpose of this lookup anyway?

Huh? In order to find out whether a declaration is the first
declaration, lookup must
be done. For an unqualified identifier, that lookup is done just for
the enclosing
namespace, but a lookup must happen.

> The words first declared, highlighted above are also confusing to the
> reader, as they may give the impression that if a name is declared before
> the innermost enclosing namespace it will still be a friend of the class
> containing the friend declaration. For instance, consider the example below,

It can become a friend, if the friend declaration uses a qualified name.

> class C1 { }; // will become friend of N::C
> void f1(); // will become friend of N::C
> namespace N {
> class C {
> public:
> friend class C1; // OK (previously defined)
> friend void f1();

Well, yeah, those friend declarations would need to use a qualified
name in order
to befriend ::C1 and ::f1.

Johannes Schaub

unread,
Jun 25, 2015, 10:37:00 AM6/25/15
to std-dis...@isocpp.org


Am 25.06.2015 16:03 schrieb "Belloc" <jabe...@gmail.com>:
>
> First, let's look at these two "unfriendly" notes:
>
> [basic.scope.declarative]/4:
>
> [ Note: These restrictions apply to the declarative region into which a name is introduced, which is not necessarily the same as the region in which the declaration occurs. In particular, elaborated-type-specifiers (7.1.6.3) and friend declarations (11.3) may introduce a (possibly not visible) name into an enclosing namespace; these restrictions apply to that region. Local extern declarations (3.5) may introduce a name into the declarative region where the declaration appears and also introduce a (possibly not visible) name into an enclosing namespace; these restrictions apply to both regions. — end note ]
>
> [basic.scope.pdecl]/11:
>
> [ Note: Friend declarations refer to functions or classes that are members of the nearest enclosing namespace, but they do not introduce new names into that namespace (7.3.1.2). Function declarations at block scope and variable declarations with the extern specifier at block scope refer to declarations that are members of an enclosing namespace, but they do not introduce new names into that scope. —end note ]
>
> Now take a look at [namespace.memdef]/3:
>
> Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class, function, class template or function template97 the friend is a member of the innermost enclosing namespace. The friend declaration does not by itself make the name visible to unqualified lookup (3.4.1) or qualified lookup (3.4.3). [ Note: The name of the friend will be visible in its namespace if a matching declaration is provided at namespace scope (either before or after the class definition granting friendship). — end note ] If a friend function or function template is called, its name may be found by the name lookup that considers functions from namespaces and classes associated with the types of the function arguments (3.4.2). 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.
>
> The last highlighted sentence above is superfluous, as there is no lookup for the name declared in a friend declaration in this case. What would be the purpose of this lookup anyway?
>

The issue how what this means is already covered by issue 138: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#138

Also I remember there is an issue about the conflict of there being an invisible name or not... Can't find the number currently though.

Belloc

unread,
Jun 25, 2015, 10:37:26 AM6/25/15
to std-dis...@isocpp.org
On Thursday, June 25, 2015 at 11:19:05 AM UTC-3, Ville Voutilainen wrote:

>It can become a friend, if the friend declaration uses a qualified name.

 The highlighted sentence already excludes a qualified name. See below:

Ville Voutilainen

unread,
Jun 25, 2015, 10:42:23 AM6/25/15
to std-dis...@isocpp.org
On 25 June 2015 at 17:37, Belloc <jabe...@gmail.com> wrote:
>> >It can become a friend, if the friend declaration uses a qualified name.
> The highlighted sentence already excludes a qualified name. See below:
> "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."

It doesn't exclude a qualified name, it explains how the lookup is
done differently
if the name is not qualified. If the name is qualified, lookup is done
as usual for
a qualified name, but if the name is unqualified, normal unqualified lookup
is not performed, the lookup is restricted.

Belloc

unread,
Jun 25, 2015, 10:42:48 AM6/25/15
to std-dis...@isocpp.org
On Thursday, June 25, 2015 at 11:19:05 AM UTC-3, Ville Voutilainen wrote:
>Huh? In order to find out whether a declaration is the first
>declaration, lookup must
>be done. For an unqualified identifier, that lookup is done just for
>the enclosing
>namespace, but a lookup must happen.


You didn't say, what would be the purpose of this lookup? Of course, I'm talking about the case explicited in the sentence, that is,  the name of the friend declaration is neither qualified nor a template-id and the declaration is a function or an elaborated-type-specifier. 

Ville Voutilainen

unread,
Jun 25, 2015, 10:46:20 AM6/25/15
to std-dis...@isocpp.org
On 25 June 2015 at 17:42, Belloc <jabe...@gmail.com> wrote:
> On Thursday, June 25, 2015 at 11:19:05 AM UTC-3, Ville Voutilainen wrote:
>>
>> >Huh? In order to find out whether a declaration is the first
>> >declaration, lookup must
>> >be done. For an unqualified identifier, that lookup is done just for
>> >the enclosing
>> >namespace, but a lookup must happen.
> You didn't say, what would be the purpose of this lookup? Of course, I'm

To determine whether the declaration is the first declaration, as I said.
As an example,

namespace M
{
void f();
class X
{
friend void f(); // the lookup happens here, and finds void M::f();
};
}

as opposed to

namespace M2
{
class X
{
friend void f(); // the lookup happens here, and doesn't find anything
};
}

Belloc

unread,
Jun 25, 2015, 10:57:02 AM6/25/15
to std-dis...@isocpp.org
On Thursday, June 25, 2015 at 11:46:20 AM UTC-3, Ville Voutilainen wrote:
>To determine whether the declaration is the first declaration, as I said.
>As an example,

namespace M
{
    void f();
    class X
    {
      friend void f(); // the lookup happens here, and finds void M::f();
    };
}

as opposed to

namespace M2
{
    class X
    {
      friend void f(); // the lookup happens here, and doesn't find anything
    };
}

But while parsing the friend declaration inside the class, why would the compiler need to know whether the friend declaration is, or is not, the first in its namespace? Remember, there is nothing wrong with your second snippet above!

Ville Voutilainen

unread,
Jun 25, 2015, 11:02:04 AM6/25/15
to std-dis...@isocpp.org
Because it changes the semantics of the declaration, especially if
it's a declaration
that is a definition. I know there's nothing wrong with the second
snippet, but it doesn't
have the same semantics.

Belloc

unread,
Jun 25, 2015, 11:30:57 AM6/25/15
to std-dis...@isocpp.org
On Thursday, June 25, 2015 at 12:02:04 PM UTC-3, Ville Voutilainen wrote:

Because it changes the semantics of the declaration, especially if
it's a declaration
that is a definition. I know there's nothing wrong with the second
snippet, but it doesn't
have the same semantics.

I'm sorry but that didn't make much sense to me.

Jens Maurer

unread,
Jun 25, 2015, 2:59:54 PM6/25/15
to std-dis...@isocpp.org
On 06/25/2015 04:57 PM, Belloc wrote:
> But while parsing the friend declaration inside the class, why would the compiler need to know whether the friend declaration is, or is not, the first in its namespace? Remember, there is nothing wrong with your second snippet above!

Consider:

namespace N {
void f() { }
class S {
friend void f() { } // refers to N::f(); error: duplicate definition
};
}

void f() { }

namespace M {
class S {
friend void f() { } // introduces M::f() (not visible to name lookup except for ADL); ok
};
}


Jens

Johannes Schaub

unread,
Jun 25, 2015, 3:08:53 PM6/25/15
to std-dis...@isocpp.org
I believe that he's after something else. The text reads " If a friend
declaration in a non-local class first declares a class, function,
class template or function template the friend is a member of the
innermost enclosing namespace.". How is the "If ..." important" and
need to use any lookup whatsoever? Why doesn't it say "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 friend
declaration first declares the function, class or class template."?

Belloc

unread,
Jun 25, 2015, 3:44:45 PM6/25/15
to std-dis...@isocpp.org
The lookup you mentioned above has the sole objective of verifying the ODR rule. It has nothing to do with the verification whether the name used in a friend declaration, is, or isn't, the first declaration in its namespace. Maybe I should be more precise with my observation, in relation to the highlighted text in [namespace.memdef]/3: what does the compiler do with this information, i.e., that a name in a friend declaration is, or is not, first declared in its namespace? I just can't see how this information could be useful to the compiler at the moment it's parsing a friend declaration of a name of a function, or an elaborated-type-specifier, which is not qualified, nor a template-id.

Belloc

unread,
Jun 25, 2015, 4:33:23 PM6/25/15
to std-dis...@isocpp.org
On Thursday, June 25, 2015 at 3:59:54 PM UTC-3, Jens Maurer wrote:

Consider:

namespace N {
  void f() { }
  class S {
    friend void f() { }    // refers to N::f(); error: duplicate definition
  };
}

void f() { }

namespace M {
  class S {
    friend void f() { }    // introduces M::f() (not visible to name lookup except for ADL); ok
  };
}


Jens

Sometimes, my English doesn't follow exactly what I'm thinking. Therefore, I'm complementing what I've said earlier in my prior post:

The lookup you mentioned above has the sole objective of verifying the ODR rule. It has nothing to do with the verification whether the name used in a friend declaration, is, or isn't, the first declaration in its namespace. Maybe I should be more precise with my observation, in relation to the highlighted text in [namespace.memdef]/3: what does the compiler do with this information, i.e., that a name in a friend declaration is, or is not, first declared in its namespace? I just can't see how this information could be useful to the compiler at the moment it's parsing a friend declaration of a name of a function, or of an elaborated-type-specifier, which is not qualified, nor a template-id.

As a matter of fact, I dispute the importance of this information for the compiler, at the moment it is parsing the friend declaration, in any case. That is, even when the name in the friend declaration is qualified, or is a template-id, I believe the compiler doesn't care at this point whether the name is, or is not, first declared in its namespace. This will be taken into account only when the name is used, i.e., when the function, or the member function is called in the program. At this point, the compiler will consider only names in its innermost enclosing namespace for the cases where the name in the friend declaration is a function, or an elaborated-type-specifier, or will consider all the scopes, in and beyond, the innermost enclosing namespace, for the other two cases.   

David Krauss

unread,
Jun 26, 2015, 1:13:26 AM6/26/15
to std-dis...@isocpp.org

On 2015–06–26, at 4:33 AM, Belloc <jabe...@gmail.com> wrote:

As a matter of fact, I dispute the importance of this information for the compiler, at the moment it is parsing the friend declaration, in any case. That is, even when the name in the friend declaration is qualified, or is a template-id, I believe the compiler doesn't care at this point whether the name is, or is not, first declared in its namespace. This will be taken into account only when the name is used, i.e., when the function, or the member function is called in the program. At this point, the compiler will consider only names in its innermost enclosing namespace for the cases where the name in the friend declaration is a function, or an elaborated-type-specifier, or will consider all the scopes, in and beyond, the innermost enclosing namespace, for the other two cases.   

In a perfect world, perhaps implementations would defer the lookup of a friend declaration until the friendship is used by overload resolution. Ideally, friendship should be specified without declarations at all. A class should be able to grant friendship without dictating anything about the befriended entity, such as its linkage, presence in an unnamed namespace, inline qualification, or exception specification. (Friend declarations which are definitions would be the only exception.) It shouldn’t matter whether or not a friend declaration lexically precedes the declaration of the befriended entity. Really what a befriending class should do is to nominate some operation or interface as privileged without caring about the implementation behind it. I don’t think C++ falls short of this by design intent, but because declarations are an approximation to some Platonic ideal of friendship.

The compiler implementation practice is to resolve friendship immediately while processing the body of a class. The standard codifies the practice, perhaps deviating from the ideal. It would be nice to see a proposal describing the advantage to the model you’re advocating. It should also include analysis of the runtime complexity of the status quo and the proposed model.

Friendship is a little complicated because it’s done backwards: It’s declared by the granting class, but queried from the receiving entity. It’s more natural to look for special permissions attached to the enclosing scopes, than to search for references to enclosing entities in the class being accessed. (Whatever the proposal, asking implementations to change this is a non-starter.) Lazily resolving friend declarations would require inventing a sort of purgatory for them, for example a new kind of namespace-level declaration that can be removed or redirected once a matching, “real” one is processed.

Hopefully this helps answer your question about “why” friends are done this way. As for further “why not” to do it another way, perhaps that belongs on std-proposals. As for the example in TC++PL, that looks like fodder for a defect report. Whatever folks say here, you’ve conclusively proved that the current wording is too confusing.

David Rodríguez Ibeas

unread,
Jun 26, 2015, 5:42:10 AM6/26/15
to std-dis...@isocpp.org
struct Outer {
    struct TheFriend;
    struct Inner {
       friend class TheFriend;
    };
};

The declaration 'friend class TheFriend' is not the first declaration for 'TheFriend', lookup finds 'Outer::TheFriend' and makes that a friend of 'Outer::Inner'. That declaration does not make 'TheFriend' a member of the innermost namespace, it is left as a member of 'Outer'.

    David

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.

Message has been deleted

Belloc

unread,
Jun 26, 2015, 1:15:22 PM6/26/15
to std-dis...@isocpp.org, dib...@ieee.org


On Friday, June 26, 2015 at 6:42:10 AM UTC-3, David Rodríguez Ibeas wrote:
struct Outer {
    struct TheFriend;
    struct Inner {
       friend class TheFriend;
    };
};

The declaration 'friend class TheFriend' is not the first declaration for 'TheFriend', lookup finds 'Outer::TheFriend' and makes that a friend of 'Outer::Inner'. That declaration does not make 'TheFriend' a member of the innermost namespace, it is left as a member of 'Outer'.

    David

Both compilers (clang and gcc) confirm what you said above. As surprising as this may seem, for lack of other explanation, I would say that the implementers for these two compilers had the same type of misunderstanding in relation to [namespace.memdef]/3, that Mr. Stroustrup apparently showed in his example, in Section 19.4.1 of his new book, as I've already mentioned on my first post in this discussion.

Belloc

unread,
Jun 26, 2015, 2:20:39 PM6/26/15
to std-dis...@isocpp.org


On Friday, June 26, 2015 at 2:13:26 AM UTC-3, David Krauss wrote:

In a perfect world, perhaps implementations would defer the lookup of a friend declaration until the friendship is used by overload resolution. Ideally, friendship should be specified without declarations at all. A class should be able to grant friendship without dictating anything about the befriended entity, such as its linkage, presence in an unnamed namespace, inline qualification, or exception specification. (Friend declarations which are definitions would be the only exception.) It shouldn’t matter whether or not a friend declaration lexically precedes the declaration of the befriended entity. Really what a befriending class should do is to nominate some operation or interface as privileged without caring about the implementation behind it. I don’t think C++ falls short of this by design intent, but because declarations are an approximation to some Platonic ideal of friendship.

The compiler implementation practice is to resolve friendship immediately while processing the body of a class. The standard codifies the practice, perhaps deviating from the ideal. It would be nice to see a proposal describing the advantage to the model you’re advocating. It should also include analysis of the runtime complexity of the status quo and the proposed model.

The example given by David Rodriguez, which I submitted here, is an important example of how things can go bad with the current model used by the Standard. Had the friendship been defined at the time of the function call, this problem would never occur.

Belloc

unread,
Jun 26, 2015, 3:13:40 PM6/26/15
to std-dis...@isocpp.org, dib...@ieee.org
On Friday, June 26, 2015 at 6:42:10 AM UTC-3, David Rodríguez Ibeas wrote:
struct Outer {
    struct TheFriend;
    struct Inner {
       friend class TheFriend;
    };
};

The declaration 'friend class TheFriend' is not the first declaration for 'TheFriend', lookup finds 'Outer::TheFriend' and makes that a friend of 'Outer::Inner'. That declaration does not make 'TheFriend' a member of the innermost namespace, it is left as a member of 'Outer'.

    David

Please, ignore what I said earlier in my prior post to you. The implementers didn't have a choice, but to establish the friendship with the struct Outer::TheFriend, eventhough the struct is not even defined in the code. As I said in my prior post to David Krauss, this is a problem caused by the current model used by the Standard, that is, to establish the friendship at the time when the friend declaration is parsed. By the way, I want to thank you for this very insightful example. Now I think, I'm really starting to understand what's going on here.  

David Krauss

unread,
Jun 26, 2015, 10:20:47 PM6/26/15
to std-dis...@isocpp.org
On 2015–06–26, at 5:42 PM, David Rodríguez Ibeas <dib...@ieee.org> wrote:

struct Outer {
    struct TheFriend;
    struct Inner {
       friend class TheFriend;
    };
};

The declaration 'friend class TheFriend' is not the first declaration for 'TheFriend', lookup finds 'Outer::TheFriend' and makes that a friend of 'Outer::Inner'. That declaration does not make 'TheFriend' a member of the innermost namespace, it is left as a member of 'Outer’.

Correct. It’s not clear how this relates to my message, though. I’m drifting off into proposal-land. I’d prefer this to be well-formed, with the same meaning:

struct Outer {
    struct Inner {
       friend class TheFriend; // ill-formed NDR because TheFriend is redeclared in class scope.
    }; // In practice, it will bind to the namespace: http://coliru.stacked-crooked.com/a/dc2be64a15818f68

    struct TheFriend;
};

In this example, under my proposal, the friend declaration would create a placeholder in the namespace. The nested class declaration would find the placeholder, despite its association to the namespace, and bind the friend to the nested member class.

Likewise, I’d like this to work:

struct Outer {
    friend class TheFriend; // befriends ::TheFriend
};

namespace {
    struct TheFriend {…}; // but this is ::<unnamed>::TheFriend, a different thing
};

… as if there were a forward declaration of namespace { struct TheFriend; }.


On 2015–06–26, at 9:16 PM, Belloc <jabe...@gmail.com> wrote:

 used your example to produce the following snippet (see live example), where one friend declaration in Outer::Innergenerates two befriended entities at the same time:

You forgot to put access protection in this example. Changing struct to class produces the expected error, that the nested member is befriended and the namespace member isn’t: http://coliru.stacked-crooked.com/a/a9b4f1c9c666cbd7

Belloc

unread,
Jun 27, 2015, 9:02:26 AM6/27/15
to std-dis...@isocpp.org, pot...@mac.com
I haven't forgotten. I just erased my post a few seconds after posting it (see the deleted message above), after I realized it was incorrect. You're probably answering an email that was sent automatically to you, containing my deleted message. But what would you say about this new example I posted here. See below:

#include<iostream>

class Outer {
   
struct TheFriend;
public:
   
class Inner {
       
friend struct TheFriend;
//    private:
       
static const int i = -1;
   
};
};
struct TheFriend{ void f() { std::cout << Outer::Inner::i << '\n'; } };

int main()
{
   
TheFriend global;
   
global.f();
}

I just can't foresee any problem whatsoever, if the friendship is resolved while the compiler is parsing an access to a class member, like i in the expression std::cout << Outer::Inner::i << '\n'; above, instead of having to decide this while parsing the friend declaration friend struct TheFriend;. The compiler already has to verify whether the access to the member i is allowed, when it's parsing the alluded expression. It is just a matter of verifying additionally, at this point, whether the function f is a friend of Outer::Inner. Could you explain to me, in simple terms, what is the problem with this approach? 

David Krauss

unread,
Jun 27, 2015, 12:12:03 PM6/27/15
to std-dis...@isocpp.org, Belloc
On 2015–06–27, at 9:02 PM, Belloc <jabe...@gmail.com> wrote:

But what would you say about this new example I posted here. See below:

It’s essentially identical to the one I posted. The expected error occurs. So, no problem, right?

I just can't foresee any problem whatsoever, if the friendship is resolved while the compiler is parsing an access to a class member, like i in the expression std::cout << Outer::Inner::i << '\n'; above, instead of having to decide this while parsing the friend declaration friend struct TheFriend;. The compiler already has to verify whether the access to the member i is allowed, when it's parsing the alluded expression. It is just a matter of verifying additionally, at this point, whether the function f is a friend of Outer::Inner.

Even if the compiler lazily resolves the friendship, it will still choose only Outer::TheFriend and not ::TheFriend.

Lazy resolution can only make a difference when the friend declaration is the initial declaration, which was the original topic of this thread.

 Could you explain to me, in simple terms, what is the problem with this approach? 

What’s wrong with the terms of my first post to this thread? Granted, it was a bit verbose.

TL;DR: It’s easier to implement (and specify) non-lazy evaluation, so that’s what we have. If you see a benefit to lazy resolution, that discussion might be brought to the std-proposals list.

Belloc

unread,
Jun 27, 2015, 5:18:06 PM6/27/15
to std-dis...@isocpp.org, jabe...@gmail.com


On Saturday, June 27, 2015 at 1:12:03 PM UTC-3, David Krauss wrote:

On 2015–06–27, at 9:02 PM, Belloc <jabe...@gmail.com> wrote:
I just can't foresee any problem whatsoever, if the friendship is resolved while the compiler is parsing an access to a class member, like i in the expression std::cout << Outer::Inner::i << '\n'; above, instead of having to decide this while parsing the friend declaration friend struct TheFriend;. The compiler already has to verify whether the access to the member i is allowed, when it's parsing the alluded expression. It is just a matter of verifying additionally, at this point, whether the function f is a friend of Outer::Inner.

Even if the compiler lazily resolves the friendship, it will still choose only Outer::TheFriend and not ::TheFriend.

Lazy resolution can only make a difference when the friend declaration is the initial declaration, which was the original topic of this thread.

That's not what I was thinking in terms of friendship. My idea  was to simply forget about whether the friend declaration is, or is not, the first declaration in its namespace. For example, in the code you posted, which I reproduced below, both member functions Outer::TheFriend::f and ::TheFriend::f  would be friends of the class Outer::Inner. This is what I thought you were referring to, when you wrote the following, in your first post:

"In a perfect world, perhaps implementations would defer the lookup of a friend declaration until the friendship is used by overload resolution. Ideally, friendship should be specified without declarations at all. A class should be able to grant friendship without dictating anything about the befriended entity, such as its linkage, presence in an unnamed namespace, inline qualification, or exception specification. (Friend declarations which are definitions would be the only exception.) It shouldn’t matter whether or not a friend declaration lexically precedes the declaration of the befriended entity. Really what a befriending class should do is to nominate some operation or interface as privileged without caring about the implementation behind it. I don’t think C++ falls short of this by design intent, but because declarations are an approximation to some Platonic ideal of friendship.

But now you're saying "Lazy resolution can only make a difference when the friend declaration is the initial declaration, which was the original topic of this thread.". I understand the change I'm proposing is not practical now, as this would break a lot of code already in production. What I'd like to know is, why such a proposal was not adopted when C++ was in its infant stage, as this solution seems to be so much cleaner and elegant than this horrible confusion caused by the expression "If a friend declaration in a non-local class first declares a class, function, class template or function template ..." in [namespace.memdef]/3. In other words, can you imagine any significant problem, had this hypothetical solution been adopted in place of the current solution in the Standard?

#include<iostream>

struct Outer {
   
struct TheFriend{ void f() {std::cout << Inner::i << '\n'; } };

   
class Inner {
       
friend struct TheFriend;

       
static const int i = -1;
   
};
};
struct TheFriend{ void f() { std::cout << Outer::Inner::i << '\n'; } };

int main()
{
   
TheFriend global;
   
Outer::TheFriend outer;
   
global.f();
    outer
.f();
}

I must say though, that my understanding of friend declarations, as prescribed by the Standard, has improved quite a bit since I started this discussion. Thanks to all involved.

David Krauss

unread,
Jun 27, 2015, 11:07:56 PM6/27/15
to std-dis...@isocpp.org
On 2015–06–28, at 5:18 AM, Belloc <jabe...@gmail.com> wrote:

That's not what I was thinking in terms of friendship. My idea  was to simply forget about whether the friend declaration is, or is not, the first declaration in its namespace. For example, in the code you posted, which I reproduced below, both member functions Outer::TheFriend::f and ::TheFriend::f  would be friends of the class Outer::Inner. This is what I thought you were referring to, when you wrote the following, in your first post:

"In a perfect world, perhaps implementations would defer the lookup of a friend declaration until the friendship is used by overload resolution. Ideally, friendship should be specified without declarations at all. A class should be able to grant friendship without dictating anything about the befriended entity, such as its linkage, presence in an unnamed namespace, inline qualification, or exception specification.

I didn’t mention befriending two things in one declaration. I only said that friend declarations shouldn’t be taken literally as declarations, which really is a broader idea than not caring whether a friend declaration is initial.

Currently an unqualified, non-definition friend declaration has one of several outcomes, in rough priority order:

1. Use declaration matching with unqualified lookup restricted to the current namespace.
2. Make a full-blown declaration, given ADL.
3. Declare a name invisibly.
4. Find a matching a template declaration and specialize it with argument deduction.

Four different outcomes! All because friend is not really suitable for doing the work of declaring something. All the cases should have similar effects and requirements, IMHO.

But introducing a one-to-many mapping is another matter. Not saying it’s a bad thing, but it’s not motivated by your example. Having both TheFriends befriended would be surprising.

But now you're saying "Lazy resolution can only make a difference when the friend declaration is the initial declaration, which was the original topic of this thread.". I understand the change I'm proposing is not practical now, as this would break a lot of code already in production. What I'd like to know is, why such a proposal was not adopted when C++ was in its infant stage, as this solution seems to be so much cleaner and elegant than this horrible confusion caused by the expression "If a friend declaration in a non-local class first declares a class, function, class template or function template ..." in [namespace.memdef]/3. In other words, can you imagine any significant problem, had this hypothetical solution been adopted in place of the current solution in the Standard?

Friendship implementation technique continued to evolve after the infancy of C++. It’s a thorny issue and folks at the time were mostly concerned with simplicity, and keeping the compiler fast and stable. Overload resolution is critical to compiler performance, so it’s a bad idea to review friend declarations during access checking.

My elaboration of lazy friend declaration matching shouldn’t change any existing connection between a befriending class and a befriended entity. It should only break TUs that abuse friend for the sake of forward declaration without ever redeclaring the befriended entity. I’m not sure how I’ve failed to satisfy your ideal, or how your example follows from “simply forget about whether the friend declaration is, or is not, the first declaration in its namespace.” Your example shows a friend which is not a first declaration, yet something has still changed versus the status quo. So I’m a bit confused.

Perhaps your real proposal is that every friend declaration should befriend every entity that could potentially match it, as if no other entities existed.

David Rodríguez Ibeas

unread,
Jun 29, 2015, 6:15:51 AM6/29/15
to std-dis...@isocpp.org
David, while I did answer to your message, it was just because it was the last in the chain.  The example was intended to prove that the "first declared" part is important in the language and cannot just be removed (as I understood Belloc was suggesting).

Other than that, I must admit that I just skimmed over the discussion and I did not quite grasp some of the issues, so take this with a pinch of salt.

On "lazy" resolution of frienship I would divide the problem into two cases, one in which the friend is declared within the same class (Outer::Inner) and one in which it is not (example with the befriended entity in an unnamed namespace). Lazy resolution means, for the latter, that the programmer writing the class does not know what is being befriended.  In particular, the example David Krauss provides:

class T { friend class U; };
namespace { class U { ... }; }

Lazy resolution here means that anyone including the header where 'T' is defined can provide their own "U" to access the internals, incidentally causing what you did not want: "I didn’t mention befriending two things in one declaration." I don't particularly care for these (befriending multiple things, or not knowing what is befriended).

In the case of a type being defined inside class, I would be fine with it as the author of the class is in full control of both entities and the friend would be resolved right away inside the same class definition.  Yet the question remains, why would we special case that rather than leave it as is and have the programmer order the friend declaration and the befriended entity declaration appropriately?

Personally, I'd prefer the friend declaration not declaring anything, but forcing the existence of a previous declaration. The problem is that at the same time, I really like being able to define a friend function inside another type for two reasons, first because the function is only available for ADL and second because with templates it might be impossible to declare the same thing before hand:

template <typename T> class U {
   friend std::ostream& operator<<(std::ostream& out, U const & obj) { .. }
};

I cannot see how to get that same behavior without having the feature be a declaration. That is, if it is not a declaration, how can we have a definition (definitions being a subset of declarations), and if it has to be declared externally, how would we provide the above 'operator<<'? How can we minimize the pages of error messages when we try 'operator<<' on an object that does not support it? [In the case of non-template classes, the operator can be a simple declaration, with the implementation in the .cpp; for those who dislike providing definitions of functions inside types]

On Belloc's comment " For example, in the code you posted, which I reproduced below, both member functions Outer::TheFriend::f and ::TheFriend::f  would be friends of the class Outer::Inner.", we would again have to revisit whether the 'friend' declaration is or is not an declaration. The flexibility could cause different issues:

struct Outer {
    class TheFriend;
    class Inner {
       friend class TheFriend; // 1
    };
};
enum TheFriend { ... };

If the line [1] provides a namespace level entity 'class TheFriend', the latter enum would be in error as you cannot have an enum and a class with the same name in the same scope.  Other than that, it gives to the same level of abuse as the case of the unnamed namespace above.  Considering my first example, where the implementor of 'Outer' wants to grant access to 'Outer::TheFriend', either he lets any interested party define a '::TheFriend' that has full access to the internals (undesired) or he is forced into providing a '::TheFriend' class to block users from using it as a back door (ODR violation if they attempt, NDR, users might still do it unwillingly).

I agree with Belloc that this part of the specification might be confusing, I understand that many people get it wrong (I have made mistakes in the past similar to the examples in Bjarne's book), but the feature is there and I find it valuable.  If we can make the intention clearer, I am all for it.  But I'd rather not make 'friend' more diffuse by having it befriend *anything* that might come later that resembles the declaration.

Using the metaphor of frienship being you giving your keys to a friend, I'd rather name the friend than let the doorman give my keys to the first person that asks to enter my apartment.

   David

P.S. I tend to overcomplicate, so here's a TL;DR:
- I don't like a single friend declaration granting access to multiple things, more so when those things might be out of control for the implementor of the type
- I cannot see how "lazy" matching could be used to get the same as an inline friend definition gives today.
- "Lazy" friendship has the potential of unwillingly granting access to entities outside of the control of the implementor of the type.

David Krauss

unread,
Jun 29, 2015, 7:25:11 AM6/29/15
to std-dis...@isocpp.org
On 2015–06–29, at 6:15 PM, David Rodríguez Ibeas <dib...@ieee.org> wrote:

class T { friend class U; };
namespace { class U { ... }; }

Lazy resolution here means that anyone including the header where 'T' is defined can provide their own "U" to access the internals, incidentally causing what you did not want: "I didn’t mention befriending two things in one declaration." I don't particularly care for these (befriending multiple things, or not knowing what is befriended).

The lazy resolution would still be limited to the namespace and its associated unnamed namespaces. The user would have to knowingly hack in by opening the library namespace. Also, they can already do this, by supplying the forward declaration before including the library header:

// User-supplied hack:
namespace hack { class U { ... }; }
using namespace hack;

// Library intended to befriend externally-linked class U but friend lookup now finds hack instead:
class T { friend class U; };

Extension or no, the presence of the hack removes friendship from its intended target in that TU, which is very likely to cause failure. It also violates the ODR unless it’s done in every TU, which causes failure unless the friendship was unused.

(NB, friendship is successfully stolen on ICC and Clang but not on GCC. In any case, C++ access is not a security scheme, and they need to open the namespace.)

In the case of a type being defined inside class, I would be fine with it as the author of the class is in full control of both entities and the friend would be resolved right away inside the same class definition.  Yet the question remains, why would we special case that rather than leave it as is and have the programmer order the friend declaration and the befriended entity declaration appropriately?

For convenience and simplicity.

I think the strongest motivation is getting rid of header order dependencies. Declaration order inside a class just falls out of it.

Personally, I'd prefer the friend declaration not declaring anything, but forcing the existence of a previous declaration.

Then the compiler produces an error message asking the programmer to add a forward declaration, which is sort-of mechanical busywork. The message appears depending on the order of #includes.

The problem is that at the same time, I really like being able to define a friend function inside another type for two reasons, first because the function is only available for ADL and second because with templates it might be impossible to declare the same thing before hand:

template <typename T> class U {
   friend std::ostream& operator<<(std::ostream& out, U const & obj) { .. }
};

Definitions are more like “pets” than “friends.” I think they can be excluded from the discussion, since there’s really no declaration matching there.

I agree with Belloc that this part of the specification might be confusing, I understand that many people get it wrong (I have made mistakes in the past similar to the examples in Bjarne's book), but the feature is there and I find it valuable.  If we can make the intention clearer, I am all for it.  But I'd rather not make 'friend' more diffuse by having it befriend *anything* that might come later that resembles the declaration.

Using the metaphor of frienship being you giving your keys to a friend, I'd rather name the friend than let the doorman give my keys to the first person that asks to enter my apartment.

Fair nuff, but

1. It’s already diffuse. Removing that is a breaking change.
2. It’s more like leaving the key under the welcome mat in front of the apartment. Only the neighbors can get it, not folks on the street, unless they impersonate a resident.

Belloc

unread,
Jun 29, 2015, 2:15:00 PM6/29/15
to std-dis...@isocpp.org, pot...@mac.com


On Friday, June 26, 2015 at 11:20:47 PM UTC-3, David Krauss wrote:

Correct. It’s not clear how this relates to my message, though. I’m drifting off into proposal-land. I’d prefer this to be well-formed, with the same meaning:

struct Outer {
    struct Inner {
       friend class TheFriend; // ill-formed NDR because TheFriend is redeclared in class scope.
    }; // In practice, it will bind to the namespace: http://coliru.stacked-crooked.com/a/dc2be64a15818f68

    struct TheFriend;
};

Where in the Standard did you get that friend class TheFriend; is ill-formed NDR?  

Richard Smith

unread,
Jun 29, 2015, 7:44:52 PM6/29/15
to std-dis...@isocpp.org, pot...@mac.com
That's 3.3.7/1 rule 2: "A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule."

Belloc

unread,
Jun 30, 2015, 8:28:20 AM6/30/15
to std-dis...@isocpp.org, pot...@mac.com


On Monday, June 29, 2015 at 8:44:52 PM UTC-3, Richard Smith wrote:


That's 3.3.7/1 rule 2: "A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule."

So, you're basically saying that the word "complete" above means that the rule also applies for any name N in S and any name N in any nested class of S. Is that correct? 

David Rodríguez Ibeas

unread,
Jun 30, 2015, 9:21:24 AM6/30/15
to std-dis...@isocpp.org
I am not convinced that to be the most relevant quote (it may or may not, I don't have the drive to pursue it), but the next point:

3 If reordering member declarations in a class yields an alternate valid program under (1) and (2), the
program is ill-formed, no diagnostic is required.

So either the quote provided by Richard makes it undefined behavior, or that yields a valid program but reordering of the Inner and TheFriend members yields an also valid albeit different program and you have undefined behavior there.

--

Belloc

unread,
Jun 30, 2015, 9:32:49 AM6/30/15
to std-dis...@isocpp.org, dib...@ieee.org


On Tuesday, June 30, 2015 at 10:21:24 AM UTC-3, David Rodríguez Ibeas wrote:
I am not convinced that to be the most relevant quote (it may or may not, I don't have the drive to pursue it), but the next point:

3 If reordering member declarations in a class yields an alternate valid program under (1) and (2), the
program is ill-formed, no diagnostic is required.

So either the quote provided by Richard makes it undefined behavior, or that yields a valid program but reordering of the Inner and TheFriend members yields an also valid albeit different program and you have undefined behavior there.

That was great! Thanks. 

Belloc

unread,
Jun 30, 2015, 1:56:17 PM6/30/15
to std-dis...@isocpp.org, dib...@ieee.org
[namespace.memdef]/3 contains this Note: "The other forms of friend declarations cannot declare a new member of the innermost enclosing namespace and thus follow the usual lookup rules."

What other forms is this note referring to?

Richard Smith

unread,
Jun 30, 2015, 2:14:56 PM6/30/15
to std-dis...@isocpp.org
On Tue, Jun 30, 2015 at 6:21 AM, David Rodríguez Ibeas <dib...@ieee.org> wrote:
I am not convinced that to be the most relevant quote (it may or may not, I don't have the drive to pursue it), but the next point:

3 If reordering member declarations in a class yields an alternate valid program under (1) and (2), the
program is ill-formed, no diagnostic is required.

That text no longer exists in the standard. It was removed because (2) already covers all the cases that should be ill-formed, and (3) made many other classes ill-formed that should not be (such as "struct X { int a; int b; };").
 
So either the quote provided by Richard makes it undefined behavior, or that yields a valid program but reordering of the Inner and TheFriend members yields an also valid albeit different program and you have undefined behavior there.

On Tue, Jun 30, 2015 at 1:28 PM, Belloc <jabe...@gmail.com> wrote:


On Monday, June 29, 2015 at 8:44:52 PM UTC-3, Richard Smith wrote:


That's 3.3.7/1 rule 2: "A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule."

So, you're basically saying that the word "complete" above means that the rule also applies for any name N in S and any name N in any nested class of S. Is that correct? 

Yes, this is intended to apply to any name N that appears lexically within the definition of class S, including within nested classes.

Richard Smith

unread,
Jun 30, 2015, 2:19:17 PM6/30/15
to std-dis...@isocpp.org, dib...@ieee.org
On Tue, Jun 30, 2015 at 10:56 AM, Belloc <jabe...@gmail.com> wrote:
[namespace.memdef]/3 contains this Note: "The other forms of friend declarations cannot declare a new member of the innermost enclosing namespace and thus follow the usual lookup rules."

What other forms is this note referring to?

Look at the sentence to which the note is attached:

"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, [...]"

So the other forms are the ones where:
 * the name is qualified, or
 * the name is a template-id, or
 * the declaration is "friend simple-type-specifier ;" or "friend typename-specifier ;" (see 11.3/3)

Belloc

unread,
Jun 30, 2015, 2:34:28 PM6/30/15
to std-dis...@isocpp.org, dib...@ieee.org
Look at the sentence to which the note is attached:

"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, [...]"

So the other forms are the ones where:
 * the name is qualified, or
 * the name is a template-id, or
 * the declaration is "friend simple-type-specifier ;" or "friend typename-specifier ;" (see 11.3/3)

It was my understanding that when the name is a qualified-i or a template-id, these two cases were already considered by the previous sentence, which you mentioned above. But I had not noticed the other two friend declarations shown above. Thanks.

Belloc

unread,
Jun 30, 2015, 2:54:16 PM6/30/15
to std-dis...@isocpp.org, dib...@ieee.org
I've just found another odd case about friend declarations:

struct Outer {
   
class Inner {
       
friend class C;
       
friend void f();
       
static int const i = 0;
   
};
   
class C { static int const k = Inner::i; };
   
void f() { int i = Inner::i; }
};
class C { static int const k = Outer::Inner::i; };
void f() { int i = Outer::Inner::i; }

clang brefiends both the class C and the function f in the global scope, as friends of Outer::Inner, as expected.

Now consider this second snippet, were the code above was slightly changed.

struct Outer {
   
class C {};
   
void f() {}
   
class Inner {
       
friend class C;
       
friend void f();
       
static int const i = 0;
   
};
};
class C { static int const k = Outer::Inner::i; };
void f() { int i = Outer::Inner::i; }

Now clang befriends the class Outer::C and the function ::f. Why is that?

Richard Smith

unread,
Jun 30, 2015, 3:35:48 PM6/30/15
to std-dis...@isocpp.org, dib...@ieee.org
As was pointed out in the previous few messages, the first example is ill-formed, no diagnostic required (which basically means UB with an optional compile-time diagnostic).

Belloc

unread,
Jul 1, 2015, 8:07:26 AM7/1/15
to std-dis...@isocpp.org, dib...@ieee.org


On Tuesday, June 30, 2015 at 4:35:48 PM UTC-3, Richard Smith wrote:

As was pointed out in the previous few messages, the first example is ill-formed, no diagnostic required (which basically means UB with an optional compile-time diagnostic).

  1. I can only think of §3.3.7/1 (3) to justify my first snippet being ill-formed. I checked the most current draft (N4527) and the bullet point (3), "If reordering member declarations in a class yields an alternate valid program under (1) and (2), the program is ill-formed, no diagnostic is required" was really eliminated from the Standard, as you mentioned before.
  2. But I can' t see how (2)  covers this, as you have also mentioned above: "That text no longer exists in the standard. It was removed because (2) already covers all the cases that should be ill-formed, and (3) made many other classes ill-formed that should not be (such as "struct X { int a; int b; };").
  3. But let's assume for the moment bullet point 2 covers this case. Then why is my first snippet ill-formed and my second snippet well-formed? As it happens, if I change my second snippet to the one below, I would get an alternate valid program, wouldn't I?

    struct Outer {

  1.     
    void f() {}
        
    class Inner {
           
    friend class C;
           
    friend void f();
           
    static int const i = 0;
        
    };
  1.     class C {};
  1. };
    class C { static int const k = Outer::Inner::i; };
    void f() { int i = Outer::Inner::i; }

  1. And this would make my second snippet ill-formed too, vis-à-vis bullet point (3) that was erased from §3.3.7/1 .

David Rodríguez Ibeas

unread,
Jul 1, 2015, 12:41:53 PM7/1/15
to std-dis...@isocpp.org
On Tue, Jun 30, 2015 at 8:35 PM, Richard Smith <ric...@metafoo.co.uk> wrote:

struct Outer {
   
class C {};
   
void f() {}
   
class Inner {
       
friend class C;
       
friend void f();
       
static int const i = 0;
   
};
};
class C { static int const k = Outer::Inner::i; };
void f() { int i = Outer::Inner::i; }

Now clang befriends the class Outer::C and the function ::f. Why is that?

Lookup for 'C' in 'friend class C' finds '::Outer::C'. The class is well defined.  The attempt to access 'Outer::Inner::i' in '::C'  is an error as the member is private and '::C' is not a friend.  

The friend declaration 'void f()' declares a non-member function as a friend, there is no non-member function in scope so lookup fails and this is a first declaration, declaring 'void ::f()' and making it a friend. The latter declaration (and definition) matches.

I am not sure what is confusing from this example.


Belloc

unread,
Jul 1, 2015, 1:35:08 PM7/1/15
to std-dis...@isocpp.org, dib...@ieee.org
Just for clarification: the code and the phrase you quoted above, was posted by me, not by Richard Smith 

If you change the order of the two classes C and Inner inside Outer, the befriended class would turn to be the one in global scope (see live example) instead of ::Outer::C, and this change in semantics makes the struct Outer ill-formed, according to §3.3.7/1 (3) in C++14, for example.

Richard Smith

unread,
Jul 1, 2015, 2:56:09 PM7/1/15
to std-dis...@isocpp.org, dib...@ieee.org
On Wed, Jul 1, 2015 at 5:07 AM, Belloc <jabe...@gmail.com> wrote:


On Tuesday, June 30, 2015 at 4:35:48 PM UTC-3, Richard Smith wrote:

As was pointed out in the previous few messages, the first example is ill-formed, no diagnostic required (which basically means UB with an optional compile-time diagnostic).

  1. I can only think of §3.3.7/1 (3) to justify my first snippet being ill-formed. I checked the most current draft (N4527) and the bullet point (3), "If reordering member declarations in a class yields an alternate valid program under (1) and (2), the program is ill-formed, no diagnostic is required" was really eliminated from the Standard, as you mentioned before.
  2. But I can' t see how (2)  covers this,
Within the definition of "struct Outer", there is a use of the names "C" and "f". Lookup at the point where the name is used finds no declaration. Lookup in the completed scope of "struct Outer" finds Outer::C and Outer::f. So the code is ill-formed (NDR) by rule (2).
  1. as you have also mentioned above: "That text no longer exists in the standard. It was removed because (2) already covers all the cases that should be ill-formed, and (3) made many other classes ill-formed that should not be (such as "struct X { int a; int b; };").
  2. But let's assume for the moment bullet point 2 covers this case. Then why is my first snippet ill-formed and my second snippet well-formed? As it happens, if I change my second snippet to the one below, I would get an alternate valid program, wouldn't I?
There is no "alternate valid program" rule any more. 


  1. struct Outer {
        
    void f() {}
        
    class Inner {
           
    friend class C;
           
    friend void f();
           
    static int const i = 0;
        
    };
        class C {};
    };
    class C { static int const k = Outer::Inner::i; };
    void f() { int i = Outer::Inner::i; }

    And this would make my second snippet ill-formed too, vis-à-vis bullet point (3) that was erased from §3.3.7/1 .

--

Belloc

unread,
Jul 1, 2015, 4:21:58 PM7/1/15
to std-dis...@isocpp.org, dib...@ieee.org


On Wednesday, July 1, 2015 at 3:56:09 PM UTC-3, Richard Smith wrote:
Within the definition of "struct Outer", there is a use of the names "C" and "f". Lookup at the point where the name is used finds no declaration. Lookup in the completed scope of "struct Outer" finds Outer::C and Outer::f. So the code is ill-formed (NDR) by rule (2).

As far as I can understand the lookups for the names C and f in the friend declarations inside Inner, in my first snippet, do not find the names Outer::C and Outer::f., otherwise these friend declarations wouldn't be the first in its namepace (as required in [namespace.memdef]/3 for them to befriend the class C and the function f, in the global scope). But anyway, I think I understood by now, what you meant about bullet point 3 being covered by 2 in §3.3.7/1. Thanks.

But there's still one remaining problem in my second snippet (which now I understand it to be well-formed): the friend declaration friend void f(); in Outer::Inner befriends the function f in the global scope, notwithstanding the fact that the friend declaration is not the first in its namespace, as you can see here. Would that be a bug in clang?

Richard Smith

unread,
Jul 1, 2015, 5:30:17 PM7/1/15
to std-dis...@isocpp.org, dib...@ieee.org
GCC, EDG, and Clang all agree here, but I've not yet been able to find standard wording that justifies the function and class case behaving differently.

David Rodríguez Ibeas

unread,
Jul 2, 2015, 4:19:52 AM7/2/15
to std-dis...@isocpp.org
I think I already answered to this yesterday, but here the reason again:

struct Outer {
    void f();
    class Inner {
         friend void f();
    };
};

Outer::f is *not* a friend of 'Inner', the friend declaration refers to a free (non-member) function. If you wanted to befriend the member function you would have to type:

friend void Outer::f();



--

David Krauss

unread,
Jul 2, 2015, 4:39:57 AM7/2/15
to std-dis...@isocpp.org

On 2015–07–02, at 4:19 PM, David Rodríguez Ibeas <dib...@ieee.org> wrote:

I think I already answered to this yesterday, but here the reason again:

struct Outer {
    void f();
    class Inner {
         friend void f();
    };
};

Outer::f is *not* a friend of 'Inner', the friend declaration refers to a free (non-member) function. If you wanted to befriend the member function you would have to type:

friend void Outer::f();

Then why does a class declared in parallel with f get befriended? It’s as if namespace lookup for declarations matching a friend is ignoring non-types, although that’s probably only a coincidence.

The standard doesn’t seem to say more than this ([namespace.memdef] §7.3.1.2/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.

The problem is, it says what scopes are not considered without mentioning which ones are. Unless I’m missing something, it’s unspecified whether unqualified friend declarator-ids can find anything in particular. There’s implementation variance in nested namespaces (not searched by GCC) and inconsistency between member functions (never found) and member/nested classes (always found).

This deserves a DR, unless I’ve missed something or one already exists. (Didn’t look.) Unfortunately, these rules are scattered somewhat randomly in the standard. The most reasonable thing IMHO is ordinary unqualified lookup from the class scope. (This jibes, by the way, with the idea that friend declarations are less like declarators and more like expressions in disguise.)

David Krauss

unread,
Jul 2, 2015, 5:12:26 AM7/2/15
to std-dis...@isocpp.org

On 2015–07–02, at 4:39 PM, David Krauss <pot...@gmail.com> wrote:

It’s as if namespace lookup for declarations matching a friend is ignoring non-types, although that’s probably only a coincidence.

s/namespace lookup/unqualified lookup/

It’s as if unqualified lookup for declarations matching a friend is ignoring non-types, although that’s probably only a coincidence.

(The coincidence being that lookup for the LHS of :: likewise ignores functions.) 

Belloc

unread,
Jul 2, 2015, 7:30:34 AM7/2/15
to std-dis...@isocpp.org
Definitely, I have a great deal of difficulty understanding what you write. Again, could you explain this in more simple terms?  

David Rodríguez Ibeas

unread,
Jul 2, 2015, 7:59:38 AM7/2/15
to std-dis...@isocpp.org
Lookup for X in X::Y ignores functions, the same behavior (codepath?) seems to be taken by compilers when doing lookup for f in 'friend void f();'

--

Belloc

unread,
Jul 2, 2015, 3:23:43 PM7/2/15
to std-dis...@isocpp.org, dib...@ieee.org


On Thursday, July 2, 2015 at 5:19:52 AM UTC-3, David Rodríguez Ibeas wrote:
I think I already answered to this yesterday, but here the reason again:

struct Outer {
    void f();
    class Inner {
         friend void f();
    };
};

Outer::f is *not* a friend of 'Inner', the friend declaration refers to a free (non-member) function. If you wanted to befriend the member function you would have to type:

friend void Outer::f();

That doesn't seem to agree with [basic.lookup.unqual]/7 and 10.

[basic.lookup.unqual]/10:

In a friend declaration naming a member function, a name used in the function declarator and not part of a template-argument in the declarator-id is first looked up in the scope of the member function’s class (10.2). If it is not found, or if the name is part of a template-argument in the declarator-id, the look up is as described for unqualified names in the definition of the class granting friendship.

[basic.lookup.unqual]/7:

A name used in the definition of a class X outside of a member function body, default argument, exception specification, brace-or-equal-initializer of a non-static data member, or nested class definition29 shall be declared in one of the following ways:
  1. (7.1) — before its use in class X or be a member of a base class of X (10.2), or
  2. (7.2) — if X is a nested class of class Y (9.7), before the definition of X in Y, or shall be a member of a base class of Y (this lookup applies in turn to Y ’s enclosing classes, starting with the innermost enclosing class),30 or
  3. (7.3) — if X is a local class (9.8) or is a nested class of a local class, before the definition of class X in a block enclosing the definition of class X, or
  4. (7.4) — if X is a member of namespace N, or is a nested class of a class that is a member of N, or is a local class or a nested class within a local class of a function that is a member of N, before the definition of class X in namespace N or in one of N ’s enclosing namespaces.

 

David Krauss

unread,
Jul 2, 2015, 11:22:37 PM7/2/15
to std-dis...@isocpp.org

On 2015–07–02, at 7:30 PM, Belloc <jabe...@gmail.com> wrote:

Definitely, I have a great deal of difficulty understanding what you write. Again, could you explain this in more simple terms?  

Yeah. There’s a lot going on here, so I’ll start at the top. The fundamental problem here is that friend declarations are underspecified.

Background: In most contexts where a name appears, it’s either being used (like in an expression) or declared (like in a declaration). Declarations match previous declarations, but only from the exact same scope.

Unqualified names declared as friends are different because they get looked up like uses, using unqualified name lookup (§3.4.1), for the sake of declaration matching. However, this isn’t explicitly specified in the standard, as DR 138 describes. This hole in the spec has left implementations to decide their own behavior.

Implementations have apparently converged to find member (nested) classes but not member functions. Perhaps because, once upon a time, nested classes weren’t automatically friends, so you had to manually befriend them. C++03 §11.8/1:

The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (clause 11) shall be obeyed.

The Base::Data example in DR 138 is outdated due to this change. The implementation practice changed a while ago, too. I can’t find an online C++03 compiler that doesn’t implement the C++11 rules. (The oldest is GCC 4.3.6 at wandbox.com.) Note that defect reports generally cause “features” to get back-ported in this way.

Also, GCC doesn’t use a process exactly like §3.4.1. It seems to ignore nested namespaces.

Finding classes but not functions of a given name is coincidentally like what happens during qualified name lookup (e.g. the X in X::Y), as David said. However, it’s no more than a coincidence, and unlikely to be a shared code path in the implementation. Name lookup is complicated in practice. Friend function and class declarations are unlikely to share that much in common. Also, functions will certainly be found in the namespace scope, unlike X in X::Y which really isn’t interested in functions anywhere.

The behavioral status quo of friend-finding is more like unqualified lookup from the immediate context for class names, and unqualified lookup from the namespace scope for function names.

All this really should be compiled into a paper, which CWG can use to resolve DR 138. If the above-quoted rule in C++03 was defective enough to be retroactively changed by a DR, then the lookup ambiguity is, too. They’re intimately related, if my interpretation of history is correct. Because nested classes are now automatically friends, they need not be found by declaration matching. Nevertheless, that simple solution breaks examples in this thread, so perhaps classes and functions should both be found even when that would lead to redundantly befriending yourself. Or, maybe the status quo is really best.


On 2015–07–03, at 3:23 AM, Belloc <jabe...@gmail.com> wrote:

That doesn't seem to agree with [basic.lookup.unqual]/7 and 10.

¶10 defines a rule not for the name of the friend (the declarator-id), but instead e.g. names of function parameter types. In a nutshell, you can use types belonging to a member function’s class while attempting to match the member function declaration.

¶7 only begs the question of friend name lookup. If lookup finds a member, then the member has to be declared within the class. If lookup finds a non-member, it has to be declared in the namespace. ¶7 doesn’t mention that friends can be initial declarations, but the rule concerning initial declarations ([namespace.memdef]/3) handles that.

David Rodríguez Ibeas

unread,
Jul 3, 2015, 7:38:41 AM7/3/15
to std-dis...@isocpp.org
On Fri, Jul 3, 2015 at 4:22 AM, David Krauss <pot...@gmail.com> wrote:

Implementations have apparently converged to find member (nested) classes but not member functions. Perhaps because, once upon a time, nested classes weren’t automatically friends, so you had to manually befriend them. C++03 §11.8/1:

The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (clause 11) shall be obeyed.
I am not sure this is relevant, while the members of a class now have access to the enclosing class, the opposite is not true, and this is the case where the friend declaration in the example could be useful (if you find that a good design):

class Outer {
    static const int k1 = 10;
    struct Inner;
    class X { static const int k2 = k1; };  // Fine, X has access to Outer privates
    int value() { return X::k2; }           // error, k is private within this context
};
struct Outer::Inner {
    static int value() { return X::k2; }   // error, k is private within this context
};

To be able to access 'Outer::X::k2', 'Outer::X' must declare 'Outer' and 'Outer::Inner' as friends. The change you mention does not affect this direction, it grants 'Outer::X' access to 'Outer::k1' even if it is private for all other purposes.

   David




Belloc

unread,
Jul 7, 2015, 10:10:55 AM7/7/15
to std-dis...@isocpp.org, dib...@ieee.org
I'm sorry for insisting on this. But the problem is, the more I read about friend declarations, the more confused I get.

You said the the first example is ill-formed based on item (2) in §3.3.7/1 which states:

2) A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule.

I highlighted above the word used, as you seem to imply that the declaration friend class C; uses the name C, as if it were present in an expression, which is not the case here. Using this interpretation for the word used in §3.3.7/1 (2), I would say that the first example is well-formed. Of course, I'm assuming that item (3), in C++14, was eliminated from this paragraph, according to the most recent draft N4527  
  

David Rodríguez Ibeas

unread,
Jul 7, 2015, 1:09:47 PM7/7/15
to std-dis...@isocpp.org
I don't think that *used* in that context refers to used in an expression. In particular, one of the examples that is provided is:

typedef int I;
class D {
    typedef I I;
};

That is deemed in error, as the first I in the typedef refers to ::I, but the second I is a new typedef. When the 'I' is "used" in the typedef for the first time it refers to a different "thing" than when checked at the closing brace of the class.

You could consider both as being the same, as at the end of the day they are just an alias to 'int', but the fact is that 'I' resolves to ::I or ::D::I in the too contexts and it is declared to be an error.

    David   

  

--

jaay...@gmail.com

unread,
Jul 7, 2015, 5:32:30 PM7/7/15
to std-dis...@isocpp.org, dib...@ieee.org
I just can't understand the reason why the first example is ill-formed. The name C used in the friend declaration refers to ::C in its context (class Inner) and when reevaluated in the complete scope of Inner (the global scope), it also refers to ::C. What am I missing here?  
  

David Rodríguez Ibeas

unread,
Jul 7, 2015, 7:39:14 PM7/7/15
to std-dis...@isocpp.org
The missing part is that Inner's definition is inside Outer's definition, and lookup for C yields ::C inside Inner (both in the friend declaration and the end of Inner's class definition), but it would resolve to ::Outer::C when looked up at the end of Outer's scope.

Honestly I am not 100% sure, but this is what I understood from Richard,

    David

  

--

Richard Smith

unread,
Jul 7, 2015, 8:11:23 PM7/7/15
to std-dis...@isocpp.org
On Tue, Jul 7, 2015 at 4:39 PM, David Rodríguez Ibeas <dib...@ieee.org> wrote:
The missing part is that Inner's definition is inside Outer's definition, and lookup for C yields ::C inside Inner (both in the friend declaration and the end of Inner's class definition), but it would resolve to ::Outer::C when looked up at the end of Outer's scope.

Right. Name lookup looked for the name C inside Outer, and later a name C was added to Outer.

Another equivalent phrasing of the rule: name lookup inside a class scope can always find all members of the class, but the program is ill-formed (no diagnostic required) if it finds a member that has not yet been declared.

jaay...@gmail.com

unread,
Jul 8, 2015, 8:54:32 AM7/8/15
to std-dis...@isocpp.org
You want to convince me that what's written in §3.3.7/1 (2) is equivalent to what you and David said above. English is not my mother tongue, but I just don't buy this.

Belloc

unread,
Jul 11, 2015, 8:07:15 AM7/11/15
to std-dis...@isocpp.org, dib...@ieee.org
I quoted you in this discussion in SO
Message has been deleted
Message has been deleted

Belloc

unread,
Jul 12, 2015, 1:46:26 PM7/12/15
to std-dis...@isocpp.org
For those who might be interested, I suggest reading this chat in SO, where dyp showed me the way to really grasp what §3.3.7/1 (2) is all about.

Reply all
Reply to author
Forward
0 new messages