Подтвердить что ты не робот

Обход ограничения на реинтерпрет с помощью constexpr

В С++ 11 выражение constexpr не может содержать повторные интерпретации. Так, например, если вы хотите манипулировать битами числа с плавающей запятой, скажем, чтобы найти мантиссону числа:

constexpr unsigned int mantissa(float x) { 
    return ((*(unsigned int*)&x << 9) >> 9); 
};

Приведенный выше код не будет constexpr. Теоретически я не вижу, как реинтерпрет в этом или подобном случае может быть любым, отличным от арифметических операторов, но не соответствует ему (и стандарту).

Есть ли какой-нибудь умный способ обойти это ограничение?

4b9b3361

Ответ 1

Я не вижу, как реинтерпрет в этом или подобных случаях может быть любым отличной от арифметических операторов

Он не переносится.

Вероятно, вам известно о том, что ваш код вызывает поведение undefined, так как вы разыскиваете строковый указатель типа и, таким образом, нарушаете строгий псевдоним. Более того, поскольку С++ 14, операции, вызывающие поведение undefined, больше не являются постоянными выражениями и, следовательно, должны приводить к ошибке компилятора.

То, что вы в основном пытаетесь сделать, это псевдоним объекта float со встроенным значением glvalue. Первый шаг - получить это значение gl; второй для выполнения преобразования lvalue-rvalue.

В С++ 14 первый шаг невозможно выполнить в постоянных выражениях. reinterpret_cast явно запрещен. И отбрасывается в void* и void*, например static_cast<char const*>(static_cast<void const*>(&x)), также не работают (N3797, [expr.const]/2 *):

- преобразование из типа cv void * в тип указателя к объекту;

Имейте в виду, что c-стиль, подобный (char*), сводится к static_cast или reinterpret_cast, ограничения которого перечислены выше. (unsigned*)&x поэтому сводится к reinterpret_cast<unsigned*>(&x) и не работает.

В С++ 11 приведение к void const*, а затем к char const* не представляет проблемы (согласно стандарту, Clang все еще жалуется на последнее). Преобразование lvalue-to-rvalue является тем не менее:

преобразование lvalue-to-rvalue (4.1), если оно не применяется к glvalue интегрального или перечисляемого типа, который относится к энергонезависимой const с предыдущей инициализацией, инициализированной постоянное выражение, или
- значение glvalue типа literal, которое ссылается на энергонезависимый объект, определенный с помощью constexpr, или который ссылается на под-объект такого объекта, или
- значение glvalate типа, которое относится к энергонезависимому временному объекту, срок службы которого не завершено, инициализировано постоянным выражением;

Первые две пули не могут применяться здесь; Также не существует char/unsigned/etc. объект инициализировался ранее, и мы не определяли какой-либо такой объект с помощью constexpr.

Третий пуля не применяется. Если мы напишем

char ch = *(char const*)(void const*)&x;

мы не создаем объект char в инициализаторе. Мы получаем доступное значение x через glvalue типа char и используем это значение для инициализации ch.

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


* Абзац - это список, начинающийся с чего-то вроде

Условное выражение является выражением основной константы, если только [...]

(текст отличается от N3337 по N3797.)

Ответ 2

Ваш конкретный пример получения мантиссы числа float на самом деле довольно прост для реализации для чисел без кастования типа и, таким образом, реализует его в стиле constexpr. Единственная проблема заключается в том, когда вы хотите взломать NaNs.

Поскольку вы уже полагаетесь на float на binary32 из IEEE 754, мы можем предположить одно и то же, но по-другому - представить результаты. См. Следующий код:

#include <limits>
constexpr float abs(float x) { return x<0 ? -x : x; }

constexpr int exponent(float x)
{
    return abs(x)>=2 ? exponent(x/2)+1 :
           abs(x)<1  ? exponent(x*2)-1 : 0;
}

constexpr float scalbn(float value, int exponent)
{
    return exponent==0 ? value : exponent>0 ? scalbn(value*2,exponent-1) :
                                              scalbn(value/2,exponent+1);
}

constexpr unsigned mantissa(float x)
{
    return abs(x)<std::numeric_limits<float>::infinity() ?
                // remove hidden 1 and bias the exponent to get integer
                scalbn(scalbn(abs(x),-exponent(x))-1,23) : 0;
}

#include <iostream>
#include <iomanip>
#include <cstring>

int main()
{
    constexpr float x=-235.23526f;
    std::cout << std::hex << std::setfill('0');
    // Show non-constexpr result to compare with
    unsigned val; std::memcpy(&val,&x,sizeof val);
    std::cout << std::setw(8) << (val&0x7fffff) << "\n";
    // Now the sought-for constexpr result
    constexpr auto constexprMantissa=mantissa(x);
    std::cout << std::setw(8) << constexprMantissa << "\n";
}

Смотрите свою живую демонстрацию.