35

要件

関数から計算された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

2 回目の試行

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 つを正しく処理できるという観察
  • 実際に動作させることについて gcc 開発者にバグを報告できるように、少なくとも 1 つの構成要素が動作するはずの仕様へのポインター
  • 私が欲しいものは仕様上不可能であるという情報、できればこの制限の背後にある理論的根拠についての洞察があれば

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

4

4 に答える 4

25

この規格では、次のことが要求されます (セクション 9.4.2)。

リテラル型のデータ メンバーは、指定子staticを使用してクラス定義で宣言できます。constexprその場合、その宣言は、割り当て式であるすべての初期化子節が定数式であるブレースまたは等号初期化子を指定するものとします。

あなたの「2回目の試み」とIlyaの答えのコードでは、宣言にはブレースまたはイコールイニシャライザーがありません。

最初のコードは正しいです。gcc 4.6 がそれを受け入れないのは残念なことです。4.7.x を簡単に試せる場所がどこにもありません (たとえば、ideone.com はまだ gcc 4.5 に固執しています)。

constexpr残念ながら、クラスが完全なコンテキストで静的データ メンバーを初期化することが標準で禁止されているため、これは不可能です。9.2p2のブレースまたはイコール初期化子の特別なルールは、非静的データ メンバーにのみ適用されますが、これは静的です。

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

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

検討:

class C1
{
  constexpr static int foo(int x) { return x + bar; }
  constexpr static int bar = foo(sizeof(int));
};
于 2012-07-17T13:06:46.397 に答える
5

1) Ilya の例は、静的 constexpr データ メンバー barが標準の次のステートメントに違反してアウトオブラインで初期化されているという事実に基づく無効なコードである必要があります。

9.4.2 [class.static.data] p3: ... リテラル型の静的データ メンバは、constexpr 指定子を使用してクラス定義で宣言できます。その場合、その宣言は、割り当て式であるすべての初期化子節が定数式であるブレースまたは等号初期化子を指定するものとします。

2) MvG の質問のコード:

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

私が見る限り有効であり、静的メンバー foo(int)バー開始の時間処理によって定義されるため(トップダウン処理を想定)、直感的に機能すると予想されます。いくつかの事実:

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

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

3) 単純化された Ben の最後の例:

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

無効に見えますが、単純にfooがbarの初期化子で呼び出されたからではなく、さまざまな理由があります。ロジックは次のようになります。

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

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

  • したがって、(5.19 p2) の箇条書き 4 により、foo()の呼び出しは定数式ではありません。

    関数呼び出し置換 (7.1.5) によって置換された場合に定数式を生成しない引数を持つ constexpr 関数の呼び出し

于 2012-08-03T17:06:39.103 に答える
3
#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 でのみ機能します

于 2012-07-17T12:36:17.643 に答える
3

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

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

おそらく、このアイデアに関連するフィリップの回答は、質問を理解するための良い点です。

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

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);
于 2016-07-12T08:54:23.487 に答える