2010-11-12 32 views
65

ho sperimentato un po 'strano comportamento durante l'utilizzo di C tipo tratti ++ ed ho ridotto il mio problema fino a questo piccolo problema eccentrico per la quale io darò un sacco di spiegazione dal momento che non voglio lasciare tutto aperto a interpretazioni errate.C++: long long int vs long int vs int64_t

Diciamo che avete un programma in questo modo:

#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; 
} 

sia a 32-bit compilare con GCC (e con 32 e 64 bit MSVC), l'output del programma sarà:

int:   0 
int64_t:  1 
long int:  0 
long long int: 1 

Tuttavia, il programma risultante da un 64-bit GCC compilare stamperà:

int:   0 
int64_t:  1 
long int:  1 
long long int: 0 

è curioso, poiché 0.123.è un numero intero a 64 bit ed è identico ai tipi long int e int64_t, quindi logicamente, int64_t, long int e long long int sarebbero tipi equivalenti: l'assembly generato quando si utilizzano questi tipi è identico. Uno sguardo a stdint.h mi dice perché:

# if __WORDSIZE == 64 
typedef long int int64_t; 
# else 
__extension__ 
typedef long long int int64_t; 
# endif 

In un 64-bit di compilazione, int64_t è long int, non un long long int (ovviamente).

La correzione di questa situazione è piuttosto semplice:

#if defined(__GNUC__) && (__WORDSIZE == 64) 
template <> 
bool is_int64<long long int>() { return true; } 
#endif 

Ma questo è terribilmente hacker e non scala bene (funzioni reali di sostanza, uint64_t, ecc). ci è un modo per dire al compilatore che una long long int è il anche una int64_t, proprio come long int è: Quindi la mia domanda è?


I miei pensieri iniziali sono che questo non è possibile, a causa del modo in cui funzionano le definizioni di tipo C/C++. Non c'è un modo per specificare l'equivalenza di tipo dei tipi di dati di base al compilatore, dal momento che è il lavoro del compilatore (e che consente di rompere un sacco di cose) e typedef va solo in un modo.

Anche io non sono troppo preoccupato di ottenere una risposta qui, poiché si tratta di un caso limite super-duper che non sospetto che a nessuno interesserà mai quando gli esempi non sono orribilmente inventati (vuol dire che questo dovrebbe essere wiki della comunità?).


Append: Il motivo per cui sto utilizzando il modello di specializzazione parziale, invece di un esempio più semplice del tipo:

void go(int64_t) { } 

int main() 
{ 
    long long int x = 2; 
    go(x); 
    return 0; 
} 

è che ha detto ad esempio sarà ancora la compilazione, dal momento che long long int è implicitamente convertibile in una int64_t.


Append: L'unica risposta presuppone finora che voglio sapere se un tipo è 64-bit. Non volevo indurre in errore le persone a pensare che mi interessi e probabilmente avrei dovuto fornire altri esempi di dove questo problema si manifesta.

template <typename T> 
struct some_type_trait : boost::false_type { }; 

template <> 
struct some_type_trait<int64_t> : boost::true_type { }; 

In questo esempio, some_type_trait<long int> sarà un boost::true_type, ma some_type_trait<long long int> non sarà. Mentre questo ha senso nell'idea di tipi del C++, non è desiderabile.

Un altro esempio sta usando un qualificatore come same_type (che è abbastanza comune l'uso in C++ 0x Concetti):

template <typename T> 
void same_type(T, T) { } 

void foo() 
{ 
    long int x; 
    long long int y; 
    same_type(x, y); 
} 

Questo esempio non riesce a compilare, in quanto C++ (correttamente) vede che i tipi sono diverso. g ++ non riuscirà a compilare con un errore del tipo: nessuna funzione corrispondente chiamata same_type(long int&, long long int&).

Mi piacerebbe sottolineare che capisco perché questo sta accadendo, ma sto cercando una soluzione che non mi costringa a ripetere il codice in tutto il luogo.

+0

Per curiosità, il programma di esempio fornisce gli stessi risultati per il 'sizeof' di ogni tipo? Forse il compilatore sta trattando le dimensioni di "long long int" in modo diverso. –

+0

Hai compilato con C++ 0x abilitato? C++ 03 non ha '', quindi forse il fatto che abbia da dire "questa è un'estensione" (che è) lo sta facendo sembrare pazzo. – GManNickG

+0

Sì, avrei probabilmente specificato che sto usando '--std = C++ 0x'. E sì, 'sizeof (long long int) == sizeof (long int) == sizeof (int64_t) == 8'. –

risposta

40

Non è necessario andare a 64-bit per vedere qualcosa di simile. Si consideri int32_t su piattaforme a 32 bit comuni. Potrebbe essere typedef 'come int o come long, ma ovviamente solo uno dei due alla volta. int e long sono ovviamente tipi distinti.

Non è difficile vedere che non vi è alcuna soluzione alternativa che rende int == int32_t == long su sistemi a 32 bit. Per lo stesso motivo, non c'è modo di rendere long == int64_t == long long su sistemi a 64 bit.

Se si potesse, le possibili conseguenze sarebbero piuttosto doloroso per il codice che sovraccarica foo(int), foo(long) e foo(long long) - improvvisamente avrebbero avuto due definizioni per la stessa di sovraccarico ?!

La soluzione corretta è che il codice del modello di solito non dovrebbe basarsi su un tipo preciso, ma sulle proprietà di quel tipo. L'intero same_type logica potrebbe ancora essere OK per casi specifici:

long foo(long x); 
std::tr1::disable_if(same_type(int64_t, long), int64_t)::type foo(int64_t); 

cioè del sovraccarico foo(int64_t) non è definito quando è esattamente lo stesso di foo(long).

[modifica] Con C++ 11, ora abbiamo un modo standard per scrivere questo:

long foo(long x); 
std::enable_if<!std::is_same<int64_t, long>::value, int64_t>::type foo(int64_t); 
4

Volete sapere se un tipo è dello stesso tipo di int64_t o volete sapere se qualcosa è a 64 bit? Sulla base della soluzione proposta, penso che tu stia chiedendo di quest'ultimo. In tal caso, vorrei fare qualcosa di simile

template<typename T> 
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64 
+1

Non ti manca un' return' e un punto e virgola? – casablanca

+11

non più. la sintassi corretta è comunque sopravvalutata;) –

+0

Non è quello che sto cercando. L'esempio è stato fornito per mostrare un modo per manifestare l'errore, non come un requisito reale. –

0

Quindi la mia domanda è: C'è un modo per dire al compilatore che lungo un lungo int è anche un int64_t, proprio come int lungo?

Questa è una buona domanda o problema, ma sospetto che la risposta sia NO.

Inoltre, un long int potrebbe non essere un long long int.


# if __WORDSIZE == 64 
typedef long int int64_t; 
# else 
__extension__ 
typedef long long int int64_t; 
# endif 

Credo che questo sia libc. Sospetto che tu voglia andare più a fondo.

sia a 32 bit compilato con GCC (e con 32 e 64 bit MSVC), il output del programma sarà:

int:   0 
int64_t:  1 
long int:  0 
long long int: 1 

32-bit Linux utilizza la Modello di dati ILP32. Interi, long e puntatori sono a 32 bit. Il tipo a 64 bit è un long long.

Microsoft documenta gli intervalli su Data Type Ranges. Il dire che il long long è equivalente a __int64.

Tuttavia, il programma risultante da una a 64 bit GCC compilare uscita volontà:

int:   0 
int64_t:  1 
long int:  1 
long long int: 0 

64-bit Linux utilizza il modello di dati LP64. Longs 64-bit e long long sono 64-bit. Come con 32 bit, Microsoft documenta gli intervalli a Data Type Ranges e il tempo lungo è ancora __int64.

C'è un modello di dati ILP64 in cui tutto è a 64 bit. Devi fare del lavoro extra per ottenere una definizione per il tuo tipo word32. Vedere anche i documenti come 64-Bit Programming Models: Why LP64?


Ma questo è terribilmente hacker e non scala bene (funzioni reali di sostanza, uint64_t, ecc) ...

Sì, è ancora meglio. GCC miscela e confronta dichiarazioni che dovrebbero prendere tipi a 64 bit, quindi è facile mettersi nei guai anche se si segue un particolare modello di dati. Ad esempio, le seguenti cause di un errore di compilazione e ti dice di usare -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); 

Essa si traduce in:

error: invalid conversion from `word64* {aka long unsigned int*}' to `long long unsigned int*' 

Così, ignorare LP64 e modificarla in:

typedef unsigned long long word64; 

Quindi, passa a un gadget ARM IoT a 64 bit che definisce LP64 e utilizza NEON:

error: invalid conversion from `word64* {aka long long unsigned int*}' to `uint64_t*'