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

Я испытал странное поведение при использовании черт типа 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&).

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


person Travis Gockel    schedule 12.11.2010    source источник
comment
Из любопытства, дает ли ваша программа-пример одинаковые результаты для sizeof каждого типа? Возможно, компилятор по-другому трактует размер long long int.   -  person Blair Holloway    schedule 12.11.2010
comment
Вы скомпилировали с включенным C ++ 0x? В C ++ 03 нет <cstdint>, поэтому, возможно, тот факт, что он должен сказать, что это расширение (а это так), его обманывает.   -  person GManNickG    schedule 12.11.2010
comment
Да, наверное, мне следовало указать, что я использую --std=c++0x. И да, sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8.   -  person Travis Gockel    schedule 12.11.2010
comment
Никто еще не упомянул об этом, но в случае, если это было упущено из виду: long и long long - разные типы (даже если они имеют одинаковый размер и представление). int64_t всегда является псевдонимом для другого существующего типа (несмотря на свое имя, typedef не создает новые типы, а просто дает псевдоним для уже существующего)   -  person M.M    schedule 20.03.2015
comment
В ответах / комментариях отсутствует одно важное утверждение, которое помогло мне, когда меня поразила эта странность: Никогда не используйте типы фиксированного размера для надежно специализированных шаблонов. Всегда используйте базовые типы и охватывайте все возможные случаи (даже если вы используете типы фиксированного размера для создания экземпляров этих шаблонов). Все возможные случаи означает: если вам нужно создать экземпляр с int16_t, тогда специализируйтесь на short и int, и вы будете защищены. (и с signed char, если вы любите приключения)   -  person Irfy    schedule 03.02.2016


Ответы (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).

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

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

[править] Или C ++ 20

long foo(long x);
int64_t foo(int64_t) requires (!std::is_same_v<int64_t, long>);
person MSalters    schedule 12.11.2010
comment
Печальные новости - это, например, на 64-битном MSVC19 (2017) sizeof() long и int идентичны, но std::is_same<long, int>::value возвращает false. Та же странность на AppleClang 9.1 на OSX HighSierra. - person Ax3l; 06.09.2018
comment
@ Ax3l: Это не странно. Практически каждый компилятор, начиная с ISO C 90, имеет хотя бы одну такую ​​пару. - person MSalters; 07.09.2018
comment
Это правда, это разные типы. - person Ax3l; 08.09.2018

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

template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
person Logan Capaldo    schedule 12.11.2010
comment
Разве вы не пропустили return и точку с запятой? - person casablanca; 12.11.2010
comment
Совсем не то, что я ищу. Этот пример был предоставлен для того, чтобы показать способ проявления ошибки, а не как фактическое требование. - person Travis Gockel; 12.11.2010
comment
Тем не менее, вы должны использовать для этого sizeof. - person Ben Voigt; 12.11.2010
comment
Нет, не надо. template <T> struct has_trivial_destructor : boost::false_type { }; template <> struct has_trivial_destructor<int64_t> : boost::true_type { }; Теперь has_trivial_destructor<long long int> ошибочно будет boost::false_type. Это пример проявления этой проблемы, не имеющей ничего общего с переменным размером. - person Travis Gockel; 12.11.2010
comment
long long int и long int не одного типа, независимо от того, имеют ли они одинаковый размер. Поведение не ошибочное. Это просто C ++. - person Logan Capaldo; 12.11.2010
comment
В то время как C ++ рассматривает их как разные типы, генератор кода GCC обрабатывает их точно так же. Я имею в виду ошибочные в контексте использования, а не в контексте языка. Это подводит меня к моему первоначальному вопросу: как лучше всего решить эту проблему? Я спрашиваю, потому что не думаю, что он есть. - person Travis Gockel; 12.11.2010
comment
Какая проблема? Вы пытаетесь определить, как ведет себя генератор кода, на уровне исходного кода? Или вы просто не хотите набирать template <> struct trait<long long int> { ... }, потому что ожидаете, что template <> struct trait<int64_t> { ... } покроет это? Для компилятора вполне возможно иметь long long int, размер которого превышает 64 бита. Это та же проблема, что и у int32_t, int и long int в некоторых компиляторах. int64_t - это не все возможные встроенные 64-битные числовые типы со знаком, это один из тех встроенных типов, который удовлетворяет условию 64-битности. - person Logan Capaldo; 12.11.2010
comment
Я знаю. Основной вопрос (который мне трудно выразить) на самом деле является вопросом системы типов C ++. int64_t - лишь один из примеров этого. Есть много типов, которые во время генерации кода являются абсолютно одинаковыми (например, long int и long long int в 64-битном GCC), но во времена C ++ они не распознаются как один и тот же тип. Кажется, это фундаментальное ограничение системы типов C ++ - как обойти это чистым (/ не таким уродливым) способом? - person Travis Gockel; 12.11.2010
comment
Не думаю, что я бы назвал это ограничением. Если бы вы могли обойти это, это было бы нарушением принципа номинального равенства типов, присутствующего в C ++. Например, если специализация на long int считается специализацией на long long int, тогда рассмотрите struct A { int a; }; и struct B { int a; };. Должна ли специализация на A также применяться к B? В конце концов, они сгенерируют один и тот же код. Есть языки, которые работают подобным образом, но C ++ не входит в их число. - person Logan Capaldo; 13.11.2010
comment
Тогда было бы справедливо сказать, что это ограничение номинального набора текста? - person Travis Gockel; 13.11.2010
comment
Что ж, это интрично для всей идеи, в C ++ типы идентифицируются по их именам. Это также делает такие полезные вещи, как предварительное объявление pimpl и указатели безопасным по типу. Например, в большинстве C ++ подразумевает, что строки * и int * имеют одинаковый размер. Язык со структурной (под) типизацией будет иметь другие ограничения и компромиссы. Многие шаблоны программирования на C ++ полагаются на теги (пустые структуры), которые перестают работать. Это один из тех пирожных, и его тоже надо есть. - person Logan Capaldo; 13.11.2010
comment
Это не ограничение номинального набора текста. Это ограничение бессмысленного номинального набора текста. В старые времена стандартом де-факто было short = 16 бит, long = 32 бит и int = собственный размер. В наши дни 64-битных int и long больше ничего не значат. - person dan04; 14.11.2010
comment
@ dan04: Они не более и менее значимы, чем когда-либо. short - не менее 16 бит, int - не менее 16 бит, а long - не менее 32 бит, с (следует неряшливая нотация) short ‹= int‹ = long. Старые дни, о которых вы говорите, никогда не существовали; всегда были вариации в пределах ограничений, налагаемых языком. Заблуждение All the world о x86 столь же опасно, как и более старое заблуждение, связанное с VAX. - person Keith Thompson; 13.09.2011

Итак, мой вопрос: есть ли способ сообщить компилятору, что 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. Long - это 64-битные, а long long - 64-битные. Как и в случае с 32-разрядной версией, Microsoft документирует диапазоны в диапазонах типов данных и долго долго еще __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*'
person jww    schedule 30.07.2016