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

87

J'ai eu un comportement étrange en utilisant des traits de type C ++ et j'ai réduit mon problème à ce petit problème bizarre pour lequel je vais donner une tonne d'explications car je ne veux rien laisser ouvert à une mauvaise interprétation.

Disons que vous avez un programme comme celui-ci:

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

Dans les deux compilations 32 bits avec GCC (et avec MSVC 32 et 64 bits), la sortie du programme sera:

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

Cependant, le programme résultant d'une compilation GCC 64 bits affichera:

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

C'est curieux, car il long long ints'agit d'un entier 64 bits signé et, à toutes fins utiles, identique aux types long intet int64_t, donc logiquement int64_t, long intet long long intserait des types équivalents - l'assemblage généré lors de l'utilisation de ces types est identique. Un regard sur stdint.hme dit pourquoi:

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

Dans une compilation 64 bits, int64_testlong int pas un long long int(évidemment).

La solution à cette situation est assez simple:

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

Mais c'est horriblement hackish et ne s'adapte pas bien (fonctions réelles de la substance uint64_t, etc.). Ma question est donc la suivante: existe-t-il un moyen de dire au compilateur que a long long intest aussi a int64_t, tout commelong int est?


Mes premières pensées sont que ce n'est pas possible, en raison du fonctionnement des définitions de type C / C ++. Il n'y a pas de moyen de spécifier l'équivalence de type des types de données de base au compilateur, puisque c'est le travail du compilateur (et permettre que cela puisse casser beaucoup de choses) ettypedef ne va que dans un sens.

Je ne suis pas non plus trop soucieux d'obtenir une réponse ici, car il s'agit d'un cas super-duper edge dont je ne soupçonne pas que quiconque se souciera jamais lorsque les exemples ne sont pas horriblement artificiels (cela signifie-t-il que cela devrait être un wiki de la communauté?) .


Ajouter : La raison pour laquelle j'utilise la spécialisation partielle des modèles au lieu d'un exemple plus simple comme:

void go(int64_t) { }

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

est que ledit exemple sera toujours compilé, car il long long intest implicitement convertible en unint64_t .


Ajouter : La seule réponse à ce jour suppose que je veux savoir si un type est 64 bits. Je ne voulais pas induire les gens en erreur en leur faisant croire que je me soucie de cela et que j'aurais probablement dû fournir plus d'exemples d'où ce problème se manifeste.

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

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

Dans cet exemple, some_type_trait<long int>sera un boost::true_type, maissome_type_trait<long long int> ne sera pas. Bien que cela ait du sens dans l'idée des types de C ++, ce n'est pas souhaitable.

Un autre exemple consiste à utiliser un qualificatif comme same_type(qui est assez courant à utiliser dans les concepts C ++ 0x):

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

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

Cet exemple ne parvient pas à se compiler, car C ++ voit (correctement) que les types sont différents. g ++ ne parviendra pas à se compiler avec une erreur comme: aucun appel de fonction correspondantsame_type(long int&, long long int&) .

Je tiens à souligner que je comprends pourquoi cela se produit, mais je recherche une solution de contournement qui ne me force pas à répéter le code partout.

Travis Gockel
la source
Par curiosité, votre exemple de programme donne-t-il les mêmes résultats pour sizeofchaque type? Peut-être que le compilateur traite la taille de long long intdifféremment.
Blair Holloway
Avez-vous compilé avec C ++ 0x activé? C ++ 03 ne l'a pas <cstdint>, alors peut-être que le fait qu'il doive dire "ceci est une extension" (ce que c'est) le déjoue.
GManNickG
Oui, j'aurais probablement dû préciser que j'utilise --std=c++0x. Et oui, sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8.
Travis Gockel
1
Personne ne l'a encore mentionné, mais au cas où cela aurait été négligé: longet long longsont des types distincts (même s'ils ont la même taille et la même représentation). int64_test toujours un alias pour un autre type existant (malgré son nom, typedefne crée pas de nouveaux types, il donne juste un alias à celui qui existe déjà)
MM
3
Une déclaration importante manque dans les réponses / commentaires, ce qui m'a aidé lorsque cette bizarrerie m'a frappé: n'utilisez jamais de types de taille fixe pour spécialiser de manière fiable les modèles. Utilisez toujours des types de base et couvrez tous les cas possibles (même si vous utilisez des types de taille fixe pour instancier ces modèles). Tous les cas possibles signifient: si vous devez instancier avec int16_t, spécialisez-vous avec shortet intet vous serez couvert. (et avec signed charsi vous vous sentez aventureux)
Irfy

Réponses:

49

Vous n'avez pas besoin d'aller en 64 bits pour voir quelque chose comme ça. Pensez int32_taux plates-formes 32 bits courantes. Il peut être typedef«édité comme intou comme un long, mais évidemment seulement un des deux à la fois. intet longsont bien sûr des types distincts.

Il n'est pas difficile de voir qu'il n'y a pas de solution de contournement int == int32_t == longsur les systèmes 32 bits. Pour la même raison, il n'y a aucun moyen de faire long == int64_t == long longsur des systèmes 64 bits.

Si vous le pouviez, les conséquences possibles seraient plutôt douloureuses pour un code surchargé foo(int), foo(long)et foo(long long)- du coup, ils auraient deux définitions pour la même surcharge?!

La solution correcte est que le code de votre modèle ne doit généralement pas reposer sur un type précis, mais sur les propriétés de ce type. Toute la same_typelogique pourrait encore être OK pour des cas spécifiques:

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

C'est-à-dire que la surcharge foo(int64_t)n'est pas définie lorsqu'elle est exactement la même que foo(long).

[modifier] Avec C ++ 11, nous avons maintenant une manière standard d'écrire ceci:

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

[modifier] Ou C ++ 20

long foo(long x);
int64_t foo(int64_t) requires (!std::is_same_v<int64_t, long>);
MSalters
la source
1
Triste nouvelle est, par exemple sur MSVC19 (2017) 64 bits sizeof() longet intest identique, mais std::is_same<long, int>::valuerevient false. Même bizarrerie sur AppleClang 9.1 sur OSX HighSierra.
Ax3l
3
@ Ax3l: Ce n'est pas bizarre. Pratiquement tous les compilateurs depuis ISO C 90 ont au moins une de ces paires.
MSalters
C'est vrai, ce sont des types distincts.
Ax3l
6

Voulez-vous savoir si un type est du même type que int64_t ou voulez-vous savoir si quelque chose est 64 bits? Sur la base de la solution que vous proposez, je pense que vous vous interrogez sur cette dernière. Dans ce cas, je ferais quelque chose comme

template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
Logan Capaldo
la source
1
Vous ne manquez pas un returnet un point-virgule?
casablanca
1
Pourtant, vous devriez utiliser sizeofpour cela.
Ben Voigt
5
long long int et long int ne sont pas du même type, qu'ils aient ou non la même taille. Le comportement n'est pas erroné. C'est juste du C ++.
Logan Capaldo
5
Ce n'est pas une limitation du typage nominal. C'est une limitation du typage nominal dénué de sens . Dans l'ancien temps, la norme de facto était short= 16 bits, long= 32 bits et int= taille native. En ces jours de 64 bits, intet longne signifie plus rien.
dan04
1
@ dan04: Ils ne sont ni plus ni moins significatifs qu'ils ne l'ont jamais été. shortest d' au moins 16 bits, d' intau moins 16 bits et d' longau moins 32 bits, avec (la notation bâclée suit) short <= int <= long. Les «vieux jours» dont vous parlez n'ont jamais existé; il y a toujours eu des variations dans les restrictions imposées par la langue. L'erreur "Tout le monde est un x86" est tout aussi dangereuse que l'ancienne "Tout le monde est une erreur VAX.
Keith Thompson
1

Ma question est donc la suivante: y a-t-il un moyen de dire au compilateur qu'un long long int est aussi un int64_t, tout comme le long int l'est?

C'est une bonne question ou un problème, mais je soupçonne que la réponse est NON.

De plus, a long intpeut ne pas être un long long int.


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

Je crois que c'est libc. Je soupçonne que vous voulez aller plus loin.

Dans les deux compilations 32 bits avec GCC (et avec MSVC 32 et 64 bits), la sortie du programme sera:

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

Linux 32 bits utilise le modèle de données ILP32. Les entiers, les longs et les pointeurs sont de 32 bits. Le type 64 bits est un long long.

Microsoft documente les plages dans les plages de types de données . Le dire long longest équivalent à __int64.

Cependant, le programme résultant d'une compilation GCC 64 bits affichera:

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

Linux 64 bits utilise le LP64modèle de données. Les longs sont 64 bits et long long64 bits. Comme avec 32 bits, Microsoft documente les plages aux plages de types de données et long long est toujours __int64.

Il existe un ILP64modèle de données où tout est 64 bits. Vous devez faire un travail supplémentaire pour obtenir une définition de votre word32type. Voir également des articles tels que Modèles de programmation 64 bits: Pourquoi LP64?


Mais c'est horriblement hackish et ne s'adapte pas bien (fonctions réelles de substance, uint64_t, etc) ...

Ouais, ça va encore mieux. GCC mélange et associe les déclarations qui sont censées prendre des types 64 bits, il est donc facile d'avoir des problèmes même si vous suivez un modèle de données particulier. Par exemple, ce qui suit provoque une erreur de compilation et vous indique d'utiliser -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);

Il en résulte:

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

Alors, ignorez-le LP64et changez-le en:

typedef unsigned long long word64;

Ensuite, dirigez-vous vers un gadget ARM IoT 64 bits qui définit LP64et utilise NEON:

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