web-dev-qa-db-ja.com

MSVC constexpr関数 'xyz'は定数式にならない

値の2進数表現を維持しながら、複数の小さな値を1つの大きな値に連結する関数を作成しました(例:int argb複数からunsigned char r, g, b, a)。値を少しシフトすることでこれを達成することもできますが、それはこの問題の問題ではありません。

ただし、これらの値から実際に整数を生成するために関数を使用すると、msvcはコンパイラエラーをスローします。

error C3615: constexpr function 'Color::operator int' cannot result in a constant expression
note: failure was caused by call of undefined function or one not declared 'constexpr'
note: see usage of '<lambda_dcb9c20fcc2050e56c066522a838749d>::operator ()'

ここ は完全なサンプルです。 Clangとgccはコードをコンパイルしますが、msvcは拒否します:

#include <type_traits>
#include <memory>

namespace detail
{
    template <typename From, typename To, size_t Size>
    union binary_fusion_helper
    {
        const From from[Size];
        const To to;
    };

    template <typename To, typename Arg, typename ...Args, typename = std::enable_if_t<(... && std::is_same_v<std::remove_reference_t<Arg>, std::remove_reference_t<Args>>)>>
    constexpr To binary_fusion(Arg arg, Args... args)
    {
        using in_t = std::remove_reference_t<Arg>;
        using out_t = To;
        static_assert(sizeof(out_t) == sizeof(in_t) * (sizeof...(Args) + 1), "The target type must be of exact same size as the sum of all argument types.");
        constexpr size_t num = sizeof(out_t) / sizeof(in_t);
        return binary_fusion_helper<in_t, out_t, num> { std::forward<Arg>(arg), std::forward<Args>(args)... }.to;
    }
}

template <typename To>
constexpr auto binary_fusion = [](auto ...values) -> To
{
    return detail::binary_fusion<std::remove_reference_t<To>>(values...);
};

struct Color
{
    float r, g, b, a;

    explicit constexpr operator int() const noexcept
    {
        return binary_fusion<int>(static_cast<unsigned char>(r * 255), static_cast<unsigned char>(g * 255),
                                  static_cast<unsigned char>(b * 255), static_cast<unsigned char>(a * 255));
    }
};

Clangとgccは、コードがconstexprとして実行されないこと、またはmsvcが間違っていることを単に無視しますか?そして、msvcが正しい場合、コンパイル時に関数を実行できないのはなぜですか?

11
Timo

すべてのコンパイラは正しいです。 [dcl.constexpr]/5 のルールは次のとおりです。

デフォルトでもテンプレートでもないconstexpr関数またはconstexprコンストラクターの場合、引数値が存在しないため、関数またはコンストラクターの呼び出しは、コア定数式の評価された部分式、またはコンストラクターの場合は定数初期化子になります。一部のオブジェクト([basic.start.static])、プログラムの形式が正しくないため、診断は必要ありません。

コア定数式として評価できるようにする_binary_fusion_に渡すことができる引数のセットはないため、constexprの宣言は不正な形式のNDRです。これが事実である理由は、detail::binary_fusion()が1つのアクティブなメンバーを使用してユニオンを初期化し、定数式( [expr.const] /4.8 ):

ユニオンまたはそのサブオブジェクトの非アクティブメンバーを参照するglvalueに適用される左辺値から右辺値への変換。

MSVCはこれを何らかの形で診断しますが、gcc/clangはそうではありません。すべてのコンパイラはこれを正しく診断します:

_constexpr Color c{1.0f, 1.0f, 1.0f, 1.0f};
constexpr int i = static_cast<int>(c); // error: not a constant expression
_
15
Barry