web-dev-qa-db-fra.com

Fonctions lambda récursives en C++ 11

Je suis nouveau en C++ 11. J'écris la fonction lambda récursive suivante, mais elle ne compile pas.

sum.cpp

#include <iostream>
#include <functional>

auto term = [](int a)->int {
  return a*a;
};

auto next = [](int a)->int {
  return ++a;
};

auto sum = [term,next,&sum](int a, int b)mutable ->int {
  if(a>b)
    return 0;
  else
    return term(a) + sum(next(a),b);
};

int main(){
  std::cout<<sum(1,10)<<std::endl;
  return 0;
}

erreur de compilation:

vimal @ linux-718q: ~/Study/09C++/c ++ 0x/lambda> g ++ -std = c ++ 0x sum.cpp

sum.cpp: Dans la fonction lambda: sum.cpp: 18: 36: erreur: ‘((<lambda(int, int)>*)this)-><lambda(int, int)>::sum’ ne peut pas être utilisé comme fonction

version gcc

gcc version 4.5.0 20091231 (expérimental) (GCC)

Mais si je change la déclaration de sum() comme ci-dessous, cela fonctionne:

std::function<int(int,int)> sum = [term,next,&sum](int a, int b)->int {
   if(a>b)
     return 0;
   else
     return term(a) + sum(next(a),b);
};

Quelqu'un pourrait-il s'il vous plaît éclaircir la question?

106
weima

Réfléchissez à la différence entre la version auto et la version de type entièrement spécifiée. Le mot clé auto déduit son type de tout ce avec quoi il est initialisé, mais ce que vous initialisez doit savoir quel est son type (dans ce cas, la fermeture lambda doit connaître les types capturés). Quelque chose d'un problème de poulet et d'oeufs.

D'un autre côté, le type d'un objet fonction entièrement spécifié n'a pas besoin de "savoir" sur ce qui lui est assigné, de sorte que la fermeture du lambda peut également être entièrement informée sur les types qu'il capture.

Considérez cette légère modification de votre code et cela peut sembler plus logique:

std::function<int(int,int)> sum;
sum = [term,next,&sum](int a, int b)->int {
if(a>b)
    return 0;
else
    return term(a) + sum(next(a),b);
};

Évidemment, cela ne fonctionnerait pas avec auto . Les fonctions lambda récursives fonctionnent parfaitement (du moins en MSVC, où je les connais bien), c'est simplement qu'elles ne sont pas vraiment compatibles avec l'inférence de type.

137
I. M. McIntosh

L'astuce consiste à alimenter l'implémentation lambda en tant que paramètre, pas par capture.

const auto sum = [term,next](int a, int b) {
  auto sum_impl=[term,next](int a,int b,auto& sum_ref) mutable {
    if(a>b){
      return 0;
    }
    return term(a) + sum_ref(next(a),b,sum_ref);
  };
  return sum_impl(a,b,sum_impl);
};

_ {Tous les problèmes d’informatique peuvent être résolus par un autre niveau d’indirection}. J'ai d'abord trouvé cette astuce facile à http://pedromelendez.com/recursive-lambdas-in-c14/

Il a nécessite C++ 14 alors que la question est sur C++ 11, mais peut-être intéressante pour la plupart.

Passer par std::function est également possible, mais peut entraîne un code plus lent. Mais pas toujours. Consultez les réponses à std :: function vs template

38
Johan Lundberg

Avec C++ 14, il est maintenant très facile de créer un lambda récursif efficace sans avoir à supporter la surcharge supplémentaire de std::function, en seulement quelques lignes de code (avec une petite édition de l'original pour éviter que l'utilisateur ne prenne accidentellement copie):

template <class F>
struct y_combinator {
    F f; // the lambda will be stored here

    // a forwarding operator():
    template <class... Args>
    decltype(auto) operator()(Args&&... args) const {
        // we pass ourselves to f, then the arguments.
        // [edit: Barry] pass in std::ref(*this) instead of *this
        return f(std::ref(*this), std::forward<Args>(args)...);
    }
};

// helper function that deduces the type of the lambda:
template <class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f) {
    return {std::forward<F>(f)};
}

avec lequel votre tentative sum originale devient:

auto sum = make_y_combinator([term,next](auto sum, int a, int b) {
  if (a>b) {
    return 0;
  }
  else {
    return term(a) + sum(next(a),b);
  }
});
23
Barry

J'ai une autre solution, mais je ne travaille qu'avec des lambdas sans état:

void f()
{
    static int (*self)(int) = [](int i)->int { return i>0 ? self(i-1)*i : 1; };
    std::cout<<self(10);
}

Le truc ici est que lambdas peut accéder aux variables statiques et que vous pouvez convertir celles sans état en pointeur de fonction.

Vous pouvez l'utiliser avec des lambdas standard:

void g()
{
    int sum;
    auto rec = [&sum](int i) -> int
    {
        static int (*inner)(int&, int) = [](int& _sum, int i)->int 
        {
            _sum += i;
            return i>0 ? inner(_sum, i-1)*i : 1; 
        };
        return inner(sum, i);
    };
}

Son travail dans GCC 4.7

20
Yankes

Vous pouvez faire en sorte qu'une fonction lambda s'appelle de manière récursive. La seule chose à faire est de la référencer via un wrapper de fonction afin que le compilateur sache qu'il est renvoyé et son type d'argument (vous ne pouvez pas capturer une variable - la lambda elle-même - qui n'a pas encore été définie). .

  function<int (int)> f;

  f = [&f](int x) {
    if (x == 0) return 0;
    return x + f(x-1);
  };

  printf("%d\n", f(10));

Faites très attention à ne pas épuiser la portée de l'emballage. F.

10
Zuza

Pour rendre lambda récursif sans utiliser de classes et de fonctions externes (comme std::function ou un combinateur à virgule fixe), vous pouvez utiliser la construction suivante en C++ 14 ( exemple vivant ):

#include <utility>
#include <list>
#include <memory>
#include <iostream>

int main()
{
    struct tree
    {
        int payload;
        std::list< tree > children = {}; // std::list of incomplete type is allowed
    };
    std::size_t indent = 0;
    // indication of result type here is essential
    const auto print = [&] (const auto & self, const tree & node) -> void
    {
        std::cout << std::string(indent, ' ') << node.payload << '\n';
        ++indent;
        for (const tree & t : node.children) {
            self(self, t);
        }
        --indent;
    };
    print(print, {1, {{2, {{8}}}, {3, {{5, {{7}}}, {6}}}, {4}}});
}

impressions:

1
 2
  8
 3
  5
   7
  6
 4

Remarque, le type de résultat de lambda doit être spécifié explicitement.

8
Orient

J'ai exécuté un test comparant une fonction récursive à une fonction lambda récursive à l'aide de la méthode de capture std::function<>. Avec les optimisations complètes activées sur la version 4.1 de Clang, la version de Lambda a été considérablement ralentie.

#include <iostream>
#include <functional>
#include <chrono>

uint64_t sum1(int n) {
  return (n <= 1) ? 1 : n + sum1(n - 1);
}

std::function<uint64_t(int)> sum2 = [&] (int n) {
  return (n <= 1) ? 1 : n + sum2(n - 1);
};

auto const ITERATIONS = 10000;
auto const DEPTH = 100000;

template <class Func, class Input>
void benchmark(Func&& func, Input&& input) {
  auto t1 = std::chrono::high_resolution_clock::now();
  for (auto i = 0; i != ITERATIONS; ++i) {
    func(input);
  }
  auto t2 = std::chrono::high_resolution_clock::now();
  auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count();
  std::cout << "Duration: " << duration << std::endl;
}

int main() {
  benchmark(sum1, DEPTH);
  benchmark(sum2, DEPTH);
}

Produit des résultats:

Duration: 0 // regular function
Duration: 4027 // lambda function

(Remarque: j'ai également confirmé avec une version qui prenait les entrées de cin, afin d'éliminer l'évaluation du temps de compilation)

Clang produit également un avertissement pour le compilateur:

main.cc:10:29: warning: variable 'sum2' is uninitialized when used within its own initialization [-Wuninitialized]

Ce qui est prévu et sûr, mais devrait être noté.

C'est formidable d'avoir une solution dans nos ceintures à outils, mais je pense que le langage nécessitera un meilleur moyen de gérer ce cas si les performances doivent être comparables aux méthodes actuelles.

Remarque:

Comme l’a souligné un intervenant, il semble que la dernière version de VC++ ait trouvé le moyen de l’optimiser jusqu’à égalité de performances. Peut-être n’avons-nous pas besoin d’un meilleur moyen de gérer cela, après tout (sauf pour le sucre syntaxique).

De plus, comme d'autres SO articles l'ont souligné ces dernières semaines, les performances de std::function<> peuvent être à l'origine du ralentissement par rapport à l'appel direct, du moins lorsque la capture lambda est trop volumineuse pour tenir dans un espace optimisé pour la bibliothèque. std::function utilise pour les petits foncteurs (j'imagine un peu les différentes optimisations de chaînes courtes?).

6
mmocny

Il s’agit d’une implémentation un peu plus simple de l’opérateur fixpoint qui rend un peu plus évident ce qui se passe exactement.

#include <iostream>
#include <functional>

using namespace std;

template<typename T, typename... Args>
struct fixpoint
{
    typedef function<T(Args...)> effective_type;
    typedef function<T(const effective_type&, Args...)> function_type;

    function_type f_nonr;

    T operator()(Args... args) const
    {
        return f_nonr(*this, args...);
    }

    fixpoint(const function_type& p_f)
        : f_nonr(p_f)
    {
    }
};


int main()
{
    auto fib_nonr = [](const function<int(int)>& f, int n) -> int
    {
        return n < 2 ? n : f(n-1) + f(n-2);
    };

    auto fib = fixpoint<int,int>(fib_nonr);

    for (int i = 0; i < 6; ++i)
    {
        cout << fib(i) << '\n';
    }
}
1
Pseudonym

C++ 14: Voici un ensemble générique lambdas Anonyme sans état/sans capture récursif, qui renvoie tous les nombres de 1, 20

([](auto f, auto n, auto m) {
    f(f, n, m);
})(
    [](auto f, auto n, auto m) -> void
{
    cout << typeid(n).name() << el;
    cout << n << el;
    if (n<m)
        f(f, ++n, m);
},
    1, 20);

Si je comprends bien, cela utilise la solution du combinateur en Y

Et voici la version sum (n, m)

auto sum = [](auto n, auto m) {
    return ([](auto f, auto n, auto m) {
        int res = f(f, n, m);
        return res;
    })(
        [](auto f, auto n, auto m) -> int
        {
            if (n > m)
                return 0;
            else {
                int sum = n + f(f, n + 1, m);
                return sum;
            }
        },
        n, m); };

auto result = sum(1, 10); //result == 55
0
Jonas Brandel