I experienced some odd behavior while using C++ type traits and have narrowed my problem down to this quirky little problem for which I will give a ton of explanation since I do not want to leave anything open for misinterpretation.
Say you have a program like so:
#include <iostream>
#include <cstdint>
template <typename T>
bool is_int64() { return false; }
template <>
bool is_int64<int64_t>() { return true; }
int main()
{
std::cout << "int:\t" << is_int64<int>() << std::endl;
std::cout << "int64_t:\t" << is_int64<int64_t>() << std::endl;
std::cout << "long int:\t" << is_int64<long int>() << std::endl;
std::cout << "long long int:\t" << is_int64<long long int>() << std::endl;
return 0;
}
In both 32-bit compile with GCC (and with 32- and 64-bit MSVC), the output of the program will be:
int: 0
int64_t: 1
long int: 0
long long int: 1
However, the program resulting from a 64-bit GCC compile will output:
int: 0
int64_t: 1
long int: 1
long long int: 0
This is curious, since long long int
is a signed 64-bit integer and is, for all intents and purposes, identical to the long int
and int64_t
types, so logically, int64_t
, long int
and long long int
would be equivalent types - the assembly generated when using these types is identical. One look at stdint.h
tells me why:
# if __WORDSIZE == 64
typedef long int int64_t;
# else
__extension__
typedef long long int int64_t;
# endif
In a 64-bit compile, int64_t
is long int
, not a long long int
(obviously).
The fix for this situation is pretty easy:
#if defined(__GNUC__) && (__WORDSIZE == 64)
template <>
bool is_int64<long long int>() { return true; }
#endif
But this is horribly hackish and does not scale well (actual functions of substance, uint64_t
, etc). So my question is: Is there a way to tell the compiler that a long long int
is the also a int64_t
, just like long int
is?
My initial thoughts are that this is not possible, due to the way C/C++ type definitions work. There is not a way to specify type equivalence of the basic data types to the compiler, since that is the compiler's job (and allowing that could break a lot of things) and typedef
only goes one way.
I'm also not too concerned with getting an answer here, since this is a super-duper edge case that I do not suspect anyone will ever care about when the examples are not horribly contrived (does that mean this should be community wiki?).
Append: The reason why I'm using partial template specialization instead of an easier example like:
void go(int64_t) { }
int main()
{
long long int x = 2;
go(x);
return 0;
}
is that said example will still compile, since long long int
is implicitly convertible to an int64_t
.
Append: The only answer so far assumes that I want to know if a type is 64-bits. I did not want to mislead people into thinking that I care about that and probably should have provided more examples of where this problem manifests itself.
template <typename T>
struct some_type_trait : boost::false_type { };
template <>
struct some_type_trait<int64_t> : boost::true_type { };
In this example, some_type_trait<long int>
will be a boost::true_type
, but some_type_trait<long long int>
will not be. While this makes sense in C++'s idea of types, it is not desirable.
Another example is using a qualifier like same_type
(which is pretty common to use in C++0x Concepts):
template <typename T>
void same_type(T, T) { }
void foo()
{
long int x;
long long int y;
same_type(x, y);
}
That example fails to compile, since C++ (correctly) sees that the types are different. g++ will fail to compile with an error like: no matching function call same_type(long int&, long long int&)
.
I would like to stress that I understand why this is happening, but I am looking for a workaround that does not force me to repeat code all over the place.
You don't need to go to 64-bit to see something like this. Consider int32_t
on common 32-bit platforms. It might be typedef
'ed as int
or as a long
, but obviously only one of the two at a time. int
and long
are of course distinct types.
It's not hard to see that there is no workaround which makes int == int32_t == long
on 32-bit systems. For the same reason, there's no way to make long == int64_t == long long
on 64-bit systems.
If you could, the possible consequences would be rather painful for code that overloaded foo(int)
, foo(long)
and foo(long long)
- suddenly they'd have two definitions for the same overload?!
The correct solution is that your template code usually should not be relying on a precise type, but on the properties of that type. The whole same_type
logic could still be OK for specific cases:
long foo(long x);
std::tr1::disable_if(same_type(int64_t, long), int64_t)::type foo(int64_t);
I.e., the overload foo(int64_t)
is not defined when it's exactly the same as foo(long)
.
[edit] With C++11, we now have a standard way to write this:
long foo(long x);
std::enable_if<!std::is_same<int64_t, long>::value, int64_t>::type foo(int64_t);
[edit] Or C++20
long foo(long x);
int64_t foo(int64_t) requires (!std::is_same_v<int64_t, long>);