ADL lookup inside a class (template) member function body - c++

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.

Related

Is it a defect when the base class is a dependent type

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.

Why doesn't function declared inside other function participate in argument dependent lookup?

Consider a simple example:
template <class T>
struct tag { };
int main() {
auto foo = [](auto x) -> decltype(bar(x)) { return {}; };
tag<int> bar(tag<int>);
bar(tag<int>{}); // <- compiles OK
foo(tag<int>{}); // 'bar' was not declared in this scope ?!
}
tag<int> bar(tag<int>) { return {}; }
Both [gcc] and [clang] refuses to compile the code. Is this code ill-formed in some way?
foo(tag<int>{}); triggers the implicit instantiation of a specialization of the function call operator member function template of foo's closure type with the template argument tag<int>. This creates a point of instantiation for this member function template specialization. According to [temp.point]/1:
For a function template specialization, a member function template
specialization, or a specialization for a member function or static
data member of a class template, if the specialization is implicitly
instantiated because it is referenced from within another template
specialization and the context from which it is referenced depends on
a template parameter, the point of instantiation of the specialization
is the point of instantiation of the enclosing specialization.
Otherwise, the point of instantiation for such a specialization
immediately follows the namespace scope declaration or definition
that refers to the specialization.
(emphasis mine)
So, the point of instantiation is immediately after main's definition, before the namespace-scope definition of bar.
Name lookup for bar used in decltype(bar(x)) proceeds according to [temp.dep.candidate]/1:
For a function call where the postfix-expression is a dependent
name, the candidate functions are found using the usual lookup rules
(6.4.1, 6.4.2) except that:
(1.1) — For the part of the lookup using unqualified name lookup
(6.4.1), only function declarations from the template definition
context are found.
(1.2) — For the part of the lookup using associated namespaces
(6.4.2), only function declarations found in either the template
definition context or the template instantiation context are found. [...]
Plain unqualified lookup in the definition context doesn't find anything. ADL in the definition context doesn't find anything either. ADL in the instantiation context, according to [temp.point]/7:
The instantiation context of an expression that depends on the
template arguments is the set of declarations with external linkage
declared prior to the point of instantiation of the template
specialization in the same translation unit.
Again, nothing, because bar hasn't been declared at namespace scope yet.
So, the compilers are correct. Moreover, note [temp.point]/8:
A specialization for a function template, a member function template,
or of a member function or static data member of a class template may
have multiple points of instantiations within a translation unit, and
in addition to the points of instantiation described above, for any
such specialization that has a point of instantiation within the
translation unit, the end of the translation unit is also considered
a point of instantiation. A specialization for a class template has
at most one point of instantiation within a translation unit. A
specialization for any template may have points of instantiation in
multiple translation units.
If two different points of instantiation give a template specialization different meanings according to the one-definition rule
(6.2), the program is ill-formed, no diagnostic required.
(emphasis mine)
and the second part of [temp.dep.candidate]/1:
[...] If the call would be ill-formed or would find a better match had
the lookup within the associated namespaces considered all the
function declarations with external linkage introduced in those
namespaces in all translation units, not just considering those
declarations found in the template definition and template
instantiation contexts, then the program has undefined behavior.
So, ill-formed NDR or undefined behavior, take your pick.
Let's consider the example from your comment above:
template <class T>
struct tag { };
auto build() {
auto foo = [](auto x) -> decltype(bar(x)) { return {}; };
return foo;
}
tag<int> bar(tag<int>) { return {}; }
int main() {
auto foo = build();
foo(tag<int>{});
}
Lookup in the definition context still doesn't find anything, but the instantiation context is immediately after main's definition, so ADL in that context finds bar in the global namespace (associated with tag<int>) and the code compiles.
Let's also consider AndyG's example from his comment above:
template <class T>
struct tag { };
//namespace{
//tag<int> bar(tag<int>) { return {}; }
//}
auto build() {
auto foo = [](auto x) -> decltype(bar(x)) { return {}; };
return foo;
}
namespace{
tag<int> bar(tag<int>) { return {}; }
}
int main() {
auto foo = build();
foo(tag<int>{});
}
Again, the instantiation point is immediately after main's definition, so why isn't bar visible? An unnamed namespace definition introduces a using-directive for that namespace in its enclosing namespace (the global namespace in this case). This would make bar visible to plain unqualified lookup, but not to ADL according to [basic.lookup.argdep]/4:
When considering an associated namespace, the lookup is the same as
the lookup performed when the associated namespace is used as a
qualifier (6.4.3.2) except that:
(4.1) — Any using-directives in the associated namespace are
ignored. [...]
Since only the ADL part of the lookup is performed in the instantiation context, bar in the unnamed namespace is not visible.
Commenting out the lower definition and uncommenting the upper one makes bar in the unnamed namespace visible to plain unqualified lookup in the definition context, so the code compiles.
Let's also consider the example from your other comment above:
template <class T>
struct tag { };
int main() {
void bar(int);
auto foo = [](auto x) -> decltype(bar(decltype(x){})) { return {}; };
tag<int> bar(tag<int>);
bar(tag<int>{});
foo(tag<int>{});
}
tag<int> bar(tag<int>) { return {}; }
This is accepted by GCC, but rejected by Clang. While I was initially quite sure that this is a bug in GCC, the answer may actually not be so clear-cut.
The block-scope declaration void bar(int); disables ADL according to [basic.lookup.argdep]/3:
Let X be the lookup set produced by unqualified lookup (6.4.1) 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. [...]
(emphasis mine)
Now, the question is whether this disables ADL in both the definition and instantiation contexts, or only in the definition context.
If we consider ADL disabled in both contexts, then:
The block-scope declaration, visible to plain unqualified lookup in the definition context, is the only one visible for all instantiations of the closure type's member function template specializations. Clang's error message, that there's no viable conversion to int, is correct and required - the two quotes above regarding ill-formed NDR and undefined behavior don't apply, since the instantiation context doesn't influence the result of name lookup in this case.
Even if we move bar's namespace-scope definition above main, the code still doesn't compile, for the same reason as above: plain unqualified lookup stops when it finds the block-scope declaration void bar(int); and ADL is not performed.
If we consider ADL disabled only in the definition context, then:
As far as the instantiation context is concerned, we're back to the first example; ADL still can't find the namespace-scope definition of bar. The two quotes above (ill-formed NDR and UB) do apply however, and so we can't blame a compiler for not issuing an error message.
Moving bar's namespace-scope definition above main makes the code well-formed.
This would also mean that ADL in the instantiation context is always performed for dependent names, unless we have somehow determined that the expression is not a function call (which usually involves the definition context...).
Looking at how [temp.dep.candidate]/1 is worded, it seems to say that plain unqualified lookup is performed only in the definition context as a first step, and then ADL is performed according to the rules in [basic.lookup.argdep] in both contexts as a second step. This would imply that the result of plain unqualified lookup influences this second step as a whole, which makes me lean towards the first option.
Also, an even stronger argument in favor of the first option is that performing ADL in the instantiation context when either [basic.lookup.argdep]/3.1 or 3.3 apply in the definition context doesn't seem to make sense.
Still... it may be worth asking about this one on std-discussion.
All quotes are from N4713, the current standard draft.
From unqualified lookup rules ([basic.lookup.unqual]):
For the members of a class X, a name used in a member function body, [...], shall be declared in one of the
following ways
— if X is a local class or is a nested class of a local class, before the definition of class X in a block
enclosing the definition of class X
Your generic lambda is a local class within main, so to use bar, the name bar must appear in a declaration beforehand.

Nested class strange function lookup: surrounding class functions hide global functions

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;
}

Overload Resolution: How is this not ambiguous?

Suppose we have this code, copied from a separate question:
namespace x
{
void f()
{
}
class C
{
void f()
{
using x::f;
f(); // <==
}
};
}
The name f on the indicated line unambiguously refers to x::f (at least according to both gcc and clang). Why is x::f preferred over x::C::f in this case? Shouldn't it be ambiguous as both names are visible?
Because the using declaration brings x::f into the scope of f, which is narrower than that of C. Unqualified lookup considers the local block scope, finds a match, and stops before considering the wider class scope. There is no argument-dependent lookup since there are no function arguments, so no further scopes are considered.
#MikeSeymour's answer is spot on; here are the relevant standard quotes (C++11, emphasis mine):
13.3.1.1.1/3:
In unqualified function calls, the name is not qualified by an -> or . operator and has the more general form
of a primary-expression. The name is looked up in the context of the function call following the normal rules
for name lookup in function calls (3.4). The function declarations found by that lookup constitute the set of
candidate functions. Because of the rules for name lookup, the set of candidate functions consists (1) entirely
of non-member functions or (2) entirely of member functions of some class T. ...
3.4.1/1:
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.
3.4.1/8
A name used in the definition of a member function (9.3) of class X following the function'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 (6.3), or
shall be a member of class X or be a member of a base class of X (10.2), or
...
From 3.4.1/8, we see that a declaration for the name f (such as the declaration using x::f;) in the block in which it's used is listed earlier than f as the member of class C. As per 3.4.1/1, the earlier one is chosen, so the entire lookup resolves to x::f introduced by the using declaration.
I think that these quotes from the C++ Standard will be relevant:
From the C++ Standard (7.3.3 The using declaration)
13 Since a using-declaration is a declaration, the restrictions on
declarations of the same name in the same declarative region (3.3)
also apply to using-declarations.
And (3.3.7 Class scope)
4) A name declared within a member function hides a declaration of the
same name whose scope extends to or past the end of the member
function’s class.

I need some clarification regarding this example on Stroustrup's new book about ADL

I reproduce below the argument-dependent lookup (ADL) example given in pages 396 and 397 of Stroustrup book (4th edition):
namespace N {
struct S { int i; };
void f(S);
void g(S);
void h(int);
};
struct Base {
void f(N::S);
};
struct D : Base {
void mf(N::S);
void g(N::S x)
{
f(x); // call Base::f()
mf(x); // call D::mf()
h(1); // error: no h(int) available
}
};
What the comments say above is correct (I've tested it), but that doesn't seem to agree with what the author says in the next paragraph:
In the standard, the rules for argument-dependent lookup are phrased
in terms of associated namespaces (iso §3.4.2). Basically:
If an argument is a class member , the associated namespaces are the class itself (including its base classes) and the class's
enclosing namespaces.
If an argument is a member of a namespace, the associated namespaces are the enclosing namespaces.
If an argument is a built-in type, there are no associated namespaces.
In the example, x, which has type N::S is not a member of class D, nor of its base Base. But it's a member of namespace N. According to the second bullet above, the function N::f(S) should be the one called, instead of Base::f().
The result above also doesn't seem to agree with the second bullet in paragraph 3.4.2p2 in the Standard, which says:
If T is a class type (including unions), its associated classes are:
the class itself; the class of which it is a member, if any; and its
direct and indirect base classes. Its associated namespaces are the
namespaces of which its associated classes are members. Furthermore,
if T is a class template specialization, its associated namespaces and
classes also include: the namespaces and classes associated with the
types of the template arguments provided for template type parameters
(excluding template template parameters); the namespaces of which any
template template arguments are members; and the classes of which any
member templates used as template template arguments are members.
3.4.2/3 Let X be the lookup set produced by unqualified lookup (3.4.1) and let Y be the lookup set produced by argument dependent
lookup (defined as follows). If X contains
a declaration of a class member, or
a block-scope function declaration that is not a using-declaration, or
a declaration that is neither a function or a function template
then Y is empty. Otherwise...
So basically, ADL doesn't kick in when the ordinary lookup finds a member function or local (block-scope) function declaration (or something that's not a function). It does kick in when the ordinary lookup finds a stand-alone namespace-scope function, or when it finds nothing at all.