2014-10-05 6 views
12

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

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

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

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

+1

Это также недопустимо в контекстах, не связанных с constexpr. Это просто неопределенное. Но это может быть сделано допустимым, и этого все равно не будет достаточно, чтобы сделать его constexpr-valid, поэтому вопрос остается в силе. – hvd

+0

Почему вы хотите сделать это в 'constexpr',' constexpr' новая функция для злоупотребления? –

+0

@ DavidRodríguez-dribeas - действительно. Я пытаюсь изучить границы некоторых возможностей C++ 11, это не настоящая необходимость. – nbubis

ответ

7

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

Это не является переносимым.

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

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

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

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

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

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

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

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

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

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

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

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


* Этот пункт является список, который начинается с чем-то вроде

условно-выражения является постоянным выражением ядра если не [...]

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

6

Ваш конкретный пример получения мантиссы номера 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"; 
} 

См its live demo.