C++ name lookup - example from the standard

时光毁灭记忆、已成空白 提交于 2020-05-27 18:18:42

问题


I need an explanation on this example from standard [basic.lookup.unqual]/3:

typedef int f;
namespace N {
struct A {
    friend void f(A &);
    operator int();
    void g(A a) {
        int i = f(a); // f is the typedef, not the friend
                      // function: equivalent to int(a)
    }
};
}

I would argue that void N::f(A &) is closer than int(a) because it does not involve the type-cast operator. I cannot however be sure because the standard contains only one instance of "type cast".


By the way, compiling that code fails in MSVC2015 (but it works in clang and g++).

Error C2440 'initializing': cannot convert from 'void' to 'int'


UPDATE Addressing some of the comments.

  1. Type casting is formally known as "type conversions" and they are covered in (12.3) (thanks jefffrey).

  2. What I am looking for is a description of the syntax parsing. In particular, why postfix-expression ( expression-list opt ) is trampled over by simple-type-specifier ( expression-list opt ). Since according to (5.2), both of those expression are evaluated left-to-right. Hence, out of the two candidates to be before the (a), ::N::f should be closer than ::f when evaluating expressions in ::N::A::g.


回答1:


There are a few things going on here. As the note containing the example says (3.4/3):

For purposes of determining (during parsing) whether an expression is a postfix-expression for a function call, the usual name lookup rules apply. The rules in 3.4.2 have no effect on the syntactic interpretation of an expression.

So first we need to know whether f is a simple-type-specifier or a postfix-expression, using name lookup rules that don't include section 3.4.2. And under these rules, the function N::f is not visible.

7.3.1.2/3:

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. The friend declaration does not by itself make the name visible to unqualified lookup (3.4.1) or qualified lookup (3.4.3).

Therefore the unqualified lookup does not see any declaration of N::f at all, and finds only ::f, which is a typename. So the syntax is simple-type-specifier ( expression-list opt) and not postfix-expression ( expression-list opt), and argument-dependent lookup (3.4.2) does not apply.

( If the unqualified lookup had found a function name, 3.4.2 would apply and would be able to include N::f in the candidate list despite the lack of declaration. If N::f had a previous declaration other than the friend declaration, it would have won the unqualified lookup. )




回答2:


"Type casting" has nothing to do with this scenario. The rules for argument-dependent lookup include, from [basic.lookup.argdep]:

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
(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 or a function template
then Y is empty. Otherwise Y is the set of declarations found in the namespaces associated with the argument types as described below. The set of declarations found by the lookup of the name is the union of X and Y.

The lookup set produced by unqualified lookup for f is:

typedef int f;

That declaration is neither a function nor a function template, therefore Y is empty. We do not consider the friend function f, since it is not visible to unqualified lookup.




回答3:


The statements around the quote are actually quite clear on why the example does not call the function f:

Because the expression is not a function call, the argument-dependent name lookup (3.4.2) does not apply and the friend function f is not found.

The actual question is why the argument-dependent look-up is not applies. It seems 5.2.3 [expr.type.conv] paragraph 1 applies:

A simple-type-specifier (7.1.6.2) or typename-specifier (14.6) followed by a parenthesized expression-list constructs a value of the specified type given the expression list.

Clearly, f is a simple-type-specifier: the typedef int f; arranges for that to be the case. Remove this typedef and f isn't a simple-type-specifier anymore resulting in N::A::f being found.




回答4:


According to [class.conv]/1 (emphasis mine):

Type conversions of class objects can be specified by constructors and by conversion functions.

Thus, both ::N::f and ::N::A::operator int() are functions. However, [namespace.memdef]/3 specifies (emphasis mine):

If a friend declaration in a non-local class first declares a class, function, class template or function template 97 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).

Since ::N::f is out of the picture while looking up for names, the closest is ::f which then becomes ::N::A::operator int().


The following code should clarify things. Because it fails to compile (in clang), it shows that ::N::f has preference over ::f when ::N::f is actually in the list of available names.

#include <boost/type_index.hpp>
#include <iostream>

typedef int f;
namespace N {
struct A;
void f( A & )
{
    std::cout << "::N::f\n";
}
struct A {
    friend void f(A &);
    operator int() { std::cout << "Yes, I am a function\n"; return 5; }
    void g(A a) {
        int i = f(a); // f is the typedef, not the friend
                      // function: equivalent to int(a)
        std::cout << boost::typeindex::type_id_with_cvr<decltype(f(a))>().pretty_name() << '\n';
        std::cout << i;
    }
};
}

int main()
{
    N::A a;
    a.g( a );
}

Further explanation

The part

void f( A & )
{
    std::cout << "::N::f\n";
}

is both the declaration (and the definition) of ::N::f. That is, it introduces the name ::N::f in the list of names contained in ::N. When evaluating the f in int i = f(a), we look in the list available names and we find ::N::f before ::f. Hence f(a) is type void and the code above fails to compile with the message "cannot initialize a type int with a type void". That is, int i = f(a) calls ::N::f when ::N::f is available even in the presence of ::N::A::operator int.

On the other hand, if we remove the ::N::f definition part, the name ::N::f exists only as introduced by the friend declaration. Since such a name cannot be the result of a lookup, then the f in f(a) is the next available, which is the global typedef ::f. Now that we know that the f in f(a) is int, we can call the function ::N::A::operator int.




回答5:


Standard (n4296) says as 11.3 §1 (Member access control / Friends) A class specifies its friends, if any, by way of friend declarations. Such declarations give special access rights to the friends, but they do not make the nominated friends members of the befriending class.

And at 7.3.1.2 (Namespace member definitions) §3 The friend declaration does not by itself make the name visible to unqualified lookup or qualified lookup .

I slightly modified your example to make it simpler to see what actually happens:

  1. any try to declare f outside N (meaning at top level scope) gives an error redefinition of 'f' as different kind of symbol, be it before of after the namespace N block

    typedef int f;
    
    namespace N {
    struct A {
        friend int f(A &);
        operator int();
        int g(A a) {
            int i = f(a); // f is the typedef, not the friend
                          // function: equivalent to int(a)
            return i;
        }
        int i;
        A(int i): i(i) {};
    };
    }
    
    
    int f(N::A& a) {  // Error here
        return 2*a.i;
    }
    N::A::operator int() {
        return this->i;
    }
    
  2. if f is declared (in namespace N) after int i = f(a) f is taken as the int conversion:

    #include <iostream>
    
    typedef int f;
    
    namespace N {
    struct A {
        friend int f(A &);
        operator int();
        int g(A a) {
            int i = f(a); // f is the typedef, not the friend
                          // function: equivalent to int(a)
            return i;
        }
        int i;
        A(int i): i(i) {};
    };
    
    int f(A& a) {
        return 2*a.i;
    }
    }
    
    N::A::operator int() {
        return this->i;
    }
    
    int main() {
        N::A a(2);
        std::cout << a.g(a) << std::endl;
        return 0;
    }
    

    outputs 2

  3. if f is declared before int i = f(a) the function declaration inside the namespace has precedence over int conversion:

    #include <iostream>
    
    typedef int f;
    
    namespace N {
    int f(struct A&); // <= simple DECLARATION here
    struct A {
        friend int f(A &);
        operator int();
        int g(A a) {
            int i = f(a); // f is the typedef, not the friend
                          // function: equivalent to int(a)
            return i;
        }
        int i;
        A(int i): i(i) {};
    };
    
    int f(A& a) {
        return 2*a.i;
    }
    }
    
    N::A::operator int() {
        return this->i;
    }
    
    int main() {
        N::A a(2);
        std::cout << a.g(a) << std::endl;
        return 0;
    }
    

    outputs 4

TL/DR : The example on the standard assumes that there is no declaration of the function f before int i = f(a);. As a friend declaration in a class or in a namespace does not make the name visible to unqualified lookup or qualified lookup, the only declaration that is visible at that time is typedef int f;. So f is taken as the typedef.

But if there is a declaration for the function f in the namespace, it will take precedence over the typedef, because in N scope it hides the top level declaration.



来源:https://stackoverflow.com/questions/31772588/c-name-lookup-example-from-the-standard

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!