C ++名称查找 - 来自标准

时间:2015-08-02 13:55:59

标签: c++ c++11 language-lawyer

我需要从标准[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)
    }
};
}

我认为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::f中的两个候选者中::f应该比::N::A::g更接近。

5 个答案:

答案 0 :(得分:2)

"类型铸造"与此场景无关。参数依赖查找的规则包括来自[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,因为它对于不合格的查找是不可见的。

答案 1 :(得分:0)

引用周围的陈述实际上非常清楚示例为什么不调用函数f

  

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

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

  

简单类型说明符(7.1.6.2)或 typename-specifier (14.6),后跟带括号的表达式列表构造给定表达式列表的指定类型的值。

显然,f是一个简单类型说明符typedef int f;安排了这种情况。删除此typedeff不再是简单类型说明符,导致找到N::A::f

答案 2 :(得分:0)

根据[class.conv] / 1(强调我的):

  

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

因此,::N::f::N::A::operator int()都是函数。但是,[namespace.memdef] / 3指定(强调我的):

  

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

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

以下代码应澄清事情。因为它无法编译(在clang中),所以当::N::f实际位于可用名称列表中时,它显示::f优先于::N::f

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

进一步说明

部分

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

既是::N::f的声明(和定义)。也就是说,它在::N::f中包含的名称列表中引入了名称::N。在评估f中的int i = f(a)时,我们会查看列表中的可用名称,然后在::N::f之前找到::f。因此f(a)类型为void,上面的代码无法使用消息“无法使用类型void初始化类型int”进行编译。也就是说,int i = f(a)即使在::N::f出现时::N::f可用,也会调用::N::A::operator int

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

答案 3 :(得分:0)

标准(n4296)表示为11.3§1(成员访问控制/朋友)一个类通过朋友声明指定其朋友(如果有的话)。这样的声明给出了 对朋友的特殊访问权限,但他们不会成为朋友的指定朋友 类。

在7.3.1.2(命名空间成员定义)§3友情声明不通过 本身使名称对非限定查找或限定查找可见

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

  1. 尝试在f之外声明N(意味着在顶级范围内)会将错误重新定义为'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. 如果声明f(在名称空间N中) <{strong> int i = f(a) 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. 如果f int i = f(a)之前声明为,则命名空间内的函数声明优先于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

  4. TL / DR:标准上的示例假定在f之前没有声明函数int i = f(a);。作为类或命名空间中的朋友声明使名称对非限定查找或限定查找可见,那时唯一可见的声明是typedef int f;。因此f被视为typedef。

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

答案 4 :(得分:0)

这里有一些事情发生。正如包含该示例的说明所述(3.4 / 3):

  

为了确定(在解析期间)表达式是否为函数调用的 postfix-expression ,通常的名称查找规则适用。 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,这是一个类型名称。所以语法是 simple-type-specifier ( expression-list opt )而不是 postfix -expression ( 表达式列表 opt ),并且依赖于参数的查找(3.4.2)不适用。< / p>

(如果非限定查找找到了一个函数名,则3.4.2将适用,并且尽管缺少声明,仍可以在候选列表中包含N::f。如果N::f有先前的声明除了friend声明之外,它将赢得不合格的查询。)