Doświadczyłem dziwnego zachowania podczas używania cech typu C ++ i zawęziłem swój problem do tego dziwacznego małego problemu, dla którego dam mnóstwo wyjaśnień, ponieważ nie chcę zostawiać niczego otwartego na błędną interpretację.
Powiedzmy, że masz taki program:
#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;
}
W obu 32-bitowych kompilacjach z GCC (oraz z 32- i 64-bitowym MSVC), wyjście programu będzie:
int: 0
int64_t: 1
long int: 0
long long int: 1
Jednak program powstały z 64-bitowej kompilacji GCC wyświetli:
int: 0
int64_t: 1
long int: 1
long long int: 0
Jest to ciekawe, ponieważ long long int
jest to 64-bitowa liczba całkowita ze znakiem i pod każdym względem jest identyczna z typami long int
i int64_t
, więc logicznie int64_t
, long int
i long long int
byłyby równoważnymi typami - zestaw generowany podczas korzystania z tych typów jest identyczny. Jedno spojrzenie stdint.h
mówi mi, dlaczego:
# if __WORDSIZE == 64
typedef long int int64_t;
# else
__extension__
typedef long long int int64_t;
# endif
W 64-bitowym kompilacji, int64_t
to long int
, a nie long long int
(oczywiście).
Rozwiązanie tej sytuacji jest dość łatwe:
#if defined(__GNUC__) && (__WORDSIZE == 64)
template <>
bool is_int64<long long int>() { return true; }
#endif
Ale to jest strasznie hakerskie i nie skaluje się dobrze (rzeczywiste funkcje substancji uint64_t
itp.). Więc moje pytanie brzmi: czy istnieje sposób, aby powiedzieć kompilatorowi, że a long long int
jest również a int64_t
, tak jak long int
jest?
Moje początkowe przemyślenia są takie, że nie jest to możliwe ze względu na sposób działania definicji typów C / C ++. Nie ma sposobu, aby określić równoważność typów podstawowych typów danych kompilatorowi, ponieważ jest to zadanie kompilatora (i pozwolenie na to może zepsuć wiele rzeczy) i działa typedef
tylko w jedną stronę.
Nie jestem też zbyt zainteresowany uzyskaniem odpowiedzi tutaj, ponieważ jest to super-duper edge przypadek, o którym nie podejrzewam, że ktoś kiedykolwiek będzie się przejmował, gdy przykłady nie są okropnie wymyślone (czy to oznacza, że powinno to być wiki społeczności?) .
Dołącz : powód, dla którego używam częściowej specjalizacji szablonu zamiast prostszego przykładu, takiego jak:
void go(int64_t) { }
int main()
{
long long int x = 2;
go(x);
return 0;
}
jest to, że wspomniany przykład będzie nadal kompilowany, ponieważ long long int
jest niejawnie konwertowany na plik int64_t
.
Dołącz : jedyna jak dotąd odpowiedź zakłada, że chcę wiedzieć, czy typ jest 64-bitowy. Nie chciałem wprowadzać ludzi w błąd, aby myśleli, że mi na tym zależy i prawdopodobnie powinienem był podać więcej przykładów, gdzie ten problem się objawia.
template <typename T>
struct some_type_trait : boost::false_type { };
template <>
struct some_type_trait<int64_t> : boost::true_type { };
W tym przykładzie some_type_trait<long int>
będzie boost::true_type
, ale some_type_trait<long long int>
nie będzie. Chociaż ma to sens w koncepcji typów w C ++, nie jest pożądane.
Innym przykładem jest użycie kwalifikatora takiego jak same_type
(który jest dość powszechny w C ++ 0x Pojęcia):
template <typename T>
void same_type(T, T) { }
void foo()
{
long int x;
long long int y;
same_type(x, y);
}
Ten przykład nie można skompilować, ponieważ C ++ (poprawnie) widzi, że typy są różne. g ++ nie skompiluje się z błędem takim jak: brak dopasowania funkcji same_type(long int&, long long int&)
.
Chciałbym podkreślić, że rozumiem, dlaczego tak się dzieje, ale szukam obejścia, które nie zmusza mnie do powtarzania kodu w każdym miejscu.
sizeof
każdego typu? Być może kompilatorlong long int
inaczej traktuje rozmiar .<cstdint>
, więc być może fakt, że musi powiedzieć „to jest rozszerzenie” (którym jest), jest dla niego sprzeczny.--std=c++0x
. I tak,sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8
.long
ilong long
są to różne typy (nawet jeśli mają tę samą wielkość i reprezentację).int64_t
jest zawsze aliasem dla innego istniejącego typu (pomimo swojej nazwytypedef
nie tworzy nowych typów, po prostu nadaje alias do już istniejącego)int16_t
, specjalizuj się wshort
i,int
a będziesz objęty ochroną. (isigned char
jeśli czujesz się naOdpowiedzi:
Nie musisz przechodzić do wersji 64-bitowej, aby zobaczyć coś takiego. Weź
int32_t
pod uwagę typowe platformy 32-bitowe. Może byćtypedef
wydany jakoint
lub jakolong
, ale oczywiście tylko jeden z dwóch naraz.int
ilong
oczywiście są to różne typy.Nietrudno zauważyć, że nie ma obejścia, które działa
int == int32_t == long
na systemach 32-bitowych. Z tego samego powodu nie ma możliwości tworzenialong == int64_t == long long
na systemach 64-bitowych.Jeśli mógł, ewentualne konsekwencje byłyby dość bolesne dla kodu, który przeciążone
foo(int)
,foo(long)
ifoo(long long)
- nagle mieliby dwie definicje tego samego przeciążenia ?!Prawidłowym rozwiązaniem jest to, że kod szablonu zwykle nie powinien opierać się na dokładnym typie, ale na właściwościach tego typu. Cała
same_type
logika może nadal działać w określonych przypadkach:long foo(long x); std::tr1::disable_if(same_type(int64_t, long), int64_t)::type foo(int64_t);
Oznacza to, że przeciążenie
foo(int64_t)
nie jest zdefiniowane, gdy jest dokładnie takie samo jakfoo(long)
.[edytuj] W C ++ 11 mamy teraz standardowy sposób pisania tego:
long foo(long x); std::enable_if<!std::is_same<int64_t, long>::value, int64_t>::type foo(int64_t);
[edytuj] Lub C ++ 20
long foo(long x); int64_t foo(int64_t) requires (!std::is_same_v<int64_t, long>);
źródło
sizeof()
long
iint
jest identyczna, alestd::is_same<long, int>::value
wracafalse
. Ta sama dziwność w AppleClang 9.1 na OSX HighSierra.Czy chcesz wiedzieć, czy typ jest tego samego typu co int64_t, czy chcesz wiedzieć, czy coś ma 64 bity? Opierając się na proponowanym przez Ciebie rozwiązaniu, myślę, że pytasz o to drugie. W takim razie zrobiłbym coś takiego
template<typename T> bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
źródło
return
i średnika?sizeof
do tego.short
= 16 bitów,long
= 32 bity iint
= rozmiar natywny. W dzisiejszych czasach 64-bitowychint
i jużlong
nic nie znaczą .short
ma co najmniej 16 bitów,int
ma co najmniej 16 bitów ilong
ma co najmniej 32 bity, z (następującą notacją niechlujną) short <= int <= long. „Stare czasy”, o których mówisz, nigdy nie istniały; zawsze istniały różnice w ograniczeniach narzuconych przez język. Błąd „Cały świat x86” jest tak samo niebezpieczny jak starszy „Błąd„ Cały świat to błąd VAX ”.To dobre pytanie lub problem, ale podejrzewam, że odpowiedź brzmi NIE.
Ponadto a
long int
nie może byćlong long int
.Uważam, że to jest libc. Podejrzewam, że chcesz wejść głębiej.
32-bitowy Linux używa modelu danych ILP32. Liczby całkowite, długie i wskaźniki są 32-bitowe. Typ 64-bitowy to
long long
.Firma Microsoft dokumentuje zakresy w zakresach typów danych . Powiedz, że
long long
jest równoważne__int64
.64-bitowy Linux używa
LP64
modelu danych. Długie są 64-bitowe ilong long
64-bitowe. Podobnie jak w przypadku wersji 32-bitowej, Microsoft dokumentuje zakresy w zakresach typów danych, a long long jest nadal__int64
.Istnieje
ILP64
model danych, w którym wszystko jest 64-bitowe. Musisz wykonać dodatkową pracę, aby uzyskać definicję swojegoword32
typu. Zobacz także artykuły, takie jak 64-bitowe modele programowania: Dlaczego LP64?Tak, jest jeszcze lepiej. GCC miesza i dopasowuje deklaracje, które mają przyjmować typy 64-bitowe, więc łatwo jest wpaść w kłopoty, nawet jeśli podążasz za określonym modelem danych. Na przykład poniższe powoduje błąd kompilacji i nakazuje użycie
-fpermissive
:#if __LP64__ typedef unsigned long word64; #else typedef unsigned long long word64; #endif // intel definition of rdrand64_step (http://software.intel.com/en-us/node/523864) // extern int _rdrand64_step(unsigned __int64 *random_val); // Try it: word64 val; int res = rdrand64_step(&val);
Skutkuje to:
error: invalid conversion from `word64* {aka long unsigned int*}' to `long long unsigned int*'
Więc zignoruj
LP64
i zmień to na:typedef unsigned long long word64;
Następnie przejdź do 64-bitowego gadżetu ARM IoT, który definiuje
LP64
i używa NEON:error: invalid conversion from `word64* {aka long long unsigned int*}' to `uint64_t*'
źródło