3

次のテスト コードで観察されるように、私の暫定的な答えはノーです。

#include <functional>
#include <iostream>
#include <string>
#include <vector>

using namespace std;

void TestFunc (void);
int TestFuncHelper (vector<int>&, int, int);

int main (int argc, char* argv[]) {
    TestFunc ();
    return 0;
} // End main ()

void TestFunc (void) {
    // Recursive lambda
    function<int (vector<int>&, int, int)> r = [&] (vector<int>& v_, int d_, int a_) {
        if (d_ == v_.size ()) return a_;
        else return r (v_, d_ + 1, a_ + v_.at (d_));
    };
    int UpperLimit = 100000; // Change this value to possibly observe different behaviour
    vector<int> v;
    for (auto i = 1; i <= UpperLimit; i++) v.push_back (i);
    // cout << TestFuncHelper (v, 0, 0) << endl; // Uncomment this, and the programme works
    // cout << r (v, 0, 0) << endl; // Uncomment this, and we have this web site
} // End Test ()

int TestFuncHelper (vector<int>& v_, int d_, int a_) {
    if (d_ == v_.size ()) return a_;
        else return TestFuncHelper (v_, d_ + 1, a_ + v_.at (d_));
} // End TestHelper ()

ラムダで再帰的な末尾呼び出しを最適化するようにコンパイラに強制する方法はありますか?

よろしくお願いします。

編集

C++11 がラムダの再帰的な末尾呼び出しを最適化するかどうかを尋ねるつもりだったことを明確にしたかっただけです。私は Visual Studio 2012 を使用していますが、GCC が目的の最適化を行うことが確実にわかっている場合は、環境を切り替えることができます。

4

1 に答える 1

9

少なくとも直接ではなく、「ラムダ」コードで実際にテールコールを行っているわけではありません。std::function態的な関数ラッパーです。つまり、あらゆる種類の呼び出し可能なエンティティを格納できます。C++ のラムダには、名前のない一意のクラス型があり、std::functionオブジェクトではなく、単に格納できます。

型消去を使用するためstd::function、最初に渡されたものを呼び出すには、いくつかのフープをジャンプする必要があります。これらのフープは、通常、仮想関数または関数テンプレートの特殊化への関数ポインターを使用して行われますvoid*

インダイレクションの唯一の性質は、オプティマイザがそれらを見通すことを非常に困難にします。std::function同じように、末尾再帰呼び出しがあるかどうかをコンパイラが見抜いて判断するのは非常に困難です。

別の問題は、単純な変数であるため、内部または同時にr変更される可能性がありr、突然再帰呼び出しがなくなることです! 関数識別子では、それは不可能です。途中で意味を変更することはできません。

C++11 がラムダの再帰的な末尾呼び出しを最適化するかどうかを尋ねるつもりだったことを明確にしたかっただけです。

C++11 標準は、コンパイラが最適化する方法ではなく、抽象マシン上で動作するプログラムがどのように動作するかを記述しています。実際、コンパイラーは、プログラムの観察可能な動作を変更しない場合にのみ最適化を許可されます (copy-elision/(N)RVO は例外です)。

于 2012-10-28T23:18:51.120 に答える