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&)
.
Я хотел бы подчеркнуть, что я понимаю, почему это происходит, но я ищу обходное решение, которое не заставляет меня повторять код повсюду.
Ответы
Ответ 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*'