31

I have two base classes with using clauses

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


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

I then declare a class

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

the compiler then flags an error reference to 'NetworkPacket' is ambiguous 'sendNetworkPacket(NetworkPacket &... '

Now both 'using clauses' resolve to the same underlying class Networking:NetworkPacket

and in fact if I replace the method declaration with:

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

it compiles fine.

Why is the compiler treating each using clause as a distinct type even though they both point to the same underlying type. Is this mandated by the standard or do we have a compiler bug ?

Matteo Tassinari
  • 18,121
  • 8
  • 60
  • 81
Andrew Goedhart
  • 937
  • 9
  • 17
  • It seems compiler not clever enough – idris Feb 12 '20 at 10:09
  • The point being that compiler at this point just knows that there exist three `NetworkPacket` - in MultiCmdQueueCallback, in PlcMsgFactoryImplCallback, in Networking. Which one to use should be specified. And I don't think putting `virtual` is going to be of any help here. – theWiseBro Feb 12 '20 at 10:10
  • @idris: instead, you meant standard is not enough permissive. compilers are right to follow standard. – Jarod42 Feb 12 '20 at 10:12
  • @Jarod42 In below answer 'synonym for the type denoted by type-id' so if they have same type-id it can be allowed to use both. whether standart or compiler, it just seems someones actually not clever enough. – idris Feb 12 '20 at 10:15
  • one of the problems of multi-inheritance – eagle275 Feb 13 '20 at 09:21

4 Answers4

27

Before looking at alias resulting type, (and accessibility)

we look at names

and indeed,

NetworkPacket might be

  • MultiCmdQueueCallback::NetworkPacket
  • or PlcMsgFactoryImplCallback::NetworkPacket

The fact they both point to Networking::NetworkPacket is irrelevant.

We do first name resolution, which results in ambiguity.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Actually this is only partially true If I add a using to PlcNetwork: | using NetworkPacket = MultiCmdQueueCallback::NetworkPacket; I get a compiler error because the previous using clause is private. – Andrew Goedhart Feb 12 '20 at 10:41
  • @AndrewGoedhart Not a contradiction. Name lookup starts in own class first. As compiler then finds a unique name there already, it is satisfied. – Aconcagua Feb 12 '20 at 10:42
  • My problem here is why is the name propogating from a private naming clause in the base class. If I remove one of the private declarations i.e. so one of the base classes has a private using clause and the other none, the error changes to ' Network Packet does not name a type' – Andrew Goedhart Feb 12 '20 at 11:05
  • @AndrewGoedhart: From what I understood from your modifications, I got visibility issues [Demo](https://godbolt.org/z/U3oCoA), as expected. – Jarod42 Feb 12 '20 at 11:11
  • Yes but why is it ambiguous name if neither are accessible – Andrew Goedhart Feb 12 '20 at 11:17
  • 1
    @AndrewGoedhart Name lookup (obviously) doesn't consider accessibility. You get the same error if you make one public and the other one private. That's the first error to be discovered, so that's the first error to be printed. If you remove one alias, then the problem of ambiguity is gone, but the one of inacessibility remains, so you get the next error printed. By the way, not a good error message (MSVC once more?), GCC is more is more precise about: `error: [...] is private within this context`. – Aconcagua Feb 12 '20 at 11:28
  • actually it was gcc ;-) – Andrew Goedhart Feb 12 '20 at 11:32
  • 1
    @AndrewGoedhart Consider the following: `class A { public: void f(char, int) { } private: void f(int, char) { } }; void demo() { A a; a.f('a', 'd'); }` – not the same, but overload resolution works alike: Consider all available functions, only after selecting the appropriate one consider accesibility... In given case, you get ambiguity as well; if you change the privat function to accept two chars, it will be selected albeit being private – and you run into next compilation error. – Aconcagua Feb 12 '20 at 11:35
14

You simply can resolve the ambiguity by manually selecting which one you want to use.

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

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

}

The compiler only looks for the definitions in the base classes. If the same type and or alias is present in both base classes it simply complains that it does not know which one to use. It doesn't matter if the resulting type is the same or not.

The compiler only looks for names in the first step, fully independent if this name is a function, type, alias, method or whatever. If names are ambiguous no further action is done from the compiler! It simply complains with the error message and stops. So simply resolve the ambiguity with the given using statement.

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • Have some doubts about the wording. If it looks up the *definitions*, wouldn't it consider the type then as well? Wouldn't it only look up *names* only (and forget about how defined)? Some reference to the standard would be great... – Aconcagua Feb 12 '20 at 10:36
  • This last comment is what explains the *why* correctly. Replace the last paragraph with this comment and I'll upvote ;) – Aconcagua Feb 12 '20 at 10:41
  • I cannot accept – I'm not the question author... Sorry if I might have gotten on your nerves. Just trying to improve the answer, as I felt it didn't answer the QA's core question before... – Aconcagua Feb 12 '20 at 10:45
  • @Aconcagua: Ubs, my fault :-) Thanks for improvement! – Klaus Feb 12 '20 at 10:46
  • Actually this doesn't work because both using clauses are private. If I add a using to PlcNetwork: | using NetworkPacket = MultiCmdQueueCallback::NetworkPacket; I get a compiler error because the previous using clause is private. By the way if I make the one base class using clause public and the other private, I still get an ambiguity error. I get ambiguity errors on methods that are not defined in the base class. – Andrew Goedhart Feb 12 '20 at 10:50
  • @AndrewGoedhart: Quite clear! If you want to use it in derived class you have to make them `protected` or `public`. That is the same as for methods or data members. Nothing special! You don't get "still" ambiguity errors, if you add the using line or your compiler is broken :-). "I get ambiguity errors on methods that are not defined in the base class. " That makes no sense for me, can you edit your question and provide the exact error message there please! – Klaus Feb 12 '20 at 10:58
  • @AndrewGoedhart Ambiguity and non-accessibility are two orthogonal problems, they have nothing to do one with another... – Aconcagua Feb 12 '20 at 10:59
  • Okay so what is the name look up that is happening. If I remove the using clause from one of the base classes. The error changes to NetworkPacket does not name a type because the remaining base class using is private. – Andrew Goedhart Feb 12 '20 at 11:13
  • If its not accessible how come its ambiguous – Andrew Goedhart Feb 12 '20 at 11:14
8

From the docs:

A type alias declaration introduces a name which can be used as a synonym for the type denoted by type-id. It does not introduce a new type and it cannot change the meaning of an existing type name.

Although, those two using clauses represent the same type, compiler has two choices in the following situation:

void sendNetworkPacket(const NetworkPacket &pdu);

It can choose between:

  • MultiCmdQueueCallback::NetworkPacket and
  • PlcMsgFactoryImplCallback::NetworkPacket

because it inherits from both MultiCmdQueueCallback and PlcMsgFactoryImplCallback base classes. A result of compiler's name resolution is ambiguity error that you have. In order to fix this, you need to explicitly instruct the compiler to use one or another like this:

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

or

void sendNetworkPacket(const PlcMsgFactoryImplCallback::NetworkPacket &pdu);
NutCracker
  • 11,485
  • 4
  • 44
  • 68
  • To be honest, I don't feel satisfied... They are both synonyms for the same type. I can easily have `class C { void f(uint32_t); }; void C::f(unsigned int) { }` (provided the alias matches). So why a difference here? They still are the same type, confirmed by your citation (which I don't consider sufficient to explain)... – Aconcagua Feb 12 '20 at 10:20
  • @Aconcagua: Using the base type or the alias one makes never a difference. An alias is never a new type. Your observation has nothing to do with the ambiguity you generate by give the SAME alias in two base classes. – Klaus Feb 12 '20 at 10:24
  • 1
    @Aconcagua I think example you mentioned is not the right equivalent for the situation from the question – NutCracker Feb 12 '20 at 10:26
  • Well, let's change a bit: Let's name the classes A, B and C and the typedef D, then you even can do: `class C : public A, public B { void f(A::D); }; void C::f(B::D) { }` – at least GCC accepts. – Aconcagua Feb 12 '20 at 10:27
  • Question author literally asked *'Why is the compiler treating each using clause as a distinct type even though they both point to the same underlying type?'* – and I do not see how the citation would clarify the *why*, instead, it just confirms the confusion of the QA... Don't want to say the answer is *wrong*, but it doesn't clarify sufficiently in my eyes... – Aconcagua Feb 12 '20 at 10:28
  • @Aconcagua: Please look at my answer! In your comment example you explicitly name the typefef/using from a SINGLE base, so you select ONE of multiple which exactly is the solution I already write in my answer. `B::D` selects D from B and resolves ambiguity A::D vs B::D. – Klaus Feb 12 '20 at 10:30
  • @Klaus Your answer goes more into the direction I'd expect as valid answer... – Aconcagua Feb 12 '20 at 10:35
  • @Aconcagua please take a look at the example [here](https://godbolt.org/z/tDQ9ma) – NutCracker Feb 12 '20 at 10:36
  • @Klaus: or add other answer. (As I mostly agree with Aconcagua, that that answer mostly give argument about OP's confusion, I added mine about **name resolution**). – Jarod42 Feb 12 '20 at 10:47
  • @NutCracker That last example is totally different, there are two entirely different functions that just happen to have the same signature, but might behave totally differently. That's not comparable. – Aconcagua Feb 12 '20 at 10:57
2

There are two errors:

  1. Accessing private type aliases
  2. Ambiguous reference to type aliases

private-private

I don't see a problem that compiler complains about the second problem first because the order doesn't really matter - you have to fix both issues to proceed.

public-public

If you change the visibility of both MultiCmdQueueCallback::NetworkPacket and PlcMsgFactoryImplCallback::NetworkPacket to either public or protected, then the second issue (ambiguity) is obvious - those are two different type aliases although they have the same underlying data type. Some may think that a "clever" compiler can solve this (a specific case) for you, but keep in mind that the compiler needs to "think in general" and make decisions based on global rules instead of making case-specific exceptions. Imagine the following case:

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


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

Should the compiler treat both NetworkPacketID the same? Sure not. Because on a 32-bit system, size_t is 32-bit long while uint64_t is always 64-bit. But if we want the compiler to check for underlying data types, then it couldn't distinguish those on a 64-bit system.

public-private

I believe this example doesn't make any sense in OP's use-case, but since here we are solving problems in general, let's consider that:

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

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

I think in this case the compiler should treat PlcNetwork::NetworkPacket as PlcMsgFactoryImplCallback::NetworkPacket because it has no other choises. Why it still refuses to do so and blames on ambiguity is a mistery to me.

PooSH
  • 601
  • 4
  • 11
  • "Why it still refuses to do so and blames on ambiguity is a mistery to me." In C++, name lookup (visibility) precedes access checking. IIRC, I read somewhere that the rationale is that changing a name from private to public shouldn't break existing code, but I'm not entirely sure. – L. F. Feb 13 '20 at 04:37