long long int против long int против int64_t в C ++ - PullRequest
78 голосов
/ 12 ноября 2010

Я столкнулся с некоторым странным поведением при использовании черт типа 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 ]

46 голосов
/ 12 ноября 2010

Вам не нужно переходить на 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);
5 голосов
/ 12 ноября 2010

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

template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
1 голос
/ 31 июля 2016

Итак, мой вопрос: есть ли способ сообщить компилятору, что 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, и используйте NEON:

error: invalid conversion from `word64* {aka long long unsigned int*}' to `uint64_t*'
...