long long int vs long int vs int64_t en C ++

87

Experimenté un comportamiento extraño mientras usaba rasgos de tipo C ++ y he reducido mi problema a este pequeño y peculiar problema para el que daré un montón de explicaciones, ya que no quiero dejar nada abierto a una mala interpretación.

Digamos que tiene un programa como este:

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

En la compilación de 32 bits con GCC (y con MSVC de 32 y 64 bits), la salida del programa será:

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

Sin embargo, el programa resultante de una compilación GCC de 64 bits generará:

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

Esto es curioso, ya que long long intes un entero de 64 bits con signo y es, para todos los efectos, idéntico a los tipos long inty int64_t, por lo tanto, lógicamente int64_t, long inty long long intserían tipos equivalentes: el ensamblado generado al usar estos tipos es idéntico. Una mirada stdint.hme dice por qué:

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

En una compilación de 64 bits, int64_tes long int, no una long long int(obviamente).

La solución para esta situación es bastante fácil:

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

Pero esto es horriblemente pirateado y no se escala bien (funciones reales de sustancia uint64_t, etc.). Entonces mi pregunta es: ¿Hay alguna manera de decirle al compilador que a long long intes también a int64_t, como long intes?


Mis pensamientos iniciales son que esto no es posible, debido a la forma en que funcionan las definiciones de tipo C / C ++. No hay manera de especificar la equivalencia de tipos de los tipos de datos básicos para el compilador, ya que ese es el trabajo del compilador (y permitir eso podría romper muchas cosas) y typedefsolo va en una dirección.

Tampoco estoy muy preocupado por obtener una respuesta aquí, ya que este es un caso de borde súper duper que no sospecho que a nadie le importará cuando los ejemplos no estén horriblemente ideados (¿eso significa que esto debería ser un wiki de la comunidad?) .


Agregar : La razón por la que estoy usando la especialización de plantilla parcial en lugar de un ejemplo más fácil como:

void go(int64_t) { }

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

es que dicho ejemplo aún se compilará, ya que long long intes implícitamente convertible a un int64_t.


Agregar : la única respuesta hasta ahora asume que quiero saber si un tipo es de 64 bits. No quería engañar a la gente para que pensara que eso me importa y que probablemente debería haber proporcionado más ejemplos de dónde se manifiesta este problema.

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

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

En este ejemplo, some_type_trait<long int>será un boost::true_type, pero some_type_trait<long long int>no lo será. Si bien esto tiene sentido en la idea de tipos de C ++, no es deseable.

Otro ejemplo es usar un calificador como same_type(que es bastante común de usar en C ++ 0x Concepts):

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

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

Ese ejemplo no se compila, ya que C ++ (correctamente) ve que los tipos son diferentes. g ++ fallará al compilar con un error como: no hay llamada de función coincidente same_type(long int&, long long int&).

Me gustaría enfatizar que entiendo por qué sucede esto, pero estoy buscando una solución que no me obligue a repetir el código por todas partes.

Travis Gockel
fuente
Por curiosidad, ¿su programa de muestra da los mismos resultados para sizeofcada tipo? Quizás el compilador esté tratando el tamaño de de manera long long intdiferente.
Blair Holloway
¿Ha compilado con C ++ 0x habilitado? C ++ 03 no lo tiene <cstdint>, así que quizás el hecho de que tenga que decir "esto es una extensión" (que lo es) lo esté engañando.
GManNickG
Sí, probablemente debería haber especificado que estoy usando --std=c++0x. Y sí, sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8.
Travis Gockel
1
Nadie mencionó esto todavía, pero en caso de que se haya pasado por alto: longy long longson tipos distintos (incluso si tienen el mismo tamaño y representación). int64_tes siempre un alias para otro tipo existente (a pesar de su nombre, typedefno crea nuevos tipos, solo le da un alias a uno que ya existe)
MM
3
Falta una declaración importante en las respuestas / comentarios, lo que me ayudó cuando me golpeó esta peculiaridad: nunca use tipos de tamaño fijo para las plantillas especializadas de manera confiable. Utilice siempre tipos básicos y cubra todos los casos posibles (incluso si utiliza tipos de tamaño fijo para crear instancias de esas plantillas). Todos los casos posibles significan: si necesita crear una instancia con int16_t, entonces especialícese con shorty inty estará cubierto. (y con signed charsi te sientes aventurero)
Irfy

Respuestas:

49

No es necesario ir a 64 bits para ver algo como esto. Piense int32_ten plataformas comunes de 32 bits. Puede ser typedefeditado como into como long, pero obviamente solo uno de los dos a la vez. inty long, por supuesto, son tipos distintos.

No es difícil ver que no existe una solución alternativa int == int32_t == longpara los sistemas de 32 bits. Por la misma razón, no hay forma de hacerlo long == int64_t == long longen sistemas de 64 bits.

Si pudiera, las posibles consecuencias serían bastante doloroso para el código que sobrecargado foo(int), foo(long)y foo(long long)- de repente tendrían dos definiciones para la misma sobrecarga ?!

La solución correcta es que el código de su plantilla generalmente no debería depender de un tipo preciso, sino de las propiedades de ese tipo. Toda la same_typelógica aún podría estar bien para casos específicos:

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

Es decir, la sobrecarga foo(int64_t)no se define cuando es exactamente igual que foo(long).

[editar] Con C ++ 11, ahora tenemos una forma estándar de escribir esto:

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

[editar] O C ++ 20

long foo(long x);
int64_t foo(int64_t) requires (!std::is_same_v<int64_t, long>);
MSalters
fuente
1
La noticia triste es, por ejemplo, en MSVC19 de 64 bits (2017) sizeof() longy intes idéntica, pero std::is_same<long, int>::valueregresa false. La misma rareza en AppleClang 9.1 en OSX HighSierra.
Ax3l
3
@ Ax3l: Eso no es extraño. Prácticamente todos los compiladores desde ISO C 90 tienen al menos uno de esos pares.
MSalters
Eso es cierto, son tipos distintos.
Ax3l
6

¿Quieres saber si un tipo es del mismo tipo que int64_t o quieres saber si algo es de 64 bits? Según su solución propuesta, creo que está preguntando sobre este último. En ese caso, haría algo como

template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
Logan Capaldo
fuente
1
¿No te faltan un returny un punto y coma?
casablanca
1
Aún así, debería usar sizeofpara esto.
Ben Voigt
5
long long int y long int no son del mismo tipo, tengan o no el mismo tamaño. El comportamiento no es erróneo. Eso es solo C ++.
Logan Capaldo
5
No es una limitación de la escritura nominal. Es una limitación de la mecanografía nominal sin sentido . En los viejos tiempos, el estándar de facto era short= 16 bits, long= 32 bits y int= tamaño nativo. En estos días de 64 bits, intya longno significan nada.
dan04
1
@ dan04: No son más ni menos significativos de lo que nunca fueron. shorttiene al menos 16 bits, inttiene al menos 16 bits y longal menos 32 bits, con (sigue una notación descuidada) corto <= int <= largo. Los "viejos tiempos" a los que te refieres nunca existieron; siempre ha habido variaciones dentro de las restricciones impuestas por el idioma. La falacia de "Todo el mundo es un x86" es tan peligrosa como la antigua "Todo el mundo es una falacia de VAX."
Keith Thompson
1

Entonces, mi pregunta es: ¿hay alguna manera de decirle al compilador que un long long int es también un int64_t, al igual que long int?

Esta es una buena pregunta o problema, pero sospecho que la respuesta es NO.

Además, a long intpuede no ser a long long int.


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

Creo que esto es libc. Sospecho que quieres profundizar más.

En la compilación de 32 bits con GCC (y con MSVC de 32 y 64 bits), la salida del programa será:

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

Linux de 32 bits utiliza el modelo de datos ILP32. Los números enteros, largos y punteros son de 32 bits. El tipo de 64 bits es un long long.

Microsoft documenta los rangos en rangos de tipos de datos . Dicen que long longes equivalente a __int64.

Sin embargo, el programa resultante de una compilación GCC de 64 bits generará:

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

Linux de 64 bits usa el LP64modelo de datos. Los largos son de 64 bits y de long long64 bits. Al igual que con 32 bits, Microsoft documenta los rangos en los rangos de tipos de datos y long long is still __int64.

Hay un ILP64modelo de datos donde todo es de 64 bits. Tienes que hacer un trabajo adicional para obtener una definición para tu word32tipo. Consulte también artículos como Modelos de programación de 64 bits: ¿Por qué LP64?


Pero esto es horriblemente pirateado y no se escala bien (funciones reales de sustancia, uint64_t, etc.) ...

Sí, se pone aún mejor. GCC mezcla y combina declaraciones que se supone deben tomar tipos de 64 bits, por lo que es fácil meterse en problemas aunque siga un modelo de datos en particular. Por ejemplo, lo siguiente provoca un error de compilación y le indica que utilice -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);

En resultado de:

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

Entonces, ignórelo LP64y cámbielo a:

typedef unsigned long long word64;

Luego, diríjase a un dispositivo ARM IoT de 64 bits que define LP64y usa NEON:

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