Можно ли проверить, поддерживает ли тип отрицательный ноль в C ++ во время компиляции? - PullRequest
0 голосов
/ 23 февраля 2019

Есть ли способ написать характеристику типа, чтобы определить, поддерживает ли тип отрицательный ноль в C ++ (включая целочисленные представления, такие как знак и величина )?Я не вижу ничего, что напрямую делает это, и std::signbit не выглядит как constexpr.

Чтобы уточнить: я спрашиваю, потому что я хочу знать, возможно ли это, независимо от того,каков вариант использования, если таковой имеется.

Ответы [ 4 ]

0 голосов
/ 06 марта 2019

Кто-нибудь придет и укажет, что это совершенно неправильно по стандартам.

В любом случае, десятичные машины больше не разрешены, и на протяжении веков был только один отрицательный ноль.На практике этих тестов достаточно:

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

, но ваш код не работает по двум причинам.Несмотря на то, что говорится в стандарте, constexprs оцениваются на хосте с использованием правил хоста, и существует архитектура, в которой это происходит сбой во время компиляции.

Попытка отменить ловушку невозможна.~(unsigned)0 == (unsigned)-1 надежно проверяет комплимент 2s, поэтому обратный тест действительно проверяет комплимент *;однако ~0 - единственный способ сгенерировать отрицательный ноль при комплиментах, и любое использование этого значения в качестве числа со знаком может быть перехвачено, поэтому мы не можем проверить его поведение.Даже используя специфичный для платформы код, мы не можем поймать ловушки в constexpr, поэтому forgetaboutit.

*, исключая действительно экзотическую арифметику, но эй

Все используют #define s для выбора архитектуры.Если вам нужно знать, используйте его.

Если вы передали мне фактически стандартный компилятор жалоб, который привел к ошибке компиляции при прерывании в 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
}
0 голосов
/ 24 февраля 2019

Лучшее, что можно сделать, это исключить возможность нулевого знака во время компиляции, но никогда не быть полностью уверенным в его существовании во время компиляции.Стандарт 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, то нулевой ноль невозможен.Эта уверенность очень слабая, но я не вижу более сильной уверенности.Кроме того, он не говорит ничего конструктивного о типах с плавающей точкой.Я не смог найти разумного способа проверить типы с плавающей запятой.

0 голосов
/ 06 марта 2019

Стандартная функция std::signbit в C ++ имеет конструктор, который получает целое значение

  • bool signbit( IntegralType arg ); (4) (начиная с C ++ 11)

Так что вы можете проверить с помощью static_assert(signbit(-0)).Однако на это есть сноска (выделено мной)

Набор перегрузок или шаблон функции, принимающий аргумент arg любого интегрального типа.Эквивалент (2) (аргумент приведен к double) .

, что, к сожалению, означает, что вам все еще приходится полагаться на тип с плавающей точкой с отрицательнымнуль.Вы можете принудительно использовать IEEE-754 с нулевым знаком со знаком std::numeric_limits<double>::is_iec559

Аналогично std::copysign имеет перегрузку Promoted copysign ( Arithmetic1 x, Arithmetic2 y );, которая может использоваться для этой цели.К сожалению, signbit и copysign не являются constexpr в соответствии с текущими стандартами, хотя есть некоторые предложения сделать это

Тем не менее Clang и GCC уже могут учитывать этиconstexpr, если вы не хотите ждать обновления стандарта. Вот их результаты


Системы с отрицательным нулем также имеют сбалансированный диапазон, поэтому можно просто проверить, имеют ли положительный и отрицательный диапазоны одинаковую величину

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 - это также то, как библиотеки определяют INT_MIN в дополнении к двум

Но самое простое решение было бы , исключающее случаи дополнения не-два , чтов настоящее время практически отсутствуют

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

Связанные:

0 голосов
/ 23 февраля 2019

К сожалению, я не могу себе представить способ для этого.Дело в том, что стандарт C считает, что представления типов не должны беспокоить программиста (*), а должны только сказать разработчикам , что они должны делать.

Как программист, все, что у вас естьзнать, что:

  • 2-дополнение не единственное возможное представление для отрицательного целого числа
  • отрицательный 0 может существовать
  • арифметическая операция над целыми числами не может вернутьОтрицательный 0, только побитовая операция может

(*) Мнение здесь: Знание внутреннего представления может привести к тому, что программисты будут использовать старые хорошие оптимизации, которые слепо игнорируют правило строгого псевдонима.Если вы видите тип непрозрачного объекта, который может использоваться только в стандартных операциях, у вас будет меньше вопросов по переносимости ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...