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*'
Другие вопросы по тегам