class 之外的函数重载未见
Function overload outside class not seen
考虑以下代码片段:
enum class Bar {
A
};
void foo(Bar) {}
struct Baz {
void foo() {
foo(Bar::A);
}
};
编译失败,gcc 9.2 的消息是:
:12:19: error: no matching function for call to 'Baz::foo(Bar)'
12 | foo(Bar::A);
|
我不怀疑这是一个错误,因为 clang 10 也失败了。关于这种情况,我有两个问题:
标准在哪里定义此类重载的行为?
以这种方式指定编译器行为的可能原因是什么?
在 Baz::foo()
中调用 foo
只会查找 class 中的名字。如果你想使用在 class Baz
之外声明的 foo
,你需要使用范围解析运算符,像这样:
enum class Bar {
A
};
void foo(Bar) {}
struct Baz {
void foo() {
::foo(Bar::A); // looks up global 'foo'
}
};
请注意,对 foo
的无范围调用失败,因为在最近的范围内找到了 Bar::foo
。如果您以不同的方式命名函数,则在 Bar
中找不到函数,编译器将在外部作用域中查找该函数。
enum class Bar {
A
};
void foo(Bar) {}
struct Baz {
void goo() { // not 'foo'
foo(Bar::A); // this is fine, since there is no 'Bar::foo' to find
}
};
这是 cppreference 对 class 定义的引述。
e) if this class is a member of a namespace, or is nested in a class that is a member of a namespace, or is a local class in a function that is a member of a namespace, the scope of the namespace is searched until the definition of the class, enclosing class, or function. if the lookup of for a name introduced by a friend declaration: in this case only the innermost enclosing namespace is considered, otherwise lookup continues to enclosing namespaces until the global scope as usual.
当然,这只适用于class定义,但是对于成员函数(这是你的例子),它说
For a name used inside a member function body, a default argument of a member function, exception specification of a member function, or a default member initializer, the scopes searched are the same as in [class definition], ...
所以同样的逻辑也适用。
根据不合格名称查找规则,从标准来看,[basic.lookup.unqual]/1,
(强调我的)
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.
这意味着名称 foo
在 class 范围内找到(即 Baz::foo
本身),然后名称查找停止;全局的将不会被发现并考虑用于稍后发生的重载解决方案。
关于你的第二个问题,函数不能通过不同的范围重载;这可能会导致不必要的混乱和复杂性。考虑以下代码:
struct Baz {
void foo(int i) { }
void foo() {
foo('A');
}
};
你知道 'A'
会被转换为 int
然后传递给 foo(int)
,没关系。如果允许通过作用域重载函数,如果某天某个人或库在全局作用域中添加了一个 foo(char)
,代码的行为就会发生变化,这非常令人困惑,尤其是当您不知道添加全球一号。
考虑以下代码片段:
enum class Bar {
A
};
void foo(Bar) {}
struct Baz {
void foo() {
foo(Bar::A);
}
};
编译失败,gcc 9.2 的消息是:
:12:19: error: no matching function for call to 'Baz::foo(Bar)'
12 | foo(Bar::A);
|
我不怀疑这是一个错误,因为 clang 10 也失败了。关于这种情况,我有两个问题:
标准在哪里定义此类重载的行为?
以这种方式指定编译器行为的可能原因是什么?
在 Baz::foo()
中调用 foo
只会查找 class 中的名字。如果你想使用在 class Baz
之外声明的 foo
,你需要使用范围解析运算符,像这样:
enum class Bar {
A
};
void foo(Bar) {}
struct Baz {
void foo() {
::foo(Bar::A); // looks up global 'foo'
}
};
请注意,对 foo
的无范围调用失败,因为在最近的范围内找到了 Bar::foo
。如果您以不同的方式命名函数,则在 Bar
中找不到函数,编译器将在外部作用域中查找该函数。
enum class Bar {
A
};
void foo(Bar) {}
struct Baz {
void goo() { // not 'foo'
foo(Bar::A); // this is fine, since there is no 'Bar::foo' to find
}
};
这是 cppreference 对 class 定义的引述。
e) if this class is a member of a namespace, or is nested in a class that is a member of a namespace, or is a local class in a function that is a member of a namespace, the scope of the namespace is searched until the definition of the class, enclosing class, or function. if the lookup of for a name introduced by a friend declaration: in this case only the innermost enclosing namespace is considered, otherwise lookup continues to enclosing namespaces until the global scope as usual.
当然,这只适用于class定义,但是对于成员函数(这是你的例子),它说
For a name used inside a member function body, a default argument of a member function, exception specification of a member function, or a default member initializer, the scopes searched are the same as in [class definition], ...
所以同样的逻辑也适用。
根据不合格名称查找规则,从标准来看,[basic.lookup.unqual]/1,
(强调我的)
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.
这意味着名称 foo
在 class 范围内找到(即 Baz::foo
本身),然后名称查找停止;全局的将不会被发现并考虑用于稍后发生的重载解决方案。
关于你的第二个问题,函数不能通过不同的范围重载;这可能会导致不必要的混乱和复杂性。考虑以下代码:
struct Baz {
void foo(int i) { }
void foo() {
foo('A');
}
};
你知道 'A'
会被转换为 int
然后传递给 foo(int)
,没关系。如果允许通过作用域重载函数,如果某天某个人或库在全局作用域中添加了一个 foo(char)
,代码的行为就会发生变化,这非常令人困惑,尤其是当您不知道添加全球一号。