【问题标题】:Why are two using clauses resolving to the same type seen as ambigious in gcc为什么两个 using 子句解析为相同的类型在 gcc 中被视为模棱两可
【发布时间】:2020-05-27 20:24:50
【问题描述】:

我有两个带有 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' is ambiguous 'sendNetworkPacket(NetworkPacket &... '

现在两个“使用子句”都解析为同一个底层类 Networking:NetworkPacket

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

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

它编译得很好。

为什么编译器将每个 using 子句视为不同的类型,即使它们都指向相同的底层类型。这是标准规定的还是我们有编译器错误?

【问题讨论】:

  • 编译器好像不够聪明
  • 关键是编译器此时只知道存在三个NetworkPacket - 在 MultiCmdQueueCallback 中,在 PlcMsgFactoryImplCallback 中,在 Networking 中。应该指定使用哪一个。而且我不认为在这里输入virtual 会有任何帮助。
  • @idris:相反,您的意思是标准不够宽松。编译器遵循标准是正确的。
  • @Jarod42 在下面的答案中,“类型 ID 表示的类型的同义词”因此如果它们具有相同的类型 ID,则可以允许两者同时使用。无论是标准还是编译器,似乎有些人实际上还不够聪明。
  • 多继承的问题之一

标签: c++ using-declaration


【解决方案1】:

有两个错误:

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

私人-私人

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

公开-公开

如果您同时更改 MultiCmdQueueCallback::NetworkPacketPlcMsgFactoryImplCallback::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,我在某处读到理由是,将名称从私有更改为公共不应破坏现有代码,但我不完全确定。
【解决方案2】:

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

我们查看名称

确实,

NetworkPacket 可能是

  • MultiCmdQueueCallback::NetworkPacket
  • PlcMsgFactoryImplCallback::NetworkPacket

他们都指向Networking::NetworkPacket这一事实无关紧要。

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

【讨论】:

  • 实际上这只是部分正确如果我向 PlcNetwork 添加一个 using: |使用 NetworkPacket = MultiCmdQueueCallback::NetworkPacket;我得到一个编译器错误,因为前面的 using 子句是私有的。
  • @AndrewGoedhart 不矛盾。名称查找首先在自己的类中开始。因为编译器已经在那里找到了一个唯一的名称,所以它很满意。
  • 我的问题是为什么名称是从基类中的私有命名子句传播的。如果我删除其中一个私有声明,即其中一个基类具有私有 using 子句而另一个没有,则错误更改为“网络数据包未命名类型”
  • @AndrewGoedhart 名称查找(显然)不考虑可访问性。如果将一个设为公开而另一个设为私有,则会出现相同的错误。这是要发现的第一个错误,所以这是要打印的第一个错误。如果您删除一个别名,那么歧义问题就消失了,但无法访问的问题仍然存在,因此您会打印下一个错误。顺便说一句,不是一个好的错误消息(MSVC 又一次?),GCC 更准确:error: [...] is private within this context
  • @AndrewGoedhart 考虑以下内容:class A { public: void f(char, int) { } private: void f(int, char) { } }; void demo() { A a; a.f('a', 'd'); } - 不一样,但重载解决方案的工作方式相似:考虑所有可用功能,只有在选择合适的功能后才考虑可访问性......在给定的情况下,你会得到含糊不清;如果您将 privat 函数更改为接受两个字符,它将被选中,尽管它是私有的——并且您会遇到下一个编译错误。
【解决方案3】:

来自docs

类型别名声明引入了一个可以用作 type-id 表示的类型的同义词。它没有引入新的 type 并且它不能改变现有类型名称的含义。

虽然这两个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 别名而产生的歧义无关。
  • @Aconcagua 我认为您提到的示例不适合问题中的情况
  • 好吧,让我们稍微改变一下:让我们命名类 A、B 和 C 以及 typedef D,然后你甚至可以这样做:class C : public A, public B { void f(A::D); }; void C::f(B::D) { }——至少 GCC 接受。
  • 问题作者从字面上问'为什么编译器将每个 using 子句都视为不同的类型,即使它们都指向相同的底层类型?' - 我没有看到引用如何澄清为什么,相反,它只是证实了 QA 的困惑......不想说答案是错误,但它没有在我眼里还不够清楚……
【解决方案4】:

您只需手动选择要使用的选项即可解决歧义。

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

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

}

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

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

【讨论】:

  • 对措辞有些疑问。如果它查找定义,那么它不会考虑类型吗?它不会只查找 names (忘记如何定义)吗?对标准的一些参考会很棒......
  • 最后一条评论正确地解释了为什么。用这条评论替换最后一段,我会赞成;)
  • 我不能接受——我不是问题的作者...对不起,如果我让你紧张了。只是想改进答案,因为我觉得它之前没有回答 QA 的核心问题......
  • @Aconcagua: Ubs,我的错 :-) 感谢改进!
  • 实际上这不起作用,因为两个 using 子句都是私有的。如果我向 PlcNetwork 添加一个 using:|使用 NetworkPacket = MultiCmdQueueCallback::NetworkPacket;我得到一个编译器错误,因为前面的 using 子句是私有的。顺便说一句,如果我使用子句将一个基类设为公共,而将另一个基类设为私有,我仍然会遇到歧义错误。我在基类中未定义的方法上遇到歧义错误。
猜你喜欢
  • 1970-01-01
  • 2011-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多