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 intи 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не a 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только в одном направлении.

Я также не слишком беспокоюсь о том, чтобы получить здесь ответ, поскольку это супер-пупер крайний случай, который, я не подозреваю, кого-то когда-либо будет волновать, когда примеры не будут ужасно надуманными (означает ли это, что это должна быть вики сообщества?) .


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

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>будет 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другому трактует размер .
Blair Holloway
Вы скомпилировали с включенным C ++ 0x? В C ++ 03 его нет <cstdint>, так что, возможно, тот факт, что он должен сказать «это расширение» (а это так), обманывает его.
GManNickG
Да, наверное, надо было указать, что я использую --std=c++0x. И да, sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8.
Travis Gockel
1
Никто еще не упомянул об этом, но в случае, если это было упущено из виду: longи long longявляются разными типами (даже если они имеют одинаковый размер и представление). int64_tвсегда является псевдонимом для другого существующего типа (несмотря на свое название, typedefне создает новые типы, он просто дает псевдоним для уже существующего)
MM
3
В ответах / комментариях отсутствует одно важное утверждение, которое помогло мне, когда меня поразила эта причуда: никогда не используйте типы фиксированного размера для надежно специализированных шаблонов. Всегда используйте базовые типы и охватывайте все возможные случаи (даже если вы используете типы фиксированного размера для создания экземпляров этих шаблонов). Все возможные случаи означают: если вам нужно создать экземпляр int16_t, специализируйтесь на shortи, intи вы будете защищены. (и signed charесли вы любите приключения)
Irfy

Ответы:

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>);
MSalters
источник
1
Печальные новости есть, например, на 64-битном MSVC19 (2017) sizeof() longи intидентичны, но std::is_same<long, int>::valueвозвращаются false. Та же странность на AppleClang 9.1 на OSX HighSierra.
Ax3l 06
3
@ Ax3l: Это не странно. Практически каждый компилятор, начиная с ISO C 90, имеет хотя бы одну такую ​​пару.
MSalters 07
Это правда, это разные типы.
Ax3l 08
6

Вы хотите знать, является ли тип тем же типом, что и int64_t, или вы хотите знать, является ли что-то 64-битным? Основываясь на предложенном вами решении, я думаю, вы спрашиваете о последнем. В этом случае я бы сделал что-то вроде

template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
Логан Капальдо
источник
1
Разве вы не пропустили returnи точку с запятой?
casablanca
1
Тем не менее, вы должны использовать sizeofдля этого.
Ben Voigt
5
long long int и long int не являются одним и тем же типом, независимо от того, имеют ли они одинаковый размер. Поведение не ошибочное. Это просто C ++.
Logan Capaldo
5
Это не ограничение номинального набора текста. Это ограничение бессмысленного номинального набора текста. Раньше стандартом де-факто было short16 бит, long32 бита и intсобственный размер. В наши дни 64-битные intи longбольше ничего не значат .
dan04
1
@ dan04: Они не более и менее значимы, чем когда-либо. shortсоставляет не менее 16 бит, intне менее 16 бит и longне менее 32 бит, с (следует небрежная запись) short <= int <= long. «Старые дни», о которых вы говорите, никогда не существовали; всегда были различия в пределах ограничений, налагаемых языком. Заблуждение «Весь мир - x86» так же опасно, как и заблуждение «Весь мир - VAX».
Кейт Томпсон
1

Итак, мой вопрос: есть ли способ сообщить компилятору, что long long int также является int64_t, как и long int?

Это хороший вопрос или проблема, но я подозреваю, что ответ - НЕТ.

Кроме того, a long intможет не быть 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 документирует диапазоны как диапазоны типов данных . Сказать long longэквивалентно __int64.

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

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

64-битный Linux использует LP64модель данных. Long 64-битные и long long64-битные. Как и в случае с 32-разрядной версией, Microsoft документирует диапазоны в Data Type Ranges, а long long по-прежнему остается __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*'
jww
источник