Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous overload of functions like `msg(long)` with candidates `msg(int32_t)` and `msg(int64_t)`

Tags:

c++

gcc

Note: This is very similar to Determine number of bits in integral type at compile time, however this is a much simplified version, all contained in a single .cpp

Edit: Added a solution - although a correct explanation was given (and accepted) I found a way to solve the problem generically.

Problem

The problem is with functions like

 msg(int32_t);
 msg(int64_t);

a call like

long long myLong = 6;
msg(myLong);    // Won't compile on gcc (4.6.3), call is ambiguous

This compiles on MSVC. Can anyone provide an explanation of why this fails on gcc (I'm assuming it's probably to do with the fact gcc is usually strictly standards compliant) and an example of how to correctly achieve the same effect?

#include <iostream>
#include <stdint.h>

#include <boost/integer.hpp>

using namespace std;

void msg(int v) { cout << "int: " << sizeof(int) << ' ' << v << '\n'; }
void msg(long v) { cout << "long: " << sizeof(long) << ' ' << v << '\n'; }
void msg(long long v) { cout << "long long: " << sizeof(long long) << ' ' << v << '\n'; }

void msg2(int32_t v) { cout << "int32_t: " << sizeof(int32_t) << ' ' << v << '\n'; }
void msg2(int64_t v) { cout << "int64_t: " << sizeof(int64_t) << ' ' << v << '\n'; }
void msg2(uint32_t v) { cout << "uint32_t: " << sizeof(uint32_t) << ' ' << v << '\n'; }
void msg2(uint64_t v) { cout << "uint64_t: " << sizeof(uint64_t) << ' ' << v << '\n'; }


int main()
{

    int myInt = -5;
    long myLong = -6L;
    long long myLongLong = -7LL;

    unsigned int myUInt = 5;
    unsigned int myULong = 6L;
    unsigned long long myULongLong = 7LL;

    msg(myInt);
    msg(myLong);
    msg(myLongLong);

    msg2(myInt);
    msg2(myLong);     // fails on gcc 4.6.3 (32 bit)
    msg2(myLongLong);

    msg2(myUInt);
    msg2(myULong);   // fails on gcc 4.6.3 (32 bit)
    msg2(myULongLong);

   return 0;
}

// Output from MSVC  (and gcc if you omit lines that would be commented out)
int: 4 5
long: 4 6
long long: 8 7
int32_t: 4 -5
int32_t: 4 -6   // omitted on gcc
int64_t: 8 -7
uint32_t: 4 5
uint32_t: 4 6   // omitted on gcc
uint64_t: 8 7

Solution

The solution is provide a function that successfully maps int, long and long long to the appropriate int32_t or int64_t. This can be done trivially at run time via if (sizeof(int)==sizeof(int32_t)) type statements, but a compile-time solution is preferable. A compile-time solution is available via the use of boost::enable_if.

The following works on MSVC10 and gcc 4.6.3. The solution could be further enhanced by disabling for non-integral types, but that is outside the scope of this problem.

#include <iostream>
#include <stdint.h>

#include <boost/integer.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_signed.hpp>
#include <boost/type_traits/is_unsigned.hpp>

using namespace std;

template <class InputT>
typename boost::enable_if_c<sizeof(InputT)==sizeof(int32_t) && boost::is_signed<InputT>::value,
 int32_t>::type ConvertIntegral(InputT z) { return static_cast<int32_t>(z); }

template <class InputT>
typename boost::enable_if_c<sizeof(InputT)==sizeof(int64_t) && boost::is_signed<InputT>::value, 
int64_t>::type ConvertIntegral(InputT z) { return static_cast<int64_t>(z); }

template <class InputT>
typename boost::enable_if_c<sizeof(InputT)==sizeof(uint32_t) && boost::is_unsigned<InputT>::value, 
uint32_t>::type ConvertIntegral(InputT z) { return static_cast<uint32_t>(z); }

template <class InputT>
typename boost::enable_if_c<sizeof(InputT)==sizeof(uint64_t) && boost::is_unsigned<InputT>::value, 
uint64_t>::type ConvertIntegral(InputT z) { return static_cast<uint64_t>(z); }

void msg(int v) { cout << "int: " << sizeof(int) << ' ' << v << '\n'; }
void msg(long v) { cout << "long: " << sizeof(long) << ' ' << v << '\n'; }
void msg(long long v) { cout << "long long: " << sizeof(long long) << ' ' << v << '\n'; }


void msg2(int32_t v) { cout << "int32_t: " << sizeof(int32_t) << ' ' << v << '\n'; }
void msg2(int64_t v) { cout << "int64_t: " << sizeof(int64_t) << ' ' << v << '\n'; }
void msg2(uint32_t v) { cout << "uint32_t: " << sizeof(uint32_t) << ' ' << v << '\n'; }
void msg2(uint64_t v) { cout << "uint64_t: " << sizeof(uint64_t) << ' ' << v << '\n'; }

int main()
{

    int myInt = -5;
    long myLong = -6L;
    long long myLongLong = -7LL;

    unsigned int myUInt = 5;
    unsigned int myULong = 6L;
    unsigned long long myULongLong = 7LL;

    msg(myInt);
    msg(myLong);
    msg(myLongLong);

    msg2(ConvertIntegral(myInt));
    msg2(ConvertIntegral(myLong));
    msg2(ConvertIntegral(myLongLong));

    msg2(ConvertIntegral(myUInt));
    msg2(ConvertIntegral(myULong));
    msg2(ConvertIntegral(myULongLong));

   return 0;
}
like image 541
Zero Avatar asked May 14 '12 08:05

Zero


2 Answers

Greg hits the nail on the head: int32_t and int64_t are typedefs which may or may not be long. If neither is a typedef for long, overload resolution can fail. Both long->int32_t and long->int64_t have Rank=Promotion (Table 12, 13.3.3.1.2)

like image 98
MSalters Avatar answered Oct 29 '22 11:10

MSalters


Whether the code will compile or not is implementation defined. There is no type int32_t nor int64_t; these are simply typedef's to an existing integral type. If the type happens to be one that is already overloaded (int, long or long long), which is almost certainly the case, then you have multiple definitions of the same function. If they are in the same translation unit, it is a compile time error, requiring a diagnostic. If they are in different translation units, it's undefined behavior, but I imagine that most implementations will also generate an error.

In your case, the best solution is probably to make msg a template, and pass the name of the type in as an argument.

like image 3
James Kanze Avatar answered Oct 29 '22 11:10

James Kanze