【问题标题】:Why is there ambiguity between uint32_t and uint64_t when using size_t on Mac OS X?为什么在 Mac OS X 上使用 size_t 时 uint32_t 和 uint64_t 之间存在歧义?
【发布时间】:2012-07-21 04:11:20
【问题描述】:

考虑以下示例代码:

#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 的错误还是功能?

【问题讨论】:

  • 不确定,但size_t 可能是签名类型
  • @BЈовић 否,标准要求 size_t 未签名。 §18.2/6 说:“类型 size_t 是实现定义的无符号整数类型,它大到足以包含任何对象的字节大小。”
  • 虽然 gcc 有一段时间错误地将其作为有符号类型,但 IIRC。
  • 另一个例子:size_t r; /* ... */ boost::endian::big_to_native_inplace(r); 。在 Linux/Solaris 上这有效,在 Mac OSX 上,由于歧义编译错误而中断 - 因为 boost::endian 只为固定宽度整数类型提供重载。另见:github.com/boostorg/endian/pull/14

标签: c++ macos gcc ambiguous


【解决方案1】:

如果你真的想要,你可以像这样实现你想要的语义:

#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 年更新:如果没有宏,您可以更习惯地做到这一点。 C++14 给了我们简写enable_if_t,C++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

【讨论】:

    【解决方案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
    

    【讨论】:

    • 所以要点是:longlong long 是不同的类型,即使它们具有相同的符号和宽度。 (情况与其他内置类型相同,例如 charsigned char 是不同的类型。)
    • @Philipp 是的,看起来是这样。
    • 好吧,然后我在 Linux 上得到一个重新定义错误(例如,在 64 位 Fedora 17 系统上,在第 2 次和第 3 次过载之间)...
    • @maxschlepzig 为什么不创建一个f(size_t) 函数来避免这个问题?
    • @trojanfoe,因为我想做低级的 32/64 位相关优化,并使用 size_t 选择本机机器字长的版本。使用 uint32_t/uint64_t 重载可以实现这样的专业化——当然,Mac OS X 除外。
    猜你喜欢
    • 2020-09-25
    • 1970-01-01
    • 1970-01-01
    • 2021-05-28
    • 1970-01-01
    • 2012-02-19
    • 1970-01-01
    • 1970-01-01
    • 2012-06-26
    相关资源
    最近更新 更多