web-dev-qa-db-ja.com

constexprでキャストの再解釈の制限を回避する

C++ 11では、constexpr式に再解釈キャストを含めることはできません。したがって、たとえば、浮動小数点数のビットを操作したい場合は、数値の仮数を検索するとします。

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

上記のコードはconstexprに失敗します。理論的には、このケースまたは同様のケースでキャストされた再解釈が算術演算子とどのように異なるかはわかりませんが、コンパイラー(および標準)では許可されていません。

この制限を回避する賢い方法はありますか?

26
nbubis

このケースまたは同様のケースでキャストされた再解釈が算術演算子とどのように異なる可能性があるのか​​わかりません

ポータブルではありません。

型パンドポインターを逆参照して厳密なエイリアスを解除するため、コードが未定義の動作を引き起こすという事実をおそらく知っています。さらに、C++ 14以降、未定義の動作を呼び出す操作は定数式ではなくなったため、コンパイラエラーが発生します。

基本的にやろうとしているのは、floatオブジェクトを整数のglvalueでエイリアスすることです。最初のステップは、そのglvalueを取得することです。左辺値から右辺値への変換を実行する2番目。

C++ 14では、定数式で最初のステップを実行することは不可能です。 _reinterpret_cast_は明示的に禁止されています。また、static_cast<char const*>(static_cast<void const*>(&x))のように_void*_との間のキャストも機能しません(N3797、[expr.const]/2 *):

—タイプcv _void *_からオブジェクトへのポインタタイプへの変換。

_(char*)_のようなCスタイルのキャストは、上記の制限がある_static_cast_または_reinterpret_cast_のいずれかに縮小されることに注意してください。したがって、_(unsigned*)&x_はreinterpret_cast<unsigned*>(&x)になり、機能しません。

C++ 11では、_void const*_、次に_char const*_へのキャストは問題を構成しません(標準によると、Clangはまだ後者について不平を言っています)。それにもかかわらず、左辺値から右辺値への変換は1つです。

に適用されない限り、左辺値から右辺値への変換(4.1)
-定数式で初期化された、先行する初期化を伴う不揮発性constオブジェクトを参照する、整数型または列挙型のglvalue、または
constexprで定義された不揮発性オブジェクトを参照する、またはそのようなオブジェクトのサブオブジェクトを参照するリテラルタイプのglvalue、または
—定数式で初期化された、存続期間が終了していない不揮発性の一時オブジェクトを参照するリテラルタイプのglvalue。

最初の2つの箇条書きはここでは適用できません。 char/unsigned/etcもありません。オブジェクトは以前に初期化されましたが、そのようなオブジェクトをconstexprで定義することもありませんでした。

3番目の箇条書きも適用されません。書けば

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

初期化子にcharオブジェクトを作成しません。 xタイプのglvalueを介してcharの格納された値にアクセスし、その値を使用してchを初期化します。

したがって、このようなエイリアシングは定数式では不可能だと思います。いくつかの実装では、緩和されたルールでこれを回避できます。


*段落は次のようなもので始まるリストです

条件式コア定数式です[...]

(テキストはN3337からN3797とは異なります。)

13
Columbo

float数の仮数を取得する特定の例は、実際には型のパンニングなしでnumbersに実装するのが非常に簡単です。 constexprの方法で実装します。唯一の問題は、NaNをハックしたいときです。

floatがIEEE754のbinary32であることにすでに依存しているため、同じことを想定できますが、別の方法で結果を提示します。次のコードを参照してください。

#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";
}

そのライブデモ を参照してください。

9
Ruslan