Search code examples
c++gccc++17overload-resolution

Understanding compiler choice in resolving function overloading


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'


Solution

  • 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);
    }