Почему возникает неоднозначность между uint32_t и uint64_t при использовании size_t в Mac OS X?

Рассмотрим следующий пример кода:

#include <iostream>
#include <inttypes.h>

using namespace std;

int f(uint32_t i)
{
  return 1;
}
int f(uint64_t i)
{
  return 2;
}

int main ()
{
  cout << sizeof(long unsigned) << '\n';
  cout << sizeof(size_t) << '\n';
  cout << sizeof(uint32_t) << '\n';
  cout << sizeof(uint64_t) << '\n';
  //long unsigned x = 3;
  size_t x = 3;
  cout << f(x) << '\n';
  return 0;
}

Это не удается на Mac OSX с:

$ g++ --version
i686-apple-darwin10-g++-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5664)
$ make test
g++     test.cc   -o test
test.cc: In function 'int main()':
test.cc:23: error: call of overloaded 'f(size_t&)' is ambiguous
test.cc:6: note: candidates are: int f(uint32_t)
test.cc:10: note:                 int f(uint64_t)
make: *** [test] Error 1

Почему? Потому что 'size_t' должен быть беззнаковым и иметь ширину 32 или 64 бита. Где тогда двусмысленность?

Попытка сделать то же самое с «unsigned long x» вместо «size_t x» приводит к аналогичному сообщению об ошибке неоднозначности.

В системах Linux/Solaris при тестировании с разными версиями GCC, разными архитектурами и т. д. двусмысленности не сообщается (и для каждой архитектуры используется правильная перегрузка).

Это ошибка Mac OS X или функция?


person maxschlepzig    schedule 22.07.2012    source источник
comment
Не уверен, но size_t может быть подписанным типом   -  person BЈовић    schedule 23.07.2012
comment
@BЈовић Нет, стандарт требует, чтобы size_t не было подписано. В §18.2/6 говорится: Тип size_t — это определяемый реализацией целочисленный тип без знака, который достаточно велик, чтобы содержать размер в байтах любого объекта.   -  person Philipp    schedule 23.07.2012
comment
Хотя gcc какое-то время ошибочно считал его подписанным типом, IIRC.   -  person cp.engr    schedule 02.10.2015
comment
Другой пример: size_t r; /* ... */ boost::endian::big_to_native_inplace(r); . В Linux/Solaris это работает, в Mac OSX это прерывается из-за ошибки компиляции неоднозначности, потому что boost::endian предоставляет перегрузки только для целочисленных типов с фиксированной шириной. См. также: github.com/boostorg/endian/pull/14   -  person maxschlepzig    schedule 27.05.2017


Ответы (2)


В Mac OS эти типы определяются как:

typedef unsigned int         uint32_t;
typedef unsigned long long   uint64_t;

Где size_t определяется как __SIZE_TYPE__:

#if defined(__GNUC__) && defined(__SIZE_TYPE__)
typedef __SIZE_TYPE__       __darwin_size_t;    /* sizeof() */
#else
typedef unsigned long       __darwin_size_t;    /* sizeof() */
#endif

Итак, если вы измените свой код на:

#include <iostream>
#include <inttypes.h>

using namespace std;

int f(uint32_t i)
{
  return 1;
}
int f(uint64_t i)
{
  return 2;
}

int f (unsigned long i)
{
  return 3;
}

int main ()
{
  cout << sizeof(unsigned long) << '\n';
  cout << sizeof(size_t) << '\n';
  cout << sizeof(uint32_t) << '\n';
  cout << sizeof(uint64_t) << '\n';
  //long unsigned x = 3;
  size_t x = 3;
  cout << f(x) << '\n';
  return 0;
}

И запустите его, вы получите:

$ g++ -o test test.cpp
$ ./test
8
8
4
8
3
person trojanfoe    schedule 22.07.2012
comment
Итак, суть этого такова: long и long long — разные типы, даже если они имеют одинаковую подпись и ширину. (Ситуация такая же, как и с другими встроенными типами, например, char и signed char — разные типы.) - person Philipp; 23.07.2012
comment
@Philipp Да, похоже, это так. - person trojanfoe; 23.07.2012
comment
Ну, тогда я получаю ошибку переопределения в Linux (например, в 64-битной системе Fedora 17 между 2-й и 3-й перегрузкой) ... - person maxschlepzig; 23.07.2012
comment
@maxschlepzig Почему бы вместо этого не создать функцию f(size_t), чтобы избежать проблемы? - person trojanfoe; 23.07.2012
comment
@trojanfoe, потому что я хочу выполнить низкоуровневую 32/64-битную оптимизацию и использовать size_t для выбора версии для собственного размера машинного слова. Использование перегрузок uint32_t/uint64_t допускает такую ​​специализацию — за исключением, конечно, Mac OS X. - person maxschlepzig; 23.07.2012

Если вы действительно хотите, вы можете реализовать желаемую семантику следующим образом:

#define IS_UINT(bits, t) (sizeof(t)==(bits/8) && \
                          std::is_integral<t>::value && \
                          !std::is_signed<t>::value)

template<class T> auto f(T) -> typename std::enable_if<IS_UINT(32,T), int>::type
{
  return 1;
}

template<class T> auto f(T) -> typename std::enable_if<IS_UINT(64,T), int>::type
{
  return 2;
}

Не сказать, что это хорошая идея; просто сказал, что ты можешь это сделать.

Может быть хороший стандартный C++ способ спросить у компилятора: «Эти два типа одинаковы, вы понимаете, что я имею в виду, не ведите себя глупо со мной», но если есть, я этого не знаю.


ОБНОВЛЕНИЕ 2020: Вы могли бы сделать это более идиоматически без макросов. С++ 14 дал нам сокращение enable_if_t, а С++ 17 дал нам is_integral_v:

template<int Bits, class T>
constexpr bool is_uint_v = 
    sizeof(T)==(Bits/8) && std::is_integral_v<T> && !std::is_signed_v<T>;

template<class T> auto f(T) -> std::enable_if_t<is_uint_v<32, T>, int>
    { return 1; }

template<class T> auto f(T) -> std::enable_if_t<is_uint_v<64, T>, int>
    { return 2; }

А затем в C++20 у нас есть еще более короткое сокращение requires:

template<int Bits, class T>
constexpr bool is_uint_v =
    sizeof(T)==(Bits/8) && std::is_integral_v<T> && !std::is_signed_v<T>;

template<class T> int f(T) requires is_uint_v<32, T> { return 1; }
template<class T> int f(T) requires is_uint_v<64, T> { return 2; }

и еще короче-короче-сокращенные «шаблоны сокращенных функций» (хотя это становится откровенно запутанным, и я бы не рекомендовал его в реальной жизни):

template<class T, int Bits>
concept uint =
    sizeof(T)==(Bits/8) && std::is_integral_v<T> && !std::is_signed_v<T>;

int f(uint<32> auto) { return 1; }  // still a template
int f(uint<64> auto) { return 2; }  // still a template
person Quuxplusone    schedule 25.03.2014