Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 304
PiotrNycz Avatar asked Dec 10 '14 13:12

PiotrNycz


3 Answers

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;
}
like image 139
Piotr Skotnicki Avatar answered Oct 12 '22 21:10

Piotr Skotnicki


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.

like image 37
JBL Avatar answered Oct 12 '22 22:10

JBL


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;
    }
like image 43
Serge Ballesta Avatar answered Oct 12 '22 22:10

Serge Ballesta