long long int vs. long int vs. int64_t em C ++

87

Eu experimentei um comportamento estranho ao usar características do tipo C ++ e reduzi meu problema a este pequeno problema peculiar para o qual darei muitas explicações, já que não quero deixar nada aberto para interpretações erradas.

Digamos que você tenha um 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;
}

Na compilação de 32 bits com GCC (e com MSVC de 32 e 64 bits), a saída do programa será:

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

No entanto, o programa resultante de uma compilação GCC de 64 bits produzirá:

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

Isso é curioso, já que long long inté um inteiro assinado de 64 bits e é, para todos os efeitos, idêntico aos tipos long inte int64_t, então logicamente int64_t, long inte long long intseriam tipos equivalentes - o assembly gerado ao usar esses tipos é idêntico. Uma olhada stdint.hme diz por quê:

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

Em uma 64 bits de compilação, int64_té long int, não um long long int(obviamente).

A solução para essa situação é muito fácil:

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

Mas isso é terrivelmente hackeado e não se ajusta bem (funções reais da substância uint64_t, etc.). Portanto, minha pergunta é: há uma maneira de dizer ao compilador que long long inta também é a int64_t, assim como long inté?


Meu pensamento inicial é que isso não é possível, devido à maneira como as definições de tipo C / C ++ funcionam. Não há uma maneira de especificar a equivalência de tipo dos tipos de dados básicos para o compilador, uma vez que essa é a função do compilador (e permitir isso poderia quebrar muitas coisas) e typedefsó vai em um caminho.

Eu também não estou muito preocupado em obter uma resposta aqui, uma vez que este é um caso extremamente extremo que eu não suspeito que alguém se importará quando os exemplos não forem terrivelmente inventados (isso significa que deve ser um wiki da comunidade?) .


Anexo : O motivo pelo qual estou usando a especialização parcial de modelo em vez de um exemplo mais fácil como:

void go(int64_t) { }

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

é que o referido exemplo ainda será compilado, uma vez que long long inté implicitamente conversível em um int64_t.


Anexo : A única resposta até agora pressupõe que eu quero saber se um tipo é 64 bits. Não queria enganar as pessoas fazendo-as pensar que me importo com isso e provavelmente deveria ter fornecido mais exemplos de onde esse problema se manifesta.

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

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

Neste exemplo, some_type_trait<long int>será um boost::true_type, mas some_type_trait<long long int>não será. Embora isso faça sentido na ideia de tipos do C ++, não é desejável.

Outro exemplo é usar um qualificador como same_type(que é muito comum de usar em C ++ 0x Concepts):

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

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

Esse exemplo falha ao compilar, uma vez que C ++ (corretamente) vê que os tipos são diferentes. g ++ falhará ao compilar com um erro como: nenhuma chamada de função correspondente same_type(long int&, long long int&).

Gostaria de enfatizar que entendo por que isso está acontecendo, mas estou procurando uma solução que não me obrigue a repetir o código em todos os lugares.

Travis Gockel
fonte
Por curiosidade, seu programa de amostra fornece os mesmos resultados para sizeofcada tipo? Talvez o compilador esteja tratando o tamanho de maneira long long intdiferente.
Blair Holloway
Você compilou com C ++ 0x ativado? C ++ 03 não tem <cstdint>, então talvez o fato de ter que dizer "esta é uma extensão" (o que é) seja um exagero.
GManNickG
Sim, eu provavelmente deveria ter especificado que estou usando --std=c++0x. E sim sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8,.
Travis Gockel
1
Ninguém mencionou isso ainda, mas caso tenha sido esquecido: longe long longsão tipos distintos (mesmo que tenham o mesmo tamanho e representação). int64_té sempre um apelido para outro tipo existente (apesar do nome, typedefnão cria novos tipos, apenas dá um apelido para um que já existe)
MM
3
Uma declaração importante está faltando nas respostas / comentários, o que me ajudou quando essa peculiaridade me atingiu: nunca use tipos de tamanho fixo para modelos especializados de forma confiável. Sempre use tipos básicos e cubra todos os casos possíveis (mesmo se você usar tipos de tamanho fixo para instanciar esses modelos). Todos os casos possíveis significam: se você precisa instanciar com int16_t, especialize-se com shorte inte você estará coberto. (e signed charse você estiver se sentindo aventureiro)
Irfy

Respostas:

49

Você não precisa ir para 64 bits para ver algo assim. Considere int32_tem plataformas comuns de 32 bits. Pode ser typedefed como intou como um long, mas obviamente apenas um dos dois de cada vez. inte, longclaro, são tipos distintos.

Não é difícil ver que não há uma solução alternativa int == int32_t == longpara sistemas de 32 bits. Pelo mesmo motivo, não há como fazer long == int64_t == long longem sistemas de 64 bits.

Se pudesse, as possíveis consequências seriam bastante doloroso para o código que sobrecarregados foo(int), foo(long)e foo(long long)- de repente eles têm duas definições para a mesma sobrecarga ?!

A solução correta é que o código do modelo geralmente não deve se basear em um tipo preciso, mas nas propriedades desse tipo. Toda a same_typelógica ainda pode estar OK para casos específicos:

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

Ou seja, a sobrecarga foo(int64_t)não é definida quando é exatamente igual a foo(long).

[editar] Com C ++ 11, agora temos uma maneira padrão de escrever isso:

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

[editar] Ou C ++ 20

long foo(long x);
int64_t foo(int64_t) requires (!std::is_same_v<int64_t, long>);
MSalters
fonte
1
A notícia triste é, por exemplo, em 64 bits MSVC19 (2017) sizeof() longe inté idêntica, mas std::is_same<long, int>::valueretorna false. Mesma estranheza no AppleClang 9.1 no OSX HighSierra.
Ax3l
3
@ Ax3l: Isso não é estranho. Praticamente todo compilador desde ISO C 90 tem pelo menos um par.
MSalters
É verdade, são tipos distintos.
Ax3l
6

Você quer saber se um tipo é do mesmo tipo que int64_t ou quer saber se algo tem 64 bits? Com base em sua solução proposta, acho que você está perguntando sobre a última. Nesse caso, eu faria algo como

template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
Logan Capaldo
fonte
1
Você não está perdendo um returne um ponto e vírgula?
casablanca
1
Ainda assim, você deve usar sizeofpara isso.
Ben Voigt
5
long long int e long int não são do mesmo tipo, quer tenham ou não o mesmo tamanho. O comportamento não é errado. Isso é apenas C ++.
Logan Capaldo
5
Não é uma limitação da digitação nominal. É uma limitação da digitação nominal sem sentido . Antigamente, o padrão de fato era short= 16 bits, long= 32 bits e int= tamanho nativo. Nestes dias de 64 bits, inte longnão significam mais nada.
dan04 de
1
@ dan04: Eles não têm mais nem menos significado do que nunca. shorttem pelo menos 16 bits, inttem pelo menos 16 bits e longtem pelo menos 32 bits, com (segue uma notação descuidada) curto <= int <= longo. Os "velhos tempos" aos quais você se refere nunca existiram; sempre houve variações dentro das restrições impostas pelo idioma. A falácia "Todo o mundo é um x86" é tão perigosa quanto a falácia "Todo o mundo é uma falácia VAX.
Keith Thompson
1

Portanto, minha pergunta é: Existe uma maneira de dizer ao compilador que um long long int é também um int64_t, assim como long int?

Esta é uma boa pergunta ou problema, mas suspeito que a resposta seja NÃO.

Além disso, a long intpode não ser a long long int.


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

Eu acredito que isso é libc. Eu suspeito que você queira ir mais fundo.

Na compilação de 32 bits com GCC (e com MSVC de 32 e 64 bits), a saída do programa será:

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

O Linux de 32 bits usa o modelo de dados ILP32. Inteiros, longos e ponteiros são de 32 bits. O tipo de 64 bits é a long long.

A Microsoft documenta os intervalos em Intervalos de tipo de dados . A palavra que long longé equivalente a __int64.

No entanto, o programa resultante de uma compilação GCC de 64 bits produzirá:

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

Linux de 64 bits usa o LP64modelo de dados. Longs são de 64 bits e long long64 bits. Tal como acontece com 32 bits, a Microsoft documenta os intervalos em Intervalos de tipo de dados e longo, longo ainda está __int64.

Existe um ILP64modelo de dados onde tudo é 64 bits. Você precisa fazer algum trabalho extra para obter uma definição para o seu word32tipo. Veja também artigos como Modelos de programação de 64 bits: Por que LP64?


Mas isso é terrivelmente hackeado e não se adapta bem (funções reais da substância, uint64_t, etc) ...

Sim, fica ainda melhor. O GCC combina e combina declarações que supostamente aceitam tipos de 64 bits, então é fácil ter problemas mesmo que você siga um modelo de dados específico. Por exemplo, o seguinte causa um erro de compilação e instrui você a usar -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);

Isso resulta em:

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

Portanto, ignore LP64e mude para:

typedef unsigned long long word64;

Em seguida, vá até um gadget ARM IoT de 64 bits que define LP64e usa NEON:

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