C++: long long int против long int против int64_t
Я испытал некоторое странное поведение при использовании черт типа 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
не long long int
(Очевидно).
Исправить эту ситуацию довольно легко:
#if defined(__GNUC__) && (__WORDSIZE == 64)
template <>
bool is_int64<long long int>() { return true; }
#endif
Но это ужасно хакерский и плохо масштабируется (фактические функции вещества, uint64_t
, так далее). Итак, мой вопрос: есть ли способ сказать компилятору, что long long int
это также 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&)
,
Я хотел бы подчеркнуть, что я понимаю, почему это происходит, но я ищу обходной путь, который не заставляет меня повторять код повсюду.
3 ответа
Вам не нужно переходить на 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)
,
[edit] С C++11 у нас теперь есть стандартный способ написать это:
long foo(long x);
std::enable_if<!std::is_same<int64_t, long>::value, int64_t>::type foo(int64_t);
Вы хотите знать, является ли тип того же типа, что и int64_t, или вы хотите знать, является ли что-то 64-битным? Исходя из предложенного вами решения, я думаю, вы спрашиваете о последнем. В этом случае я бы сделал что-то вроде
template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
Итак, мой вопрос: есть ли способ сообщить компилятору, что 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
модель данных. Лонги 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
и использовать НЕОН:
error: invalid conversion from `word64* {aka long long unsigned int*}' to `uint64_t*'