web-dev-qa-db-ja.com

定数式ではなく、メンバー配列を使用してconstexpr関数を呼び出すのはなぜですか?

私は次のヘルパー関数を持っています:

template<typename T, std::size_t N>
constexpr std::size_t Length(const T(&)[N]) {
    return N;
}

これは静的配列の長さを返します。過去にはこれは常に機能していましたが、私がこれを行うと:

struct Foo
{
    unsigned int temp1[3];
    void Bar()
    {
        constexpr std::size_t t = Length(temp1); // Error here
    }
};

MSVS 2017を使用すると、エラーが発生します。

error C2131: expression did not evaluate to a constant

note: failure was caused by a read of a variable outside its lifetime

note: see usage of 'this'

私は誰かが私が間違っていることに光を当てることができることを望んでいました。

28
James

MSVCは正しいです。 Length(temp1)は定数式ではありません。から [expr.const] p2

eは、eの評価がない限り、コア定数式です。抽象マシンのルールに従って、次の式のいずれかを評価します。

  • this、ただしe;の一部として評価されているconstexpr関数またはconstexprコンストラクターを除く。

temp1は暗黙的にthisを評価するため(this->temp1を参照しているため)、定数式はありません。 gccとclangは、拡張機能としてVLAをサポートしているため、これを受け入れます(-Werror=vlaまたは-pedantic-errorsでコンパイルしてみてください)。

なぜこれが許可されないのですか?さて、あなたは基礎となる要素にアクセスし、潜在的にそれらを変更することができます。 constexpr配列または定数式として評価されている配列を扱っている場合、これは完全に問題ありませんが、そうでない場合は、次の値を操作するため、定数式を使用できない可能性があります。実行時に設定されます。

21
Rakete1111
Length(decltype(temp1){})

動作するようです

残念ながら、コメントすることはできませんが、Mehrdadの解決策は間違っています。理由:それは技術的に未定義の動作ではありませんがそれ未定義の振る舞い。 constexprの評価中に、コンパイラは未定義の動作をキャッチする必要があります。したがって、 コードの形式が正しくありません

4
Mi-He

あなたの質問はすでに回答済みですが、それを「修正」する方法に関しては、手っ取り早い方法は置き換えることです

_Length(temp1)
_

_Length(*(true ? NULL : &temp1))
_

これは技術的に未定義の動作ですが、MSVCでは実際には正常に機能すると思います。

UBにもかかわらず機能するソリューションが必要な場合は、ポインターを使用するようにLengthを変更できます。

_template<typename T, std::size_t N>
constexpr std::size_t Length(const T(*)[N]) {
    return N;
}
_

その後、Length(true ? NULL : &temp1)を使用できます。

1
Mehrdad