25

Generalized Constant Expressions—Revision 5によると、以下は違法です。

constexpr int g(int n) // error: body not just ‘‘return expr’’
{
    int r = n;
    while (--n > 1) r *= n;
    return r;
}

これは、すべての 'constexpr' 関数が の形式である必要があるため{ return expression; }です。これがそうである必要がある理由はわかりません。

私の考えでは、本当に必要な唯一のことは、外部状態情報が読み書きされず、渡されるパラメーターも「constexpr」ステートメントであることです。これは、同じパラメーターを使用して関数を呼び出しても同じ結果が返されることを意味するため、コンパイル時に「知る」ことができます。

これに関する私の主な問題は、ループの実際のラウンドアバウト形式を強制し、コンパイラがそれを最適化して、非 constexpr 呼び出しと同じくらい高速になることを望んでいるように見えることです。

constexpr上記の例に有効なコードを記述するには、次のようにします。

constexpr int g(int n) // error: body not just ‘‘return expr’’
{
    return (n <= 1) ? n : (n * g(n-1));
}

しかし、これは理解するのがはるかに難しく、 の要件に違反するパラメーターを使用して呼び出すときに、コンパイラーが末尾再帰を処理することを期待する必要がありますconst-expr

4

5 に答える 5

18

その理由は、コンパイラは、本格的なインタープリターではなく、任意の C++ コードを評価できるようにする必要があるためです。

単一の式に固執すると、考慮するケースの数が大幅に制限されます。大まかに言えば、特にセミコロンがないことは物事を大幅に簡素化します。

a に遭遇するたびに;、コンパイラが副作用に対処しなければならないことを意味します。これは、前のステートメントで一部のローカル状態が変更されたことを意味し、次のステートメントはこれに依存します。これは、評価されるコードが、前の操作の出力を入力として受け取る一連の単純な操作ではなく、メモリへのアクセスも必要とすることを意味します。これは、推論がはるかに困難です。

一言で言えば、これは:

7 * 2 + 4 * 3

は簡単に計算できます。次のような構文ツリーを作成できます。

   +
  /\
 /  \
 *   *
/\  /\
7 2 4 3

コンパイラは、各ノードでこれらのプリミティブ操作を実行するこのツリーを単純にトラバースすることができ、ルート ノードは暗黙的に式の戻り値になります。

複数の行を使用して同じ計算を記述する場合は、次のようにすることができます。

int i0 = 7;
int i1 = 2;
int i2 = 4;
int i3 = 3;

int i4 = i0 * i1;
int i5 = i2 * i3;
int i6 = i4 + i5;
return i6;

これは解釈がはるかに困難です。メモリの読み取りと書き込みを処理する必要があり、return ステートメントを処理する必要があります。構文ツリーはさらに複雑になりました。変数宣言を処理する必要があります。戻り値を持たないステートメント (ループやメモリ書き込みなど) を処理する必要がありますが、どこかのメモリを変更するだけです。どの記憶?どこ?コンパイラ自身のメモリの一部を誤って上書きした場合はどうなりますか? セグメンテーション違反になったら?

厄介な「what-if」がなくても、コンパイラが解釈しなければならないコードは、はるかに複雑になりました。構文ツリーは次のようになります: (LDSTはそれぞれロード操作とストア操作です)

    ;    
    /\
   ST \
   /\  \
  i0 3  \
        ;
       /\
      ST \
      /\  \
     i1 4  \
           ;
          /\
         ST \
         / \ \
       i2  2  \
              ;
             /\
            ST \
            /\  \
           i3 7  \
                 ;
                /\
               ST \
               /\  \
              i4 *  \
                 /\  \
               LD LD  \
                |  |   \
                i0 i1   \
                        ;
                       /\
                      ST \
                      /\  \
                     i5 *  \
                        /\  \
                       LD LD \
                        |  |  \
                        i2 i3  \
                               ;
                              /\
                             ST \
                             /\  \
                            i6 +  \
                               /\  \
                              LD LD \
                               |  |  \
                               i4 i5  \
                                      LD
                                       |
                                       i6

より複雑に見えるだけでなく、状態が必要になりました。以前は、各サブツリーを個別に解釈できました。現在、それらはすべてプログラムの残りの部分に依存しています。LD リーフ操作の 1 つは、操作が以前に同じ場所で実行されるようにツリーに配置されない限りST意味がありません。

于 2010-07-12T10:02:54.803 に答える
5

ここで混乱が生じた場合に備えて、constexpr関数/式はコンパイル時に評価されることに注意してください。実行時のパフォーマンスに関する懸念はありません。

これを知っていると、constexpr関数で単一の return ステートメントのみを許可する理由は、コンパイラの実装者が定数値を計算するために仮想マシンを作成する必要がないようにするためです。

ただし、これに関する QoI の問題が懸念されます。コンパイラの実装者は、メモ化を実行するのに十分賢いでしょうか?

constexpr fib(int n) { return < 2 ? 1 : fib(n-1) + fib(n-2); }

メモ化がなければ、上記の関数はO(2 n )の複雑さを持ちます。これは、コンパイル時であっても、私が感じたいことではありません。

于 2010-07-12T07:14:40.210 に答える
2

編集:この回答は無視してください。参照された論文は古くなっています。標準では、限定的な再帰が許可されます (コメントを参照してください)。

どちらの形式も違法です。constexpr 関数は定義されるまで呼び出すことができないという制限があるため、constexpr 関数では再帰は許可されません。OPが提供したリンクは、これを明示的に述べています:

constexpr int twice(int x);
enum { bufsz = twice(256) }; // error: twice() isn’t (yet) defined

constexpr int fac(int x)
{ return x > 2 ? x * fac(x - 1) : 1; } // error: fac() not defined
                                       // before use

さらに数行下:

定数式関数が以前に定義された定数式関数のみを呼び出すことができるという要件により、再帰に関連する問題が発生しないことが保証されます。

...

定数式では、すべての形式で再帰を (まだ) 禁止しています。

これらの制限がないと、停止の問題に巻き込まれます(私の他の回答に対するコメントで私の記憶をジョギングしてくれた@Grantに感謝します)。設計者は、恣意的な再帰制限を課すよりも、単に「いいえ」と言う方が簡単だと考えました。

于 2010-07-13T02:34:46.587 に答える
2

私が理解しているように、彼らは言語を複雑にしないようにできるだけ単純に保ちました (実際、再帰呼び出しが許可されていなかった時代を覚えているようですが、それはもはや当てはまりません)。理論的根拠は、ルールを制限するよりも、将来の標準でルールを緩和する方がはるかに簡単だということです。

于 2010-07-12T07:08:32.557 に答える
0

実装が難しすぎるため、おそらく不適切な形式です。obj.func標準の最初のバージョンでは、メンバー関数のクロージャー (つまり、呼び出し可能な関数として渡すことができる) に関して、同様の決定が下されました。おそらく、標準の後の改訂では、より多くの許容範囲が提供されるでしょう。

于 2010-07-12T06:26:52.113 に答える