【问题标题】:Is overloading on all of the fundamental integer types is sufficient to capture all integers?重载所有基本整数类型是否足以捕获所有整数?
【发布时间】:2017-05-23 23:30:29
【问题描述】:

假设我对所有标准整数类型都有函数重载:

void foo( char );
void foo( signed char );
void foo( short );
void foo( int );
void foo( long );
void foo( long long );
// .... all unsigned variants as well

这些重载是否有可能无法为int8_t 或类似的类型找到合适的重载?是否有一种可移植的方式来处理此类重载?

参考文献怎么样?

为了澄清这个问题:它来自对这个问题的讨论Why is int8_t read as a character?,并声称可能有编译器生成的整数类型不是基本 C++ 类型的别名。因此,在这种情况下,所有基本情况的重载可能不会接受它。另一方面,我无法为int8_t 提供重载,因为在许多平台上它只是一个别名,重新定义现有重载时会出错。

【问题讨论】:

  • 不要说“全部”,您能否生成一个明确的列表来列出您正在重载的内容? (说“signedunsigned 每个:”减半就可以了)。因为你所说的“全部”不一定是“全部”。
  • 取决于您对“接受”的定义。您可以将int8_t 传递给定义为采用int 的函数,它会很好地接受它。
  • @VladfromMoscow 但是如果int8_tsigned char,你会得到一个错误,因为你删除了一个你也定义的重载。
  • @VladfromMoscow:他希望它被重载函数接受。这就是他的观点。
  • 令人惊讶的是,有多少愚蠢的匿名反对者不愿理解这个问题。

标签: c++ c++11 language-lawyer


【解决方案1】:

标准不保证标准整数类型是编译器支持的唯一整数类型。事实上,C 和 C++ 标准明确允许编译器定义其他整数类型,统称为“扩展整数类型”:

也可能有实现定义的扩展有符号整数类型

同样,对于每个扩展的有符号整数类型,都存在一个对应的扩展无符号整数类型 ...

扩展有符号整数类型和扩展无符号整数类型统称为扩展整数类型

C 标准不禁止实现使用扩展整数类型来实现stdint.h 类型。甚至还有这种非规范性的符号,只是为了说明清楚:

其中一些类型可能表示实现定义的扩展整数类型。

如果您想要一个可以采用任何整数类型的函数,您有两个选择:将其设为模板(可能使用 SFINAE 来禁止非整数类型)或提供采用 std::intmax_t 的单个重载。标准要求intmax_t 是支持的最大整数:

指定一个有符号整数类型,能够表示任何值 任何有符号整数类型

当然,“有符号整数类型”包括标准类型和扩展类型。所以std::intmax_t 可以处理实现支持的任何有符号整数。

【讨论】:

  • “确实,C 和 C++ 标准明确允许编译器定义其他整数类型”你介意引用标准吗?
  • 您提到了 C 标准,但没有明确说明第一次引用哪个标准。问题是关于 C++,而不是 C。 (据我所知,C 上没有函数重载)
  • @Slava:C++ 的cstdint 的行为完全是参照C 标准定义的。因此,如果您想了解这些类型的行为,您必须查看 C 标准。至于其他引用,大部分可能来自 C 或 C++,因为它们使用几乎相同的语言。
  • 这带来了另一个有趣的问题——stackoverflow.com/questions/41554125/…
  • gcc 提供 __int128_tintmax_t 与 64 位机器上的 int64_t 相同 (live demo)。
【解决方案2】:

int8_t 将隐式转换为 int (事实上,当采用值时,任何有符号整数类型都将转换为 intmax_t,如果有一个函数采用这种类型。通常整数类型在彼此之间隐式转换(我调查了一个奇怪的问题,我为 int 和 long 类型提供了重载[它们在特定编译器上具有不同的大小],而 C++ 编译器抱怨重载解决方案不明确)

另外,int8_tsigned char 通常是等价的。

【讨论】:

  • 问题不是通常的问题,但是否保证所有基本类型的重载都能处理所有整数类型
  • 复整数(复数,两个分量都是整数)和任何自定义类型都不是整数。只有 int 和 char 的所有变体,以及 int8_t、uint32_t 等 typedef 都被认为是整数类型,并且它们都在彼此之间进行隐式转换(即使强制为有损它仍然是隐式的)
  • 看来你不明白这个问题。我不是要求用户定义的类型。问题是“如果编译器允许创建不会被所有基本类型的重载处理的整数类型”
  • 所有内置整数类型在彼此之间隐式转换。即使它可能是有损的。
  • 很可能你做错了什么,但这与这个问题无关,你可能应该创建自己的问题。
【解决方案3】:

不能保证每个平台都会以相同的方式定义固定宽度的整数类型,或者它们将在任何给定平台上根据基本类型来定义。因此,为确保您的重载能够捕获任何给定平台上的固定宽度类型,您需要确定它们在该平台上的实现方式。

虽然您可以使用模板技巧来做到这一点,但最简单的方法是使用一个独特的、最小的帮助程序,它输出每个固定宽度和基本类型的 typeid,允许您比较它们的内部名称来确定每个固定宽度类型是如何在该平台上和使用该编译器实现的。

#include <cstdint>
#include <iostream>
#include <iomanip>
#include <typeinfo>

int main() {
    std::cout << std::left;
    std::cout << std::setw(10) << "int8_t: "   << typeid(int8_t).name()   << '\n'
              << std::setw(10) << "int16_t: "  << typeid(int16_t).name()  << '\n'
              << std::setw(10) << "int32_t: "  << typeid(int32_t).name()  << '\n'
              << std::setw(10) << "int64_t: "  << typeid(int64_t).name()  << '\n'
              << std::setw(10) << "uint8_t: "  << typeid(uint8_t).name()  << '\n'
              << std::setw(10) << "uint16_t: " << typeid(uint16_t).name() << '\n'
              << std::setw(10) << "uint32_t: " << typeid(uint32_t).name() << '\n'
              << std::setw(10) << "uint64_t: " << typeid(uint64_t).name() << '\n'
              << std::endl;

    std::cout << std::setw(20) << "char: "               << typeid(char).name()               << '\n'
              << std::setw(20) << "signed char: "        << typeid(signed char).name()        << '\n'
              << std::setw(20) << "unsigned char: "      << typeid(unsigned char).name()      << '\n'
              << std::setw(20) << "signed short: "       << typeid(signed short).name()       << '\n'
              << std::setw(20) << "unsigned short: "     << typeid(unsigned short).name()     << '\n'
              << std::setw(20) << "signed int: "         << typeid(signed int).name()         << '\n'
              << std::setw(20) << "unsigned int: "       << typeid(unsigned int).name()       << '\n'
              << std::setw(20) << "signed long: "        << typeid(signed long).name()        << '\n'
              << std::setw(20) << "unsigned long: "      << typeid(unsigned long).name()      << '\n'
              << std::setw(20) << "signed long long: "   << typeid(signed long long).name()   << '\n'
              << std::setw(20) << "unsigned long long: " << typeid(unsigned long long).name() << '\n'
              << std::endl;
}

示例输出:

// MSVC 2015 (x86 & x64):
int8_t:   signed char
int16_t:  short
int32_t:  int
int64_t:  __int64
uint8_t:  unsigned char
uint16_t: unsigned short
uint32_t: unsigned int
uint64_t: unsigned __int64

char:               char
signed char:        signed char
unsigned char:      unsigned char
signed short:       short
unsigned short:     unsigned short
signed int:         int
unsigned int:       unsigned int
signed long:        long
unsigned long:      unsigned long
signed long long:   __int64
unsigned long long: unsigned __int64

// Clang & GCC (x64):
int8_t:   a
int16_t:  s
int32_t:  i
int64_t:  l
uint8_t:  h
uint16_t: t
uint32_t: j
uint64_t: m

char:               c
signed char:        a
unsigned char:      h
signed short:       s
unsigned short:     t
signed int:         i
unsigned int:       j
signed long:        l
unsigned long:      m
signed long long:   x
unsigned long long: y

从这里:

  • MSVC(在 x86 和 x64 Windows 上,因为 ILP32 和 LLP64 使用相同的类型大小):我们可以看到固定宽度类型基于基本类型;请注意,对于 64 位整数类型,编译器将发出内部名称 __int64 long long 之前的同义词被标准化,如 Bo Persson pointed out in the comments 而不是规范名称 long long
  • Clang 和 GCC(在 x64 *nix 上):通过比较重整的类型名称,我们可以看到:
    • 8 位类型基于char 类型。
    • 16 位类型基于short 类型。
    • 32 位类型基于int 类型。
    • 64 位类型基于long 类型。

由此,我们知道在这些平台上,使用这些编译器:

  • signed char 会赶上int8_t
  • unsigned char 会赶上uint8_t
  • signed short 会赶上int16_t
  • unsigned short 会赶上uint16_t
  • 等等……
  • [请注意,由于 Windows 的 LLP64 和 *nix 的 LP64 模型之间的差异,int64_tuint64_t 将被不同的重载捕获。]

然后,您可以将此过程应用于您可能希望使用的任何其他平台和编译器,以便您检测何时需要进行特定于平台的修改。


不过,这似乎是一个 XY 问题。您的主要问题是您希望能够在与基本类型不同的平台上为固定宽度类型重载,但不知道如何在不破坏它们所在平台上的代码的情况下执行此操作与基本类型相同。

为此,您需要找到一种唯一标识您所在平台的方法;最简单的方法是通过looking for macros specific to that platform

void func(          char);
void func(  signed  char);
void func(unsigned  char);
void func(         short);
void func(unsigned short);
// ...

#ifdef PLATFORM_WHERE_INT8_T_ISNT_SIGNED_CHAR
    void func( int8_t);
    void func(uint8_t);
#endif

#ifdef PLATFORM_WHERE_INT16_T_ISNT_SIGNED_SHORT
    void func( int16_t);
    void func(uint16_t);
#endif

// ...

使用这样的设置,您可以为固定宽度类型不是基本类型的平台定义额外的重载,而不会影响它们基本类型的平台。这将允许您在将它们交给您的基本类型重载之一(如有必要)之前进行任何特定于平台的处理。

【讨论】:

  • 在 MSVC __int64 is just an alias for long long。它不是一个单独的类型,只是在long long 出现在标准之前使用的旧名称。
  • @BoPersson 好的,很高兴知道。我会更新答案以提及这一点。
【解决方案4】:

这些重载是否有可能不接受 int8_t 之类的类型?

不,不适用于int8_t。即使int8_t 不是标准整数类型之一的别名,它仍然可以转换为其他整数类型,并且毫无疑问地首选提升为int。因此,带有扩展 int8_t 的调用将解析为其中一个重载。

但是,对于较大的类型,这仍然是一个问题。

【讨论】:

  • 我不想删除重载,问题是我无法以可移植的方式为int8_t 创建重载。
  • @Slava 你当然可以创建一个重载。但它也将是别名基本类型的重载。而且这两个假设都不是可移植的:别名适用于标准类型,或者别名适用于扩展类型。
  • 在这种类型是别名的平台上,除了对基本类型的重载之外,我无法创建这样的重载。
  • @Slava 完全正确。
  • @Slava 我重写并大大简化了我的答案,现在我理解了意图。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-04-19
  • 1970-01-01
  • 1970-01-01
  • 2017-03-08
  • 2014-12-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多