为什么两个using子句解析为同一类型在gcc中被认为是模棱两可的


32

我有两个使用using子句的基类

 class MultiCmdQueueCallback {
  using NetworkPacket  = Networking::NetworkPacket;
  ....
 }


 class PlcMsgFactoryImplCallback {
   using NetworkPacket = Networking::NetworkPacket;
  ....
 }

然后我宣布一个课程

class PlcNetwork : 
  public RouterCallback, 
  public PlcMsgFactoryImplCallback, 
  public MultiCmdQueueCallback {
  private:
    void sendNetworkPacket(const NetworkPacket &pdu);
}

然后,编译器会标记对“ NetworkPacket”的错误引用是模糊的“ sendNetworkPacket(NetworkPacket&...”

现在,两个“ using子句”都解析为相同的基础类Networking:NetworkPacket

实际上,如果我将方法声明替换为:

 void sendNetworkPacket(const Networking::NetworkPacket &pdu);

它编译良好。

为什么编译器将每个using子句都视为一个不同的类型,即使它们都指向相同的基础类型。这是由标准强制执行的还是我们有编译器错误?


似乎编译器不够聪明
idris

关键是编译器此时只知道存在三个NetworkPacket-在MultiCmdQueueCallback,在PlcMsgFactoryImplCallback和在网络中。应该指定使用哪一个。而且我认为推杆virtual在这里不会有任何帮助。
theWiseBro

@idris:相反,您的意思是标准不够宽松。编译器有权遵循标准。
Jarod42

@ Jarod42在下面的答案中,“ type-id表示的类型的同义词”,因此,如果它们具有相同的type-id,则可以同时使用两者。无论是标准还是编译器,似乎实际上实际上还不够聪明。
idris

多继承性的问题之一
eagle275

Answers:


28

在查看别名结果类型(和可访问性)之前

我们看名字

的确,

NetworkPacket 可能

  • MultiCmdQueueCallback::NetworkPacket
  • 要么 PlcMsgFactoryImplCallback::NetworkPacket

他们俩都指出的事实Networking::NetworkPacket是无关紧要的。

我们进行名字解析,这导致歧义。


其实这只是部分正确,如果我在PlcNetwork中添加了using:使用NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; 我收到一个编译器错误,因为先前的using子句是私有的。
安德鲁·高哈特

@AndrewGoedhart不是矛盾。名称查找首先在自己的类中开始。然后,当编译器在那里已经找到一个唯一名称时,它就满足了。
阿空加瓜

我的问题是,为什么该名称从基类的私有命名子句中传播出来。如果我删除了一个私有声明,即一个基类有一个私有using子句,而另一个则没有,错误将变为“网络数据包未命名类型”
Andrew Goedhart

1
@AndrewGoedhart名称查找(显然)不考虑可访问性。如果将一个公开,将另一个公开,则会出现相同的错误。这是要发现的第一个错误,因此这是要打印的第一个错误。如果删除一个别名,模棱两可的问题就解决了,但是不可避免的问题仍然存在,因此您将打印下一个错误。顺便说一句,不是一个很好的出错信息(?MSVC一次),GCC更是更精确有关:error: [...] is private within this context
阿空加瓜

1
@AndrewGoedhart考虑以下问题:class A { public: void f(char, int) { } private: void f(int, char) { } }; void demo() { A a; a.f('a', 'd'); }–不一样,但是重载解析的工作原理相同:仅在选择了适当的功能后,才考虑所有可用的功能。考虑到可访问性... 如果将privat函数更改为接受两个字符,则将其选择为私有字符-并遇到下一个编译错误。
阿空加瓜

14

您可以通过手动选择要使用的选项来解决歧义。

class PlcNetwork : 
  public RouterCallback, 
  public PlcMsgFactoryImplCallback, 
  public MultiCmdQueueCallback {

using NetworkPacket= PlcMsgFactoryImplCallback::NetworkPacket; // <<< add this line
private:
    void sendNetworkPacket(const NetworkPacket &pdu);

}

编译器仅在基类中查找定义。如果两个基类中都存在相同的类型和/或别名,它只会抱怨它不知道使用哪个。产生的类型是否相同并不重要。

编译器仅在第一步中查找名称,如果该名称是函数,类型,别名,方法或其他内容,则它们完全独立。如果名称不明确,编译器将不执行进一步的操作!它只是抱怨错误消息并停止。因此,只需使用给定的using语句解决歧义。


对措辞有疑问。如果它查找定义,那么它也不会考虑类型吗?难道不是只查找名称(而忘记了定义)吗?对标准的一些参考将是很棒的……
空加瓜

最后一条评论正确解释了原因。用此评论替换最后一段,我将投票;)
阿空加瓜

我不能接受–我不是问题的作者...对不起,如果我不明白您的意思。我只是想改善答案,因为我觉得之前没有回答质量保证的核心问题……
空加瓜

@Aconcagua:抱歉,我的错是:-)感谢您的改进!
克劳斯

实际上这是行不通的,因为两个using子句都是私有的。如果我在PlcNetwork中添加了using:使用NetworkPacket = MultiCmdQueueCallback :: NetworkPacket; 我收到一个编译器错误,因为先前的using子句是私有的。顺便说一句,如果我将使用子句的一个基类设为public,将另一个使用私有,则仍然会出现歧义错误。我在基类中未定义的方法上得到歧义错误。
安德鲁·高哈特

8

文档

类型别名声明引入了一个名称,该名称可以用作由type-id表示的类型的同义词。它不会引入新类型,也无法更改现有类型名称的含义。

尽管这两个using子句表示相同的类型,但是在以下情况下,编译器有两种选择:

void sendNetworkPacket(const NetworkPacket &pdu);

它可以选择:

  • MultiCmdQueueCallback::NetworkPacket
  • PlcMsgFactoryImplCallback::NetworkPacket

因为它继承自基类MultiCmdQueueCallbackPlcMsgFactoryImplCallback基类。编译器名称解析的结果是您有歧义错误。为了解决此问题,您需要明确指示编译器使用如下所示的一个或另一个:

void sendNetworkPacket(const MultiCmdQueueCallback::NetworkPacket &pdu);

要么

void sendNetworkPacket(const PlcMsgFactoryImplCallback::NetworkPacket &pdu);

老实说,我不满意……它们都是同一类型的同义词。我可以轻松拥有class C { void f(uint32_t); }; void C::f(unsigned int) { }(提供别名匹配项)。那么为什么在这里有所不同呢?它们仍然是相同类型,由您的引用确认(我认为不足以解释)...
阿空加瓜

@Aconcagua:使用基本类型或别名不会有任何区别。别名绝不是新类型。您的观察与通过在两个基类中提供SAME别名所产生的歧义无关。
克劳斯

1
@Aconcagua我认为您提到的示例与问题的情况并不相称
NutCracker

好,让我们进行一些更改:让我们命名类A,B和C以及typedef D,那么您甚至可以执行以下操作:class C : public A, public B { void f(A::D); }; void C::f(B::D) { }–至少GCC接受。
阿空加瓜

问题作者直截了当地问:“为什么编译器为什么将每个using子句都视为不同的类型,即使它们都指向相同的基础类型?” –而且我看不到引用如何阐明其原因,相反,它只是证实了质量检查的混乱...不想说答案是错误的,但在我眼中并没有充分阐明。 。
阿空加瓜

2

有两个错误:

  1. 访问私有类型别名
  2. 对类型别名的含糊引用

私人-私人

我没有看到编译器首先抱怨第二个问题的问题,因为顺序并不重要-您必须修复两个问题才能继续进行。

公共-公共

如果你改变两者的知名度MultiCmdQueueCallback::NetworkPacket,并 PlcMsgFactoryImplCallback::NetworkPacket以公共或受保护的,那么第二个问题(不确定性)是显而易见的-这是两个不同类型的别名,虽然他们有相同的基本数据类型。有些人可能认为“聪明”的编译器可以为您解决此问题(特定情况),但请记住,编译器需要“一般考虑”并根据全局规则做出决定,而不是根据具体情况制定例外。想象以下情况:

class MultiCmdQueueCallback {
    using NetworkPacketID  = size_t;
    // ...
};


class PlcMsgFactoryImplCallback {
    using NetworkPacketID = uint64_t;
    // ...
};

编译器应该将两者对待NetworkPacketID吗?一定不。因为在32位系统上,size_t长度为32位,而uint64_t始终为64位。但是,如果我们希望编译器检查基础数据类型,则它无法区分64位系统上的数据类型。

公私

我相信这个示例在OP的用例中没有任何意义,但是由于这里我们通常在解决问题,因此我们考虑一下:

class MultiCmdQueueCallback {
private:
    using NetworkPacket  = Networking::NetworkPacket;
    // ...
};

class PlcMsgFactoryImplCallback {
public:
    using NetworkPacket  = Networking::NetworkPacket;
    // ...
};

我认为,在这种情况下,编译器应该把PlcNetwork::NetworkPacket作为PlcMsgFactoryImplCallback::NetworkPacket,因为它没有其他的,选项。为什么它仍然拒绝这样做并责怪歧义对我来说是个迷。


“为什么它仍然拒绝这样做,并且责怪歧义对我来说是种迷惑。” 在C ++中,名称查找(可见性)在访问检查之前。IIRC,我在某处读到,其基本原理是将名称从私有更改为公共不应破坏现有代码,但我不确定。
LF
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.