0

I am compiling on a platform where int= 32 bits (gcc arm none eabi, cortex M3, GCC version 9) with the dialect set to C++17.

I have a overloaded a method with both template versions and plain versions of the same method. The normal overloads are in a private base class but have been exposed through a using clause.

class MemoryStream {
  public:
    inline void write(bool value);
    inline void write(uint8_t value);
    inline void write(uint16_t value);
    inline void write(uint32_t value);
    inline void write(int16_t value);
    inline void write(int32_t value);
}

and

class WriteStream :private MemeoryStream {
  public:
    using MemoryStream::write;

  template<typename T>
    typename std::enable_if<std::is_integral<T>::value>::type WriteStream::write(T obj){
       MemoryStream::write(obj);
    }
}

when I call the method using a numeric literal i.e.

    txStream.write(0U);

I get the following error:

In instantiation of 'typename std::enable_if<std::is_integral<_Tp>::value>::type  WriteStream::write(T) [with T = unsigned int; typename std::enable_if<std::is_integral<_Tp>::value>::type = void]':
error: call of overloaded 'write(unsigned int&)' is ambiguous

1.) Why are the plain overloaded functions not being selected as they are being imported through the using clause and if I call

txStream.write<uint32_t>(0U);

or

constexpr uint32_t Zero =0;
txStream.write<uint32_t>(Zero);

They resolve without error ?

Is unsigned int seen a a distinct type from uint32_t ?

2.) Why is the compiler converting the numeric literal to a reference ??? as shown by the error message: 'call of overloaded write(unsigned int&) is ambiguous'

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
Andrew Goedhart
  • 937
  • 9
  • 17
  • 1
    Yes, `unsigned int` is a different type than `uint32_t` On your platform, they may have the same representation, but they're not the same type. – Marshall Clow May 10 '20 at 21:31

2 Answers2

2

An unsigned int is a distinct type from the fixed width types (even though one of those fixed with types will have the same size as unsigned int), so with your code the compiler doesn't know which overload of write should be called.

You'll need to provide overloads for unsigned int and int.

Consider the problems that could arise if on one system, with a 32 bit int, the write(uint32_t) was called, while on another system with 16 bit ints the exact same code would call write(uint16_t) (or substitute "64" for "16"). Or the corresponding chaos if one system would write a 32 bit number while another system would read a different sized one.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
  • I did some digging and I see that as of C++11 the fixed width types became actual distinct types where before they were just typedefs that where tailored to each deployment platform. – Andrew Goedhart May 10 '20 at 21:21
  • @AndrewGoedhart: I can’t find any statement to that effect: the `` types often *are* type aliases for certain fundamental types. In this case, I imagine it’s just that `unsigned long` is also 32 bits and was selected to be `uint32_t`. – Davis Herring May 11 '20 at 02:15
  • Distinct fixed size types where a feature of C+11 (see https://en.cppreference.com/w/cpp/types/integer) I remember having to define them manually for my own system in the early 2000's – Andrew Goedhart May 11 '20 at 09:14
  • @AndrewGoedhart: That doesn’t say they’re distinct types, and they certainly [often aren’t](https://godbolt.org/z/PpumBc). – Davis Herring May 11 '20 at 13:49
  • @David actually gcc is allowing me to define both write(uint32_t) and write(unsigned int) so it is see them as distinct types and when I do so it removes the compile error. Now the question is this correct according to the standard or just a gcc quirk – Andrew Goedhart May 12 '20 at 15:37
  • Maybe not, maybe this is a regression in the compiler. The only compiler this code struct A { int g(unsigned a){return a;} int g(uint32_t a){return a;} }; compiles on is the Arm gcc versions 8.3 and 9.0 – Andrew Goedhart May 12 '20 at 15:51
  • @David It turns out that the reason it was allowing me to define both was that gcc arm changed their definition of uint32_t to unsigned long from unsigned int somewhere between 8.2 and 8.3.1 :-0 – Andrew Goedhart May 12 '20 at 16:09
2

The reason you are getting an ambiguous overload error, is because an unsigned int could be implicitly converted to any of the overload argument types for MemoryStream::write. Since there is no direct overload for unsigned int, the compiler does not know which overload to select:

template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type WriteStream::write(T obj){
    //which overload for MemoryStream::write should be selected here?       
    MemoryStream::write(obj);
}

This does not happen if you explicitly state the template parameter txtStream.write<uint32_t>(0U);, because the argument 0L is implicitly converted to uint32_t. Since a direct overload for MemoryStream::write for uint32_t exists, you do not get any ambiguity errors.

As for your second question, within the body of WriteStream::write, obj is an lvalue reference to an unsigned int, even though, from your initial function call: txtStream.write(0L) the argument is a numerical literal:

template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type WriteStream::write(T obj){
   //obj is an lvalue reference within this scope to whatever T is
   //  deduced to be (unsigned int in your example)
   MemoryStream::write(obj);
}
Azam Bham
  • 1,389
  • 5
  • 6
  • Ah, okay, I understand. I may have misunderstood the first question. Is your second question adequately answered? – Azam Bham May 10 '20 at 21:31
  • Actually I just lifted simplified example from a much larger code base. The private base class is required because its a common base implementation which exposes methods that would pollute the write stream interface. – Andrew Goedhart May 10 '20 at 21:40
  • Actually MemoryStream::write() is exactly what I wanted Because this->write(obj) is recursive and leads to a stack overflow where as MemoryStream::write forces it to use the base class primitive write methods and in the literal case results in a error because as we have learnt int != int32_t. That is actually why I posted the question because I got the compiler error after I removed the stack overflow :-) – Andrew Goedhart May 10 '20 at 21:48
  • Yes, you're correct about the call to `write`, I missed that. I've edited the answer. – Azam Bham May 10 '20 at 21:50