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.
In a 64-bit compile, int64_t is long int , not a long long int (obviously).
If long long is present, it must have at least 64 bits, so casting from (u)int64_t to (unsigned) long long is value-preserving. If you need a type with exactly 64 bits, use (u)int64_t , if you need at least 64 bits, (unsigned) long long is perfectly fine, as would be (u)int_least64_t .
In C#, long is mapped to Int64. It is a value type and represent System. Int64 struct. It is signed and takes 64 bits.
Long long takes the double memory as compared to long. But it can also be different on various systems. Its range depends on the type of application.
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>);
Do you want to know if a type is the same type as int64_t or do you want to know if something is 64 bits? Based on your proposed solution, I think you're asking about the latter. In that case, I would do something like
template<typename T> bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
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