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.
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
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;
}
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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With