web-dev-qa-db-ja.com

静的関数を使用して静的メンバーを初期化するconstexpr

必要条件

constexpr関数から計算されたconstexpr値(つまり、コンパイル時の定数)が必要です。そして、これらの両方をクラスの名前空間、つまりクラスの静的メソッドと静的メンバーにスコープします。

最初の試み

私は最初にこれを(私にとって)明白な方法で書きました:

class C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = foo(sizeof(int));
};

g++-4.5.3 -std=gnu++0xは次のように述べています。

error: ‘static int C1::foo(int)’ cannot appear in a constant-expression
error: a function call cannot appear in a constant-expression

g++-4.6.3 -std=gnu++0xの不満:

error: field initializer is not constant

二度目の試み

OK、私はおそらく、私は物事をクラスの本体から移動させなければならないと考えました。だから私は次のことを試しました:

class C2 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar;
};
constexpr int C2::bar = C2::foo(sizeof(int));

g++-4.5.3は文句なしにコンパイルします。残念ながら、他のコードでは範囲ベースのforループを使用しているため、少なくとも4.6が必要です。 サポートリスト を詳しく見ると、constexprも4.6が必要になるようです。 g++-4.6.3

3:24: error: constexpr static data member ‘bar’ must have an initializer
5:19: error: redeclaration ‘C2::bar’ differs in ‘constexpr’
3:24: error: from previous declaration ‘C2::bar’
5:19: error: ‘C2::bar’ declared ‘constexpr’ outside its class
5:19: error: declaration of ‘const int C2::bar’ outside of class is not definition [-fpermissive]

これは私には本当に奇妙に聞こえます。ここではどのように「constexpr」が異なるのですか? -fpermissiveを追加する気はありません。他のコードを厳密にチェックすることを好みます。 foo実装をクラス本体の外側に移動しても、目に見える効果はありませんでした。

期待される答え

誰かがここで何が起こっているのか説明できますか?私がやろうとしていることをどのように達成できますか?私は主に次の種類の回答に興味があります。

  • Gcc-4.6でこれを機能させる方法
  • 後のgccバージョンがいずれかのバージョンを正しく処理できるという観察
  • 少なくとも1つのコンストラクトshouldが機能する仕様へのポインター。
  • 仕様によると、できればこの制限の背後にある理論的根拠を示すことで、私が望んでいることは不可能であるという情報

他の有用な回答も歓迎しますが、おそらく簡単に受け入れられないでしょう。

31
MvG

この規格には以下が必要です(セクション9.4.2):

リテラル型のstaticデータメンバーは、constexpr指定子を使用してクラス定義で宣言できます。そうである場合、その宣言はbrace-or-equal-initializerを指定するものとし、すべてのinitializer-clauseつまりassignment-expressionは定数式です。

「2回目の試行」とイリヤの答えのコードでは、宣言にbrace-or-equal-initializerはありません。

最初のコードは正しいです。 gcc 4.6がそれを受け入れていないのは残念ですが、4.7.xを便利に試す場所はわかりません(たとえば、ideone.comはgcc 4.5に留まっています)。

残念ながら、標準ではクラスが完全なコンテキストで静的constexprデータメンバーを初期化できないため、これは不可能です。 9.2p2のbrace-or-equal-initializersの特別なルールは、非静的データメンバーにのみ適用されますが、これは1つは静的です。

この理由として最も可能性が高いのは、constexpr変数をメンバー関数の本体内からコンパイル時の定数式として使用できる必要があるため、変数初期化子が関数本体の前に完全に定義されていることです。初期化子のコンテキストではまだ不完全(未定義)であり、このルールが作動して、式が定数式ではなくなります。

constexpr関数またはconstexprコンストラクターの定義外の未定義のconstexpr関数または未定義のconstexprコンストラクターの呼び出し。

考慮してください:

class C1
{
  constexpr static int foo(int x) { return x + bar; }
  constexpr static int bar = foo(sizeof(int));
};
19
Ben Voigt

1)Ilyaの例は、static constexpr data member barが標準外の次のステートメントに違反して行外で初期化されるという事実に基づく無効なコードである必要があります。

9.4.2 [class.static.data] p3:... constexpr指定子を使用して、リテラル型の静的データメンバーをクラス定義で宣言できます。その場合、その宣言は、brace-or-equal-initializerを指定するものとします。このイニシャライザでは、assignment-expressionであるすべてのinitializer-clauseが定数式です。

2)MvGの質問のコード:

class C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = foo(sizeof(int));
};

静的メンバーfoo(int)barが開始します(トップダウン処理を想定)。いくつかの事実:

  • class C1はfooの呼び出し時点で完全ではないことに同意します(9.2p2に基づく)but完全またはクラスC1の不完全性は、標準に関する限りfooが定義されているかどうかについては何も述べていません。
  • メンバー関数の定義について標準を検索しましたが、何も見つかりませんでした。
  • したがって、私のロジックが有効な場合、Benが言及したステートメントはここでは適用されません。

    constexpr関数またはconstexprコンストラクターの定義外の、未定義のconstexpr関数または未定義のconstexprコンストラクターの呼び出し。

class C1
{
  constexpr static int foo() { return bar; }
  constexpr static int bar = foo();
};

無効に見えますが、foobar。ロジックは次のとおりです。

  • foo()は、static constexprメンバーbarの初期化子で呼び出されます、したがって、それは定数式でなければなりません(9.4.2 p3まで)。
  • constexpr関数の呼び出しであるため、関数呼び出しsubstitution(7.1.5 p5)が開始されます。
  • それらは関数へのパラメータではないため、残っているのは「結果として返される式またはbraced-init-listを関数の戻り値の型に暗黙的に変換することで、あたかもコピー初期化によるように」です。 (7.1.5 p5)
  • 戻り式はちょうどbarで、これは左辺値であり、左辺値から右辺値への変換が必要です。
  • しかし、(5.19 p2)の箇条書き9により、barしないはまだ初期化されていないため満足します。

    • 以下に適用されない限り、左辺値から右辺値への変換(4.1):
      • 定数式で初期化された、先行する初期化を持つ不揮発性constオブジェクトを参照する整数型または列挙型のglvalue。
  • したがって、barの左辺値から右辺値への変換では、(9.4.2 p3)の要件を満たさない定数式は生成されません。

  • (5.19 p2)の箇条書き4では、foo()への呼び出しは定数式ではありません:

    関数呼び出し置換(7.1.5)で置換されたときに定数式を生成しない引数を持つconstexpr関数の呼び出し

5
user1574647
#include <iostream>

class C1 
{
public:
    constexpr static int foo(constexpr int x)
    { 
        return x + 1;
    }

    static constexpr int bar;
};

constexpr int C1::bar = C1::foo(sizeof(int));

int main()
{
    std::cout << C1::bar << std::endl;
    return 0;
}

このような初期化はうまく機能しますが、clangでのみ有効です

3
Ilya Lavrenov

おそらく、ここでの問題は、クラス内の宣言/定義の順序に関連しています。ご存知のように、クラスで宣言/定義される前であっても、任意のメンバーを使用できます。

クラスでde constexpr値を定義する場合、コンパイラはクラス内にあるため、使用できるconstexpr関数がありません。

おそらく、このアイデアに関連する Philip answerは、質問を理解するための良い点です。

問題なくコンパイルされる次のコードに注意してください。

constexpr int fooext(int x) { return x + 1; }
struct C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = fooext(5);
};

constexpr static int barext = C1::foo(5);
2
EFenix