C++名称查找-来自标准的示例

C++ name lookup - example from the standard

本文关键字:标准 查找 C++      更新时间:2023-10-16

我需要一个来自标准〔basic.lookup.uqual〕/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)
    }
};
}

我认为void N::f(A &)int(a)更接近,因为它不涉及类型转换运算符。但是我不能确定,因为标准只包含一个"类型转换"实例。


顺便说一句,在MSVC2015中编译该代码失败(但它在clang和g++中有效(。

错误C2440"初始化":无法从"void"转换为"int">


更新处理一些评论。

  1. 类型铸造在形式上被称为"类型转换",它们在(12.3(中有介绍(感谢jefffrey(。

  2. 我要找的是语法解析的描述。特别是为什么postfix-expression ( expression-list opt )会被simple-type-specifier ( expression-list opt )践踏。由于根据(5.2(,这两个表达式都是从左到右计算的。因此,在(a)之前的两个候选中,当评估::N::A::g中的表达式时,::N::f应该比::f更接近。

"类型转换"与此场景无关。与参数相关的查找规则包括,from〔basic.lookup.argdep〕:

设X为不合格查找(3.4.1.(产生的查找集,设Y为依赖于参数的查找(定义如下(。如果X包含
(3.1(-类成员的声明,或
(3.2(--不是using声明的块作用域函数声明,或
(3.3(--既不是函数也不是函数模板的声明
则Y为空。否则,Y是在与参数类型相关联的命名空间中找到的声明集,如下所述。通过查找名称找到的声明集是X和Y

f的非限定查找生成的查找集为:

typedef int f;

该声明既不是函数,也不是函数模板,因此Y为空。我们不考虑友元函数f,因为它对于非限定查找是不可见的。

这里有一些事情。正如包含示例的注释所说(3.4/3(:

为了确定(在解析过程中(表达式是否是函数调用的后缀表达式,应用通常的名称查找规则。3.4.2中的规则对表达式的句法解释没有影响。

因此,首先我们需要知道f简单类型说明符还是后缀表达式,使用不包含第3.4.2节的名称查找规则。在这些规则下,函数N::f是不可见的。

7.3.1.2/3:

如果非本地类中的friend声明首先声明了一个类、函数、类模板或函数模板,则友元是最内部封闭命名空间的成员。friend声明本身并不能使名称对非限定查找(3.4.1(或限定查找(3.4.3(可见

因此,非限定查找根本看不到N::f的任何声明,只找到::f,这是一个类型名。因此,语法是简单类型说明符(表达式列表opt),而不是后缀表达式(表达列表opt),与参数相关的查找(3.4.2(不适用。

(如果非限定查找找到了一个函数名,则3.4.2将适用,并且能够在候选列表中包括N::f,尽管没有声明。如果N::f以前有friend声明以外的声明,则它将赢得非限定查找。(。(

引号周围的语句实际上非常清楚为什么示例不调用函数f:

因为表达式不是函数调用,所以不应用依赖于参数的名称查找(3.4.2(,也找不到友元函数f。

实际问题是为什么不应用依赖于参数的查找。似乎5.2.3【expr.type.conv】第1段适用:

简单类型说明符(7.1.6.2(或类型名说明符达式列表构造给定表达式列表的指定类型的值。

很明显,f是一个简单类型说明符typedef int f;会安排这种情况。删除此typedef,并且f不再是简单类型说明符,从而导致找到N::A::f

根据[class.conv]/1(emphasis mine(:

类对象的类型转换可以由构造函数和转换函数指定。

因此,::N::f::N::A::operator int()都是函数。然而,[namespace.membedef]/3指定(emphasis mine(:

如果非本地类中的友元声明首先声明类、函数、类模板或函数模板97,则友元是最内部封闭命名空间的成员友元声明本身不会使名称对非限定查找可见(3.4.1(或限定查找(3.4.3(。

由于::N::f在查找名称时不在画面中,因此最接近的是::f,然后它变为::N::A::operator int()


下面的代码应该澄清一些事情。因为它无法编译(在clang中(,所以当::N::f实际上在可用名称列表中时,它表明::N::f::f具有优先权。

#include <boost/type_index.hpp>
#include <iostream>
typedef int f;
namespace N {
struct A;
void f( A & )
{
    std::cout << "::N::fn";
}
struct A {
    friend void f(A &);
    operator int() { std::cout << "Yes, I am a functionn"; 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 );
}

进一步解释

零件

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

既是::N::f的声明(也是定义(。也就是说,它在包含在::N中的名称列表中引入名称::N::f。在评估int i = f(a)中的f时,我们查看可用名称列表,发现::N::f位于::f之前。因此,f(a)是void类型,并且上面的代码编译失败,并显示消息"cannot initialize a type int with a type void"。也就是说,即使在存在::N::A::operator int的情况下,当::N::f可用时,int i = f(a)也调用::N::f

另一方面,如果我们移除::N::f定义部分,则名称::N::f仅在友元声明中引入时存在。由于这样的名称不能是查找的结果,因此f(a)中的f是下一个可用的名称,即全局typedef ::f。既然我们知道f(a)中的f是int,我们就可以调用函数::N::A::operator int

Standard(n4296(称为11.3§1(成员访问控制/好友(类通过好友声明指定其好友(如果有的话(。此类声明对好友的特殊访问权限,但他们不让指定的好友成为交友的成员类

在7.3.1.2(命名空间成员定义(§3中,友元声明不符合本身使名称对非限定查找或限定查找可见.

我稍微修改了你的例子,让它更容易看到实际发生的事情:

  1. 任何在N之外声明f的尝试(意味着在顶级范围(都会导致错误将"f"重新定义为不同类型的符号,无论是在namespace N块之前还是之后

    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. 如果在int i = f(a)之后声明f(在命名空间N中(f作为int转换:

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

    输出2

  3. 如果在int i = f(a)之前声明f,则命名空间内的函数声明优先于int转换:

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

    输出4

TL/DR:标准中的示例假设在int i = f(a);之前没有函数f的声明。由于类或命名空间中的友元声明不会使名称对非限定查找或限定查找可见,因此当时唯一可见的声明是typedef int f;。因此以f作为typedef。

但是,如果名称空间中有函数f的声明,它将优先于typedef,因为在N范围中它隐藏了顶级声明。