long long int проти long int проти int64_t в C ++


87

Я відчував певну дивну поведінку під час використання рис типу C ++ і звузив свою проблему до цієї химерної маленької проблеми, для якої я дам масу пояснень, оскільки не хочу залишати нічого відкритим для неправильного тлумачення.

Скажімо, у вас така програма:

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

І в 32-розрядному компілюванні з GCC (і в 32- і 64-розрядному MSVC) результат програми буде таким:

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

Однак програма, отримана в результаті 64-розрядної компіляції GCC, видасть:

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

Це цікаво, оскільки long long intє 64-розрядним цілим числом із підписом і для всіх намірів і цілей ідентично типам long intand int64_t, що логічно int64_t, long intі long long intбуде еквівалентними типами - збірка, створена при використанні цих типів, ідентична. Один погляд на це stdint.hговорить мені, чому:

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

У 64-розрядної компіляції int64_tє long int, а не long long int(очевидно).

Виправити ситуацію досить просто:

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

Але це жахливо хакі і погано масштабується (фактичні функції речовини uint64_tтощо). Тож моє запитання: чи є спосіб сказати компілятору, що a long long int- це також a int64_t, так само, як long intє?


Я спочатку думав, що це неможливо через те, як працюють визначення типів C / C ++. Немає способу вказати еквівалентність типів основних типів даних компілятору, оскільки це робота компілятора (і дозволяючи, що може зламати багато речей) і typedefпроходить лише один шлях.

Я також не надто стурбований тим, щоб отримати тут відповідь, оскільки це надзвичайний випадок, про який я не підозрюю, що комусь коли-небудь буде все одно, коли приклади не жахливо надумані (чи означає це, що це має бути wiki-спільнота?) .


Додаток : Причина, чому я використовую часткову спеціалізацію на шаблонах замість більш простого прикладу, наприклад:

void go(int64_t) { }

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

полягає в тому, що згаданий приклад все одно буде компілюватися, оскільки long long intнеявно конвертується вint64_t .


Додаток : Поки що єдина відповідь передбачає, що я хочу знати, чи є тип 64-бітовим. Я не хотів вводити людей в оману, думаючи, що я піклуюся про це, і, мабуть, мав би навести більше прикладів того, як ця проблема проявляється.

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

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

У цьому прикладі some_type_trait<long int>буде a boost::true_type, алеsome_type_trait<long long int> не буде. Хоча це має сенс у уявленні про типи типів на C ++, це не бажано.

Інший приклад - використання такого кваліфікатора, як same_type(який досить часто використовується у C ++ 0x Concepts):

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

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

Цей приклад не вдається скомпілювати, оскільки C ++ (правильно) бачить, що типи різні. g ++ не вдасться скомпілювати з помилкою типу: відсутність відповідного виклику функціїsame_type(long int&, long long int&) .

Я хотів би наголосити, що розумію, чому це відбувається, але я шукаю обхідний шлях, який не змушує мене повторювати код повсюдно.


З цікавості ваша зразкова програма дає однакові результати для sizeofкожного типу? Можливо, компілятор по- long long intрізному ставиться до розміру .
Блер Холлоуей,

Ви компілювали з включеним C ++ 0x? C ++ 03 не має <cstdint>, тому, можливо, той факт, що він повинен сказати "це розширення" (що воно є), стримує його.
GManNickG

Так, мабуть, мені слід було вказати, що я використовую --std=c++0x. І так, sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8.
Тревіс Гоккель,

1
Про це ще ніхто не згадував, але на випадок, якщо це було пропущено: longі long longце різні типи (навіть якщо вони мають однаковий розмір і представлення). int64_tзавжди є псевдонімом для іншого існуючого типу (незважаючи на свою назву, typedefне створює нових типів, він просто надає псевдонім тому, що вже існує)
MM

3
У відповідях / коментарях бракує одного важливого твердження, яке допомогло мені, коли мене вразила ця примха: Ніколи не використовуйте типи фіксованого розміру для надійно спеціалізованих шаблонів. Завжди використовуйте основні типи та охоплюйте всі можливі випадки (навіть якщо ви використовуєте типи фіксованого розміру для створення екземплярів цих шаблонів). Усі можливі випадки означають: якщо вам потрібно створити інстанцію з int16_t, тоді спеціалізуйтесь на shortі, intі вас покриють. (і signed charякщо ви відчуваєте авантюру)
Ірфі 02

Відповіді:


49

Вам не потрібно переходити до 64-розрядної версії, щоб побачити щось подібне. Розглянемо int32_tзагальні 32-розрядні платформи. Це може бути typedef"як" intабо "як" long, але, очевидно, лише одне з двох одночасно. intі longзвичайно це різні типи.

Неважко зрозуміти, що int == int32_t == longв 32-розрядних системах не існує жодного обхідного шляху . З тієї ж причини long == int64_t == long longна 64-розрядних системах неможливо заробити .

Якби ви могли, можливі наслідки були б досить болючими для коду, який перевантажився foo(int), foo(long)і foo(long long)- раптом вони мали б два визначення для однакового перевантаження ?!

Правильним рішенням є те, що код вашого шаблону зазвичай повинен покладатися не на точний тип, а на властивості цього типу. Вся same_typeлогіка все одно може бути нормальною для конкретних випадків:

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

Тобто перевантаження foo(int64_t)не визначається, коли воно точно таке ж, як foo(long).

[редагувати] З C ++ 11 тепер у нас є стандартний спосіб написати це:

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

[редагувати] Або C ++ 20

long foo(long x);
int64_t foo(int64_t) requires (!std::is_same_v<int64_t, long>);

1
Сумні новини, наприклад, на 64-бітному MSVC19 (2017) sizeof() longі intідентичні, але std::is_same<long, int>::valueповертаються false. Та сама дивина на AppleClang 9.1 на OSX HighSierra.
Ax3l

3
@ Ax3l: Це не дивно. Практично кожен компілятор, починаючи з ISO C 90, має принаймні одну таку пару.
MSalters

Це правда, це різні типи.
Ax3l

6

Ви хочете знати, чи тип є тим самим типом, що і int64_t, чи хочете знати, чи є щось у 64 біти? На основі запропонованого вами рішення, я думаю, ви запитуєте про останнє. У такому випадку я зробив би щось подібне

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

1
Вам не бракує returnкрапки з комою та крапки з комою?
casablanca

1
Тим не менш, ви повинні використовувати sizeofдля цього.
Бен Войгт

5
long long int і long int не є однаковими, незалежно від того, однакові за розміром. Поведінка не помилкова. Це просто C ++.
Логан Капальдо,

5
Це не обмеження номінального набору тексту. Це обмеження безглуздого іменного набору тексту. За старих часів фактичним стандартом було short= 16 біт, long= 32 біт і int= власний розмір. У наші дні 64-розрядні, intі вже longнічого не означають .
dan04

1
@ dan04: Вони не більш-менш значущі, ніж були коли-небудь. shortмає щонайменше 16 біт, intщонайменше 16 біт і longщонайменше 32 біт, з (недбалим позначенням) короткою <= int <= long. «Старі часи», про які ви говорите, ніколи не існували; завжди існували розбіжності в межах обмежень, накладених мовою. Помилка "Весь світ x86" настільки ж небезпечна, як і старша "Помилка VAX у всьому світі"
Кіт Томпсон,

1

Тож моє запитання: чи є спосіб сказати компілятору, що long long int - це також int64_t, як і long int?

Це хороше питання або проблема, але я підозрюю, що відповідь - НІ.

Крім того, a long intне може бути a long long int.


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

Я вважаю, що це libc. Я підозрюю, що ти хочеш піти глибше.

І в 32-розрядному компілюванні з GCC (і в 32- і 64-розрядному MSVC) результат програми буде таким:

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

32-розрядна Linux використовує модель даних ILP32. Цілі числа, довготи та покажчики є 32-розрядними. 64-розрядний тип - це long long.

Microsoft документує діапазони в діапазонах типів даних . Сказати the long longеквівалентно __int64.

Однак програма, отримана в результаті 64-розрядної компіляції GCC, видасть:

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

64-розрядна версія Linux використовує LP64модель даних. Лонги є 64-розрядними і long longє 64-розрядними. Як і у випадку з 32-розрядними версіями, Microsoft документує діапазони в діапазонах типів даних і довгими довгими __int64.

Існує ILP64модель даних, де все є 64-розрядною. Вам потрібно зробити додаткову роботу, щоб отримати визначення для вашого word32типу. Також див. Статті, такі як 64-розрядні моделі програмування: чому LP64?


Але це жахливо хакі і погано масштабується (фактичні функції речовини, uint64_t тощо) ...

Так, це стає ще кращим. GCC змішує та поєднує декларації, які мають приймати 64-бітові типи, тому легко потрапити в проблеми, навіть якщо ви дотримуєтесь певної моделі даних. Наприклад, наступне викликає помилку компіляції та повідомляє про використання -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);

Це призводить до:

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

Отже, проігноруйте LP64та змініть його на:

typedef unsigned long long word64;

Потім перейдіть до 64-розрядного гаджета ARM IoT, який визначає LP64та використовує NEON:

error: invalid conversion from `word64* {aka long long unsigned int*}' to `uint64_t*'
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.