Discussion:
friend declarations
(too old to reply)
Belloc
2015-06-25 14:03:33 UTC
Permalink
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
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Ville Voutilainen
2015-06-25 14:19:04 UTC
Permalink
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 {
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-25 14:37:25 UTC
Permalink
Post by Ville Voutilainen
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."
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Ville Voutilainen
2015-06-25 14:42:22 UTC
Permalink
Post by Belloc
Post by Ville Voutilainen
It can become a friend, if the friend declaration uses a qualified name.
"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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-25 14:42:48 UTC
Permalink
Post by Ville Voutilainen
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Ville Voutilainen
2015-06-25 14:46:19 UTC
Permalink
Post by Belloc
Post by Ville Voutilainen
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
};
}
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-25 14:57:02 UTC
Permalink
Post by Ville Voutilainen
Post by Ville Voutilainen
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!
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Ville Voutilainen
2015-06-25 15:02:03 UTC
Permalink
Post by Belloc
Post by Ville Voutilainen
Post by Ville Voutilainen
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!
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-25 15:30:57 UTC
Permalink
Post by Ville Voutilainen
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Jens Maurer
2015-06-25 18:59:50 UTC
Permalink
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
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
'Johannes Schaub' via ISO C++ Standard - Discussion
2015-06-25 19:08:52 UTC
Permalink
Post by Belloc
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!
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
};
}
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."?
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-25 19:44:45 UTC
Permalink
Post by Belloc
Post by Belloc
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!
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
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-25 20:33:23 UTC
Permalink
Post by Belloc
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Krauss
2015-06-26 05:13:12 UTC
Permalink
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Rodríguez Ibeas
2015-06-26 09:42:08 UTC
Permalink
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
Post by Belloc
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.
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-26 13:16:14 UTC
Permalink
Post by David Rodríguez Ibeas
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
I used your example to produce the following snippet (see live example
<http://coliru.stacked-crooked.com/a/6b61608ac32e8a4a>), where *one* friend
declaration in Outer::Inner generates *two* befriended entities at the same
time: Outer::TheFriend::f and ::TheFriend::f. As far as I can see, this can *only
*happen if the compilers do what I said earlier, i.e., the befriended
entities are resolved at the time the compiler parses those entities. For
me, there seems to exist a mismatch between what the Standard says, and
what the compilers are doing, in relation to this topic of friend
declarations.

#include<iostream>

struct Outer {
struct TheFriend{ void f() {std::cout << Inner::i << '\n'; } };
struct 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();
}
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-26 17:15:22 UTC
Permalink
Post by David Rodríguez Ibeas
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
<http://coliru.stacked-crooked.com/a/10dba145a6828e55>) 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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-26 19:13:40 UTC
Permalink
Post by David Rodríguez Ibeas
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 <http://coliru.stacked-crooked.com/a/10dba145a6828e55>. 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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Krauss
2015-06-27 02:20:22 UTC
Permalink
Post by David Rodríguez Ibeas
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; }.
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
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-27 13:02:25 UTC
Permalink
Post by David Rodríguez Ibeas
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
struct Outer {
struct Inner {
friend class TheFriend; // ill-formed NDR because TheFriend is
redeclared in class scope.
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.
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; }.
used your example to produce the following snippet (see live example
<http://coliru.stacked-crooked.com/a/6b61608ac32e8a4a>), where *one*
friend declaration in Outer::Innergenerates *two* befriended entities at
You forgot to put access protection in this example. Changing struct to
class produces the expected error, that the nested member is befriended
http://coliru.stacked-crooked.com/a/a9b4f1c9c666cbd7
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 <http://coliru.stacked-crooked.com/a/10dba145a6828e55>. 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? *
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Krauss
2015-06-27 16:11:55 UTC
Permalink
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-27 21:18:06 UTC
Permalink
Post by Belloc
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Krauss
2015-06-28 03:07:44 UTC
Permalink
"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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Rodríguez Ibeas
2015-06-29 10:15:48 UTC
Permalink
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.
Post by Belloc
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
"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
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.
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Krauss
2015-06-29 11:24:58 UTC
Permalink
Post by David Rodríguez Ibeas
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.)
Post by David Rodríguez Ibeas
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.
Post by David Rodríguez Ibeas
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.
Post by David Rodríguez Ibeas
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.
Post by David Rodríguez Ibeas
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-29 18:15:00 UTC
Permalink
Post by David Krauss
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
struct Outer {
struct Inner {
friend class TheFriend; // ill-formed NDR because TheFriend is
redeclared in class scope.
http://coliru.stacked-crooked.com/a/dc2be64a15818f68
struct TheFriend;
};
Where in the Standard did you get that friend class TheFriend; is
ill-formed NDR?
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Richard Smith
2015-06-29 23:44:51 UTC
Permalink
Post by Belloc
Post by David Krauss
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
struct Outer {
struct Inner {
friend class TheFriend; // ill-formed NDR because TheFriend is
redeclared in class scope.
http://coliru.stacked-crooked.com/a/dc2be64a15818f68
struct TheFriend;
};
Where in the Standard did you get that friend class TheFriend; is
ill-formed NDR?
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."
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-30 12:28:20 UTC
Permalink
Post by Richard Smith
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?
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Rodríguez Ibeas
2015-06-30 13:21:21 UTC
Permalink
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.
Post by Belloc
Post by Richard Smith
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?
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-30 13:32:49 UTC
Permalink
Post by David Rodríguez Ibeas
I am not convinced that to be the most relevant quote (it may or may not,
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-30 17:56:17 UTC
Permalink
[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?
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Richard Smith
2015-06-30 18:19:15 UTC
Permalink
Post by Belloc
[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)
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-30 18:34:28 UTC
Permalink
Post by Belloc
"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 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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-30 18:54:15 UTC
Permalink
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 <http://coliru.stacked-crooked.com/a/f9aa65ec53d7b0d1> 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 <http://coliru.stacked-crooked.com/a/8a6377eac0190b92> befriends
the class Outer::C and the function ::f. Why is that?
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Richard Smith
2015-06-30 19:35:46 UTC
Permalink
Post by David Rodríguez Ibeas
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 <http://coliru.stacked-crooked.com/a/f9aa65ec53d7b0d1> 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 <http://coliru.stacked-crooked.com/a/8a6377eac0190b92>
befriends the class Outer::C and the function ::f. Why is that?
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).
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-07-01 12:07:26 UTC
Permalink
Post by Richard Smith
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 {
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 .
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Richard Smith
2015-07-01 18:56:06 UTC
Permalink
Post by Belloc
Post by Richard Smith
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).
Post by Belloc
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 .
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-07-01 20:21:58 UTC
Permalink
Post by Belloc
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 *look**up*s 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
<http://coliru.stacked-crooked.com/a/8a6377eac0190b92>. Would that be a bug
in clang?
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Richard Smith
2015-07-01 21:30:15 UTC
Permalink
Post by Belloc
Post by Belloc
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 *look**up*s 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
<http://coliru.stacked-crooked.com/a/8a6377eac0190b92>. Would that be a
bug in clang?
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Rodríguez Ibeas
2015-07-02 08:19:49 UTC
Permalink
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();
Post by Richard Smith
Post by Belloc
Post by Belloc
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 *look**up*s 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
<http://coliru.stacked-crooked.com/a/8a6377eac0190b92>. Would that be a
bug in clang?
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.
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Krauss
2015-07-02 08:39:45 UTC
Permalink
Post by David Rodríguez Ibeas
struct Outer {
void f();
class Inner {
friend void f();
};
};
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.
Post by David Rodríguez Ibeas
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.)
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Krauss
2015-07-02 09:12:15 UTC
Permalink
Post by David Krauss
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.)
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-07-02 11:30:34 UTC
Permalink
Post by David Krauss
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.)
Definitely, I have a great deal of difficulty understanding what you write.
Again, could you explain this in more simple terms?
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Rodríguez Ibeas
2015-07-02 11:59:36 UTC
Permalink
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();'
Post by Belloc
Post by David Krauss
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.)
Definitely, I have a great deal of difficulty understanding what you
write. Again, could you explain this in more simple terms?
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Krauss
2015-07-03 03:22:29 UTC
Permalink
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.
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.
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Rodríguez Ibeas
2015-07-03 11:38:38 UTC
Permalink
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
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
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-07-02 19:23:42 UTC
Permalink
Post by David Rodríguez Ibeas
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
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Rodríguez Ibeas
2015-07-01 16:41:51 UTC
Permalink
Post by David Rodríguez Ibeas
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 <http://coliru.stacked-crooked.com/a/8a6377eac0190b92>
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-07-01 17:35:08 UTC
Permalink
Post by David Rodríguez Ibeas
Post by David Rodríguez Ibeas
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 <http://coliru.stacked-crooked.com/a/8a6377eac0190b92>
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.
*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
<http://coliru.stacked-crooked.com/a/ed129914f37e856f>) 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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-07-07 14:10:55 UTC
Permalink
Post by Richard Smith
Post by David Rodríguez Ibeas
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 <http://coliru.stacked-crooked.com/a/f9aa65ec53d7b0d1> 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 <http://coliru.stacked-crooked.com/a/8a6377eac0190b92>
befriends the class Outer::C and the function ::f. Why is that?
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).
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
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Rodríguez Ibeas
2015-07-07 17:09:44 UTC
Permalink
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
Post by Belloc
Post by Richard Smith
Post by David Rodríguez Ibeas
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 <http://coliru.stacked-crooked.com/a/f9aa65ec53d7b0d1> 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 <http://coliru.stacked-crooked.com/a/8a6377eac0190b92>
befriends the class Outer::C and the function ::f. Why is that?
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).
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
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
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
j***@gmail.com
2015-07-07 21:32:29 UTC
Permalink
Post by Belloc
Post by Richard Smith
Post by David Rodríguez Ibeas
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 <http://coliru.stacked-crooked.com/a/f9aa65ec53d7b0d1> 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 <http://coliru.stacked-crooked.com/a/8a6377eac0190b92>
befriends the class Outer::C and the function ::f. Why is that?
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).
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
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
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?
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Rodríguez Ibeas
2015-07-07 23:39:11 UTC
Permalink
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
Post by j***@gmail.com
Post by Belloc
Post by Richard Smith
Post by David Rodríguez Ibeas
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 <http://coliru.stacked-crooked.com/a/f9aa65ec53d7b0d1> 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 <http://coliru.stacked-crooked.com/a/8a6377eac0190b92>
befriends the class Outer::C and the function ::f. Why is that?
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).
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
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
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?
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Richard Smith
2015-07-08 00:11:19 UTC
Permalink
Post by David Rodríguez Ibeas
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.

Honestly I am not 100% sure, but this is what I understood from Richard,
Post by David Rodríguez Ibeas
David
Post by j***@gmail.com
Post by Belloc
Post by Richard Smith
Post by David Rodríguez Ibeas
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 <http://coliru.stacked-crooked.com/a/f9aa65ec53d7b0d1> 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 <http://coliru.stacked-crooked.com/a/8a6377eac0190b92>
befriends the class Outer::C and the function ::f. Why is that?
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).
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
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
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?
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
j***@gmail.com
2015-07-08 12:54:32 UTC
Permalink
Post by Richard Smith
Post by David Rodríguez Ibeas
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.
Honestly I am not 100% sure, but this is what I understood from Richard,
Post by David Rodríguez Ibeas
David
Post by j***@gmail.com
Post by Belloc
Post by Richard Smith
Post by David Rodríguez Ibeas
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 <http://coliru.stacked-crooked.com/a/f9aa65ec53d7b0d1> 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 <http://coliru.stacked-crooked.com/a/8a6377eac0190b92>
befriends the class Outer::C and the function ::f. Why is that?
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).
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
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
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?
--
---
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
.
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-07-11 12:08:13 UTC
Permalink
I quoted you in this discussion
<http://stackoverflow.com/q/31348475/1042389> in SO
Post by Richard Smith
Post by David Rodríguez Ibeas
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.
Honestly I am not 100% sure, but this is what I understood from Richard,
Post by David Rodríguez Ibeas
David
Post by j***@gmail.com
Post by Belloc
Post by Richard Smith
Post by David Rodríguez Ibeas
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 <http://coliru.stacked-crooked.com/a/f9aa65ec53d7b0d1> 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 <http://coliru.stacked-crooked.com/a/8a6377eac0190b92>
befriends the class Outer::C and the function ::f. Why is that?
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).
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
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
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?
--
---
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
.
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-07-12 17:41:52 UTC
Permalink
For those who might be interested, I suggest reading this chat in SO, where
dyp <http://stackoverflow.com/users/420683/dyp> showed me the way to really
grasp what §3.3.7/1 (2) is saying.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-07-12 17:46:26 UTC
Permalink
For those who might be interested, I suggest reading this chat
<http://chat.stackoverflow.com/rooms/83046/discussion-between-dyp-and-belloc>
in SO, where dyp <http://stackoverflow.com/users/420683/dyp> showed me the
way to really grasp what §3.3.7/1 (2) is all about.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-07-11 12:07:15 UTC
Permalink
I quoted you in this discussion
<http://stackoverflow.com/q/31348475/1042389> in SO
Post by David Rodríguez Ibeas
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
Post by j***@gmail.com
Post by Belloc
Post by Richard Smith
Post by David Rodríguez Ibeas
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 <http://coliru.stacked-crooked.com/a/f9aa65ec53d7b0d1> 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 <http://coliru.stacked-crooked.com/a/8a6377eac0190b92>
befriends the class Outer::C and the function ::f. Why is that?
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).
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
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
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?
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Richard Smith
2015-06-30 18:14:54 UTC
Permalink
Post by David Rodríguez Ibeas
I am not convinced that to be the most relevant quote (it may or may not,
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; };").
Post by David Rodríguez Ibeas
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.
Post by Belloc
Post by Richard Smith
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Belloc
2015-06-26 18:20:39 UTC
Permalink
Post by David Krauss
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
<http://coliru.stacked-crooked.com/a/10dba145a6828e55>, 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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
'Johannes Schaub' via ISO C++ Standard - Discussion
2015-06-25 14:36:58 UTC
Permalink
Post by Belloc
[ 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 ]
Post by Belloc
[ 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 ]
Post by Belloc
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.
Post by Belloc
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.
--
---
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-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Continue reading on narkive:
Loading...