Consider the following code snippet:
class A
{
int b[A::a]; //1, error
void foo(){ int b = A::a; } //2, ok
static const int a = 5;
}
Clause 3.4.3.1/1 (Qualified name lookup, class members) said:
If the nested-name-specifier of a qualified-id nominates a class, the
name specified after the nested-name-specifier is looked up in the
scope of the class (10.2)
This implies that the name a after the nested-name-specifier both in //1 and in //2 will be looked up in the class scope.
Clause 10.2 (Member name lookup) said:
10.2/2
The following steps define the result of name lookup for a member name
f in a class scope C.
10.2/3
The lookup set for f in C, called S(f, C)...
S(f, C) is calculated as follows:
10.2/4
If C contains a declaration of the name f, the declaration set
contains every declaration of f declared in C that satisfies the
requirements of the language construct in which the lookup occurs.
The following is unclear for me:
From the quotes I cited implies that for both //1 and //2 the same member lookup rules shall be applied. But actually its a different. Why is my reasoning wrong?
Note: I know about unqualified name lookup rules into the class scope. And I understood that behavior in the following code snippet:
class A
{
int b[a]; //error
void foo(){ int b = a; } //ok
static const int a = 5;
}
It is because that behavior described in the sections 3.4.1/7 and 3.4.1/8 (Unqualified name lookup).
The error is because when int b[A::a]; is being processed, A does not yet have a symbol a. At that point of compilation, A is still incomplete because we have not reached the closing } of the class definition yet. The compiler doesn't "look ahead" to see if future lines of source code contain a definition of a.
You can see this by reversing the order of the lines:
class A
{
static const int a = 5;
int b[A::a]; // OK
};
The function definition does not have the same problem because inline function bodies are not compiled until after compilation of the class definition. (Sorry, I don't have standard references handy for this)
The member declaration int b[A::a]; is not in the potential scope of A::a (3.3.7p1), while the body of void A::foo() is in the potential scope (3.3.7p1b1):
1) The potential scope of a name declared in a class consists not only of the declarative region following the name's point of declaration, but also of all function bodies, brace-or-equal-initializers of non-static data members, and default arguments in that class (including such things in nested classes).
3.4.3.1p1 references the potential scope rule in a note:
[...] [ Note: A class member can be referred to using a qualified-id at any point in its potential scope (3.3.7). — end note ] [...]
Of course, notes are non-normative, so the conclusion must be that the potential scope rule is implied by other material in the standard. I believe this other material is specifically 3.3.2p5:
After the point of declaration of a class member, the member name can be looked up in the scope of its class. [...]
By implication, prior to the point of declaration of that class member, that member name cannot be looked up in the scope of that class.
Related
Consider a example in the standard
Example
template<class T> struct A {
typedef int M;
struct B {
typedef void M;
struct C;
};
};
template<class T> struct A<T>::B::C : A<T> {
M m; // OK, A<T>::M
};
The comment says M refer to A<T>::M, I doubt with this, because of these rules:
temp.dep#3
In the definition of a class or class template, the scope of a dependent base class is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.
That means a name in the scope of dependent base class is never be considered during unqualified name lookup.
Name M is an unqualified name. Hence M declared in A<T> is not considered.
Then according to the rule for unqualified name lookup, that is:
basic.lookup.unqual#8
For the members of a class X, a name used in a member function body, in a default argument, in a noexcept-specifier, in the brace-or-equal-initializer of a non-static data member, or in the definition of a class member outside of the definition of X, following the member's declarator-id32, shall be declared in one of the following ways:
if X is a nested class of class Y, shall be a member of 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)
Since C is a nested class of B, Hence I think lookup shall be began at B, then A, due to there's a name M in the scope of B, hence the lookup shall be stoped.
In all the cases listed in [basic.lookup.unqual], the scopes are searched for a declaration in the order listed in each of the respective categories; name lookup ends as soon as a declaration is found for the name. If no declaration is found, the program is ill-formed.
So, according to these rules, the name M within A<T>::B::C shall refer to B::M.
The outcome is here.
GCC agreed what the standard said, however clang reported an error and denoted that the type M is void. The outcome of clang consistent with my analysis. According to these reasons, I agree clang is right.
So, I wonder is it a defect? Or what something I misunderstand ?
According to C++ Defect Report Support in Clang, currently (2020-07-06) Clang does not implement the resolution of CWG591, where the paragraph with the definition of dependent base class and the Example you cite in the question was added.
Consider the following code:
#include <iostream>
struct a {
virtual void f() = 0;
};
struct b: a {
private:
void f() override {
std::cout << "b::f()\n";
}
};
struct c: b {
public:
using a::f;
};
int main() {
::c c;
c.f();
}
This compiles and works as expected with g++, clang and msvc, i.e., print b::f().
However, if I replace a and b by this:
struct a {
void f() { }
};
struct b: a {
private:
using a::f;
};
...the code does not compile anymore with gcc but compiles fines with both clang, clang-cl and msvc (thanks StoryTeller and Adrian Mole). I get the following error (on the using a::f; line in c):
error: 'void a::f()' is inaccessible within this context
I cannot find a clear point in the standard about the behavior of using a::f; (in c) in these cases, so are the above well-defined by the standard?
Note: I am not talking about bringing something in the public scope of a class (like using b::f in c if b::f was protected), but really making members from the top-level class accessible in the most-derived class after these have been made inaccessible in an intermediate base class.
I believe GCC is wrong to reject the modified code.
[namespace.udecl]
1 Each using-declarator in a using-declaration introduces a set
of declarations into the declarative region in which the
using-declaration appears. The set of declarations introduced by the
using-declarator is found by performing qualified name lookup
([basic.lookup.qual], [class.member.lookup]) for the name in the
using-declarator, excluding functions that are hidden as described
below.
3 In a using-declaration used as a member-declaration, each
using-declarator's nested-name-specifier shall name a base class of
the class being defined. If a using-declarator names a constructor,
its nested-name-specifier shall name a direct base class of the class
being defined.
So first I'd note that paragraph 3 makes a distinction between a base and a direct base. Therefore we can name a::f in a using declaration. Secondly, according to paragraph 1, name lookup proceeds as one would expect
[class.qual]
1 If the nested-name-specifier of a qualified-id nominates a
class, the name specified after the nested-name-specifier is looked up
in the scope of the class ([class.member.lookup]), except for the
cases listed below. The name shall represent one or more members of
that class or of one of its base classes (Clause [class.derived]).
[class.member.lookup]
1 Member name lookup determines the meaning of a name
(id-expression) in a class scope. Name lookup can result in an
ambiguity, in which case the program is ill-formed. For an
id-expression, name lookup begins in the class scope of this; for a
qualified-id, name lookup begins in the scope of the
nested-name-specifier. Name lookup takes place before access control.
So a::f is to be looked up only in the scope of a or its own base classes. It should not be looked up at all in b. I would argue that therefore the accessibility of the name f in b should not affect the accessibility of the name f in a when doing qualified name lookup.
In a, f is public. And so can be named by a qualified-id in any declarative region where a may be named. That includes c. And so the using declaration uses a valid name for a valid member of a base class. That is accessible in that declarative region. It is therefore valid.
As another data point, GCC has no problem with the accessibility of a::f in other uses. For example, GCC allows forming a pointer to member to a::f inside the scope of c.
struct c: b {
public:
c() {
[[maybe_unused]] auto f = &a::f;
};
};
So it clearly does not consider the name a::f inaccessible in all contexts due to b.
struct B {};
struct C : B {};
void f(B){} // worse match than A::f<C>
struct A {
template<class T>
void f(T v) {
f(v); // #1
}
};
int main()
{
A{}.f(C{});
}
Activating ADL lookup in line #1 is as simple as
{
using ::f;
f(v);
}
I think the rule that makes the code fail without the using directive is:
[basic.lookup.argdep]/3 Let X be the lookup set produced by unqualified lookup and let Y be
the lookup set produced by argument dependent lookup (defined as
follows). If X contains
(3.1) a declaration of a class member, or
(3.2) a block-scope function declaration that is not a using-declaration, or
(3.3) a declaration that is neither a function nor a function template
then Y is empty. [...]
So, since a call to f found by non-ADL lookup will find A::f, which is a class member, overloads found by ADL-lookup are discarded.
Which C++ rule allows to ignore the restriction in 3.1 with the using declaration, to make the above code compile?
I think I'm completely misunderstanding the context where the rule [basic.lookup.argdep]/3 must be applied, or maybe I have a bigger and hidden hole in my understanding of the name lookup process.
First paragraph on unqualified name lookup:
In all the cases listed in [basic.lookup.unqual], the scopes are
searched for a declaration in the order listed in each of the
respective categories; name lookup ends as soon as a declaration is
found for the name.
In particular,
For the members of a class X, a name used in a member function body […], following
the member's declarator-id, shall be declared in one of the
following ways:
before its use in the block in which it is used or in an enclosing
block ([stmt.block]), or
shall be a member of class X or be a member
of a base class of X ([class.member.lookup]), or ...
A local (re)declaration of a name is prioritised and shadows all extrinsic declarations.
I have the following simplified code
namespace Namespace
{
int foo() { return 1; }
class Class
{
public:
int foo() const { return 2; }
class Nested {
public:
Nested()
{
cout << foo() << endl;
}
};
};
}
And I got this error:
error: cannot call member function ‘int Namespace::Class::foo() const’
without object:
cout << foo() << endl;
^^^^^
It seems that compiler selects non static int Namespace::Class::foo() const instead of global function int Namespace::foo().
But how can it be expected that non-static function from other class can be called without object? Nested object has no access to surrounding Class object - this is not Java after all.
I read carefully through overload resolution from cppreference I cannot find the rationale for this behavior. I rather doubt that this is gcc error.
Can you point the language rules responsible for this behavior?
And how do you deal with such problems?
[UPDATE]
Just an answer for 2nd question. Workaround is simple, there is a need to tell compiler that such global function exists:
Nested()
{
using Namespace::foo; //< workaround: inform compiler such function exists
cout << foo() << endl;
}
BTW, is that workaround correct? Are there any better solutions?
I read carefully through overload resolution from cppreference I cannot find the rationale for this behavior. Can you point the language rules responsible for this behavior?
Before the overload resolution procedure selects the best viable function, an initial set of candidates is generated during the name lookup phase. In other words, the expected behavior should be searched for in the Name lookup section, not in the Overload resolution one.
The name lookup procedure for an unqualified name is described in the C++ standard:
§3.4.1 [basic.lookup.unqual]/p8:
A name used in the definition of a member function (9.3) of class X following the function’s declarator-id or in the brace-or-equal-initializer of a non-static data member (9.2) of class X shall be declared in one of the following ways:
— before its use in the block in which it is used or in an enclosing block (6.3), or
— shall be a member of class X or be a member of a base class of X (10.2), or
— if X is a nested class of class Y (9.7), shall be a member of 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), or [...]
and only if still not found:
— 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 use of the name, in namespace N or in one of N's enclosing namespaces.
Since the name lookup ends as soon as the name is found (§3.4.1 [basic.lookup.unqual]/p1):
In all the cases listed in 3.4.1, the scopes are searched for a declaration in the order listed in each of the respective categories; name lookup ends as soon as a declaration is found for the name.
in your case no other scopes are searched as soon as int foo() const { return 2; } is encountered.
Workaround is simple, there is a need to tell compiler that such global function exists:
using Namespace::foo; //< workaround: inform compiler such function exists
Is that workaround correct?
§7.3.3 [namespace.udecl]/p1:
A using-declaration introduces a name into the declarative region in which the using-declaration appears.
§3.3.1 [basic.scope.declarative]/p1:
Every name is introduced in some portion of program text called a declarative region, which is the largest part of the program in which that name is valid, that is, in which that name may be used as an unqualified name to refer to the same entity.
Introducing a name with a using-declaration impacts the unqualified name lookup in a way such that it finds that function in its first step, namely that name becomes declared:
— before its use in the block in which it is used or in an enclosing block (6.3)
Are there any better solutions?
One can use a qualified name when referring to a function from some namespace scope, explicitly indicating what symbol is being referred to:
Nested()
{
cout << Namespace::foo() << endl;
}
This was indeed about Name Lookup and not about Overload Resolution.
Indeed, as the standard stipulates, §3.4.1/1 of N3376 (emphasis mine):
In all the cases listed in 3.4.1, the scopes are searched for a declaration in the order listed in each of the respective categories; name lookup ends as soon as a declaration is found for the name. If no declaration is found, the program is ill-formed.
So in fact, the lookup stops as soon as a declaration is found.
Then, you can find more about the case you're dealing with in §3.4.1/8 which deals with names used in class inside a member function definition, especially this part:
if X is a nested class of class Y (9.7), shall be a member of 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)
And if not found, in enclosing namespaces.
In your case, that means that Namespace::Class::foo() is the first name found during the lookup, and it stops as soon as it has found a name, so Namespace::foo() isn't even considered.
The example given by the standard illustrates the lookup path:
class B { };
namespace M {
namespace N {
class X : public B {
void f();
};
}
}
void M::N::X::f() {
i = 16;
}
The following scopes are searched for a declaration of i:
1) outermost block scope of M::N::X::f, before the use of i // 2) scope of class M::N::X
3) scope of M::N::X’s base class B
4) scope of namespace M::N
5) scope of namespace M
6) global scope, before the definition of M::N::X::f
And as to how you deal with such problem, you have to qualify the call, i.e.
cout << Namespace::foo() << endl;
so that the compiler selects the function you want through qualified lookup.
Piotr S. and JBL explained the why of your problem.
IMHO the simpler solution is to use the qualified name of the function :
public:
Nested()
{
cout << Namespace::foo() << endl;
}
I'm trying to understand what does the following quote mean (3.4.3/3 N3797):
names following the qualified-id are looked up in the scope of the
member’s class or namespace.
namespace A
{
class C
{
public:
static const int a=7;
static int b;
};
}
int A::C::b=a; //7
The scope of the static int b; consist only of the declarative region following by the b's point of declaration. Actually:
The potential scope of a name declared in a class consists not only of
the declarative region following the name’s point of declaration, but
also of all function bodies, default arguments,
exception-specifications, and brace-or-equal-initializers of
non-static data members in that class
This implies that static const int a=7; does not belong to the scope of static int b;. Hence the static const int a=7 cannot be found in the int A::C::b=a;.
It is a typo in the Standard or it is my misunderstanding?
This implies that static const int a=7; does not belong to the scope
of static int b;. Hence the static const int a=7 cannot be found in
the int A::C::b=a;.
No. It implies exactly what you can read there: The potential scope of a name declared in a class also contains function bodies etc. of non-static data members. That does not conflict with the quote above that - the declarative region (and the scope) of a static data member still contains the scope of the class it was declared in itself.
You quoted the relevant part yourself:
names following the qualified-id are looked up in the scope of the
member’s class or namespace
Therefore, since in this code-snippet
int A::C::b=a;
a is used after the declarator-id, it is looked up in the class and found.
The quote says "the scope of the member's class", not "the scope of the member"; so a is looked up in the class scope of C. It can be found there whether or not it's declared after b.
The rule you quoted (item 1 of 3.3.7p1) is intended for names used within a class:
namespace A
{
class C
{
public:
int b = a;
static const int a=7;
};
}
The rule that permits the code you listed is item 5 of that same paragraph:
5) The potential scope of a declaration that extends to or past the end of a class definition also extends to the regions defined by its member definitions, even if the members are defined lexically outside the class [...]
There is clearly some overlap between the applicability items 1 and 5 of 3.3.7p1, but that doesn't matter, as they have the same effect where they do overlap.
3.4.3p3 is saying the same as 3.3.7p1 item 5, just with different wording; it clarifies that the type appearing before the qualified-id is not part of the "region defined by [the] member definition".