使用"使用"两次不同编译器的解释不同

时间:2015-07-10 14:19:14

标签: c++ c++11

请考虑以下代码:

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编译呢?这是预期的吗?

2 个答案:

答案 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个声明。 XZ都相同。但是,它们指的是相同的函数,如果选择了两个不同的函数,则重载决策表明存在歧义。所以结果并不含糊。可能存在访问控制违规行为,具体取决于您阅读标准的方式。

[over.match] / 3

  

如果存在最佳可行功能并且是唯一的,则重载决策成功并将其作为结果产生。否则重载解析失败并且调用格式错误。当重载决策成功,并且在使用它的上下文中无法访问最佳可行功能(第11条)时,程序就会形成错误。