请考虑以下代码:
struct A {
int propose();
};
struct A1 : A {
int propose(int);
using A::propose;
};
struct B1 : A1 {
protected:
using A1::propose;
public:
using A::propose;
};
int main() {
B1().propose();
}
让我们编译:{{1}}。
我使用GNU 4.8.1获得了以下编译器错误:
g++ -std=c++11 main.cpp
但是,此代码在AppleClang 6.0.0.6000056中编译。
我知道main.cpp: In function 'int main()':
main.cpp:2:9: error: 'int A::propose()' is inaccessible
int propose();
^
main.cpp:18:18: error: within this context
B1().propose();
中不需要using
,(在我的代码中是必要的,但我错误地过了1 B1
)。无论如何,为什么Clang编译呢?这是预期的吗?
答案 0 :(得分:11)
在[namespace.udecl]中,我们有:
当 using-declaration 将基类中的名称带入派生类范围时,成员函数和 派生类中的成员函数模板覆盖和/或隐藏成员函数和成员函数 具有相同名称的模板,参数类型列表(8.3.5),cv-qualification和ref-qualifier(如果有) in a 基类(而不是冲突)。
标准明确指出引入的名称不会与 base 类中的名称冲突。但它没有说明引入相互冲突的名字。
该部分还说:
using-declaration 是一个声明,因此可以在多个(并且只在哪里)多次重复使用 声明是允许的。 [例如:
struct B { int i; }; struct X : B { using B::i; using B::i; // error: double member declaration };
-end example]
有趣的是,在下面的例子中,GCC很乐意编译它(并打印A),而Clang允许构造一个C但拒绝对foo的调用是不明确的:
struct A {
void foo() { std::cout << "A\n"; }
};
struct B {
void foo() { std::cout << "B\n"; }
};
struct C : A, B {
using A::foo;
using B::foo;
};
int main()
{
C{}.foo();
return 0;
}
所以简短的回答是 - 我怀疑标准中没有详细说明,并且两个编译器都在做可接受的事情。我会避免为了一般的理智而编写这种代码。
答案 1 :(得分:2)
声明是合法的。
调用它是合法的,应该可以在任何地方工作,并且只能从类和派生类中调用它,并且可以从任何类中调用它。你会注意到这没什么意义。
没有规则禁止声明中的构造(从具有相同签名的两个不同基类导入名称两次),甚至在&#34; real&#34;中使用它。派生类所在的代码,并在导入后隐藏名称。
如果你不隐藏它,那么你处于一种奇怪的情况,即同一个功能A::propose
同时受到保护和公开,因为它被命名为两次(合法地)在具有不同访问控制的相同范围内。这是......不寻常的。
如果你在一个班级,一个子条款说你可以使用它:
[class.access.base] /5.1
如果 - (5.1)m作为N的成员是公共的
,则在N级命名时,可以在R点访问成员m
和propose
显然是公开的。 (它也是protected
但我们不必继续阅读该案例!)
在其他地方,我们有一个矛盾。你被告知你可以无限制地使用它[class.access] / 1(3)。而且你被告知你只能在某些情况下使用它[class.access] / 1(2)。
我不确定如何解决这种歧义。
逻辑列的其余部分:
在[namespace.udecl] / 10中我们有:
using-declaration 是一个声明,因此可以在允许多个声明的地方重复使用。
和[namespace.udecl] / 13:
由于using声明是声明,因此对同一声明区域中同名声明的限制
所以每个using X::propose;
都是声明。
[basic.scope]对作用域中同名的两个函数没有适用的限制,除了[basic.scope.class] / 1(3)之外,它声明如果声明的重新排序改变了程序,程序是不正确的。所以我们不能说后者赢了。
在[basic.scope]下,同一范围内的两个成员函数声明是合法的。但是,在[over]下,对具有相同名称的两个成员函数有限制。
[over] / 1州:
如果为同一范围内的单个名称指定了两个或更多不同的声明,则说该名称已过载
对重载有一些限制。这通常会阻止
struct foo {
int method();
int method();
};
从合法。但是:
[over.load] / 1州:
并非所有函数声明都可以重载。这里指定了那些不能重载的东西。如果程序在同一范围内包含两个这样的不可重载声明,则该程序格式不正确。 [注意:这个 限制适用于范围中的显式声明,以及通过 using-declaration (7.3.3)进行的此类声明和声明之间的限制。它不适用于由于名称查找(例如,由于使用指令)或过载分辨率(例如,对于操作员功能)而制造的功能集。 - 注意
注释显式允许通过两个 using-declarations 引入的符号被重载限制考虑!规则仅适用于范围内的两个显式声明,或范围内的显式声明和using声明之间。
对两个 using-declarations 有零限制。它们可以具有相同的名称,并且它们的签名可能与您喜欢的一样冲突。
这很有用,因为通常你可以继续隐藏它们的声明(在派生类中使用声明),并且没有任何问题[namespace.udecl] / 15:
当 using-declaration 将基类中的名称带入派生类范围时,派生类中的成员函数和成员函数模板会覆盖和/或隐藏成员函数和成员函数模板。基类中的同名,参数类型列表(8.3.5),cv-qualification和ref-qualifier(如果有)(而不是冲突)。
然而,这不是在这里完成的。然后我们调用该方法。发生过载分辨率。
见[namespace.udecl] / 16:
出于重载解析的目的,由a引入的函数 using-declaration到派生类中将被视为派生类的成员。特别是,隐式this参数应被视为指向派生类而不是基类的指针。这对函数的类型没有影响,并且在所有其他方面,函数仍然是函数的成员 基类。
因此,为了重载解析,我们必须将它们视为派生类的成员。但这里仍有3个声明:
protected:
int A::propose(); // X
int A1::propose(int); // Y
public:
int A::propose(); // Z
因此,对B1().propose()
的调用会考虑所有3个声明。 X
和Z
都相同。但是,它们指的是相同的函数,如果选择了两个不同的函数,则重载决策表明存在歧义。所以结果并不含糊。可能存在访问控制违规行为,具体取决于您阅读标准的方式。
[over.match] / 3
如果存在最佳可行功能并且是唯一的,则重载决策成功并将其作为结果产生。否则重载解析失败并且调用格式错误。当重载决策成功,并且在使用它的上下文中无法访问最佳可行功能(第11条)时,程序就会形成错误。