Подтвердить что ты не робот

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

Я столкнулся с некоторым нечетным поведением при использовании свойств типа С++ и сузил проблему до этой причудливой маленькой проблемы, для которой я дам тонну объяснения, потому что я не хочу оставлять ничего доступного для неправильной интерпретации.

Скажите, что у вас есть такая программа:

#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, а не long long int (очевидно).

Исправить эту ситуацию довольно легко:

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

Но это ужасно хакерское и плохо масштабируется (фактические функции вещества, uint64_t и т.д.). Итак, мой вопрос: Есть ли способ сообщить компилятору, что long long int является также a int64_t, как и long int?


Мои первоначальные мысли заключаются в том, что это невозможно, из-за того, как работают определения типов 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> не будет. Хотя это имеет смысл в идеях типов С++, это нежелательно.

В другом примере используется классификатор типа same_type (который довольно часто используется в концепциях С++ 0x):

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

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

Этот пример не скомпилируется, поскольку С++ (правильно) видит, что типы разные. g++ не сможет скомпилироваться с ошибкой вроде: никакой вызов функции соответствия same_type(long int&, long long int&).

Я хотел бы подчеркнуть, что я понимаю, почему это происходит, но я ищу обходное решение, которое не заставляет меня повторять код повсюду.

4b9b3361

Ответ 1

Вам не нужно идти в 64-битный, чтобы увидеть что-то вроде этого. Рассмотрим int32_t на обычных 32-битных платформах. Он может быть typedef 'ed как 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);

I.e., перегрузка foo(int64_t) не определена, если она точно такая же, как foo(long).

[править] С С++ 11 теперь у нас есть стандартный способ написать это:

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

Ответ 2

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

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

Ответ 3

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

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

Кроме того, 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. Longs - 64-разрядные, а long long - 64-разрядные. Как и в случае с 32-разрядной версией, Microsoft документирует диапазоны Диапазоны типов данных, а 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*'