是否有可能在编译时测试类型是否支持C ++中的负零?

问题描述 投票:8回答:4

有没有办法写一个类型特征来确定一个类型是否支持C ++中的负零(包括整数表示,如sign-and-magnitude)?我没有看到任何直接做到这一点,std::signbit似乎不是constexpr

澄清:我问,因为我想知道这是否可能,无论用例是什么,如果有的话。

c++ types typetraits signed negative-zero
4个回答
3
投票

最好的方法是排除在编译时签名为零的可能性,但在编译时它永远不会完全肯定它的存在。 C ++标准在防止在编译时检查二进制表示方面有很长的路要走:

  • reinterpret_cast<char*>(&value)禁止constexpr
  • 使用union类型来规避constexpr中的上述规则也是被禁止的。
  • 整数类型的零和负零的操作表现完全相同,per-c ++标准,无法区分。
  • 对于浮点运算,在常量表达式中禁止除以零,因此测试1/0.0 != 1/-0.0是不可能的。

唯一能测试的是整数类型的域是否足够密集以排除有符号的零:

template<typename T>
constexpr bool test_possible_signed_zero()
{
    using limits = std::numeric_limits<T>;
    if constexpr (std::is_fundamental_v<T> &&
           limits::is_exact &&
           limits::is_integer) {
        auto low = limits::min();
        auto high = limits::max();
        T carry = 1;
        // This is one of the simplest ways to check that
        // the max() - min() + 1 == 2 ** bits
        // without stepping out into undefined behavior.
        for (auto bits = limits::digits ; bits > 0 ; --bits) {
            auto adder = low % 2 + high %2 + carry;
            if (adder % 2 != 0) return true;
            carry = adder / 2;
            low /= 2;
            high /= 2;
        }
        return false;
    } else {
        return true;
    }
}

template <typename T>
class is_possible_signed_zero:
 public std::integral_constant<bool, test_possible_signed_zero<T>()>
{};
template <typename T>
constexpr bool is_possible_signed_zero_v = is_possible_signed_zero<T>::value;

只保证如果此特征返回false,则无法签名为零。这种保证非常薄弱,但我看不出任何更强有力的保证。此外,它没有说浮点类型的任何建设性。我找不到任何合理的方法来测试浮点类型。


4
投票

不幸的是,我无法想象一种方法。事实是C标准认为类型表示不应该是程序员的关注(*),而只是告诉实现者他们应该做什么。

作为程序员,您必须知道的是:

  • 2补码不是负整数的唯一可能表示
  • 负0可能存在
  • 对整数的算术运算不能返回负0,只能按位运算

(*)这里的意见:了解内部表示可能会导致程序员使用盲目忽略严格别名规则的旧的优化。如果您将类型视为不能在标准操作中使用的不透明对象,那么您的可移植性问题就会减少......


2
投票

有人会过来指出这是完全错误的标准。

无论如何,十进制机器不再允许,并且在这个年代中只有一个负零。实际上,这些测试就足够了:

INT_MIN == -INT_MAX && ~0 == 0

但是你的代码不起作用有两个原因。尽管标准说的是,但是使用主机规则在主机上评估constexprs,并且存在一种在编译时崩溃的体系结构。

试图按摩陷阱是不可能的。 ~(unsigned)0 == (unsigned)-1可靠地测试2s恭维,所以它反过来确实检查了一个人的赞美*;然而,~0是唯一一种在赞美上产生负零的方法,并且任何使用该值作为有符号数都可以陷阱,因此我们无法测试其行为。即使使用特定于平台的代码,我们也无法捕获constexpr中的陷阱,所以forgetaboutit。

*除了真正奇特的算术,但嘿

每个人都使用#defines进行架构选择。如果您需要知道,请使用它。

如果你递给我一个实际的标准投诉编译器,它在constexpr中的陷阱上产生编译错误并使用目标平台规则而不是主机平台规则和转换结果进行评估,我们可以这样做:

target.o: target.c++
    $(CXX) -c target.c++ || $(CC) -DTRAP_ZERO -c target.c++

bool has_negativezero() {
#ifndef -DTRAP_ZERO
        return INT_MIN == -INT_MAX && ~0 == 0;
#else
        return 0;
#endif
}

1
投票

C ++中的标准std::signbit函数有一个接收整数值的构造函数

  • bool signbit( IntegralType arg );(4)(自C ++ 11以来)

所以你可以查看static_assert(signbit(-0))。但是有一个脚注(强调我的)

  1. 一组重载或函数模板,接受任何integral类型的arg参数。相当于(2)(论证被投射到double)。

不幸的是,你仍然需要依赖负零的浮点类型。您可以强制使用带有std::numeric_limits<double>::is_iec559的带符号零的IEEE-754

类似地,std::copysign具有可用于此目的的重载Promoted copysign ( Arithmetic1 x, Arithmetic2 y );。不幸的是,根据现行标准,signbitcopysign都不是constexpr,尽管有一些建议可以做到这一点

然而Clang and GCC can already consider those constexpr如果你不想等待标准更新。 Here's their results


负零的系统也具有平衡范围,因此可以检查正负范围是否具有相同的幅度

if constexpr(-std::numeric_limits<int>::max() != std::numeric_limits<int>::min() + 1) // or
if constexpr(-std::numeric_limits<int>::max() == std::numeric_limits<int>::min())
    // has negative zero

事实上-INT_MAX - 1也是如何libraries defined INT_MIN in two's complement

但最简单的解决方案是消除非二元补码,现在几乎不存在

static_assert(-1 == ~0, "This requires the use of 2's complement");

有关:

© www.soinside.com 2019 - 2024. All rights reserved.