web-dev-qa-db-fra.com

Passer l'expression lambda à l'argument lambda c ++ 11

J'aimerais faire quelque chose comme ça:

int main()
{
    auto f = [/*some variables*/](/*take lambda function*/)
    {/*something with lambda function*/};

    f([/*other variables*/](/*variables to be decided by f()*/)
    {/*something with variables*/});
}

Je sais qu'il est possible de passer un lambda à une fonction, ainsi qu'à un lambda . Les travaux suivants:

int main()
{
    int x=0;
    int y=0;
    auto f = [x,y](double (func)(int)) -> double
    {func(0); return 0.0;};

    f([](int i) -> double
    {return 0.0;});
}

Mais ce qui suit ne fonctionne pas (dès que je change les variables de la portée pour ajouter [x])

int main()
{
    int x=0;
    int y=0;
    auto f = [x,y](double (func)(int)) -> double
    {func(0); return 0.0;}

    f([x](int i) -> double    //[x] does not work
    {return 0.0;});
}

ce qui donne l'erreur:

error: function "lambda [](double (*)(int))->double::operator()" cannot be called with the given argument list
        argument types are: (lambda [](int)->double)
        object type is: lambda [](double (*)(int))->double

quelqu'un aurait-il une idée sur la façon de résoudre ce problème ou sur un moyen de le contourner? J'utilise le compilateur intel icpc (ICC) 13.1.2 avec std = c ++ 11

Merci

19
Couchy311

Il y a plusieurs choses à clarifier concernant votre question. Le premier est qu'est-ce qu'un lambda?

Une expression lambda est une expression simple à partir de laquelle le compilateur générera un type unique qui ne peut pas être nommé et qui, en même temps, générera une instance du type. Lorsque vous écrivez: [](int i) { std::cout << i; }, le compilateur générera pour vous un type qui correspond à peu près à:

struct __lambda_unique_name {
   void operator()(int i) const { std::cout << i; }
};

Comme vous pouvez le constater, il s’agit de non une fonction, mais d’un type qui implémente operator() en tant que fonction membre const. Si le lambda effectuait une capture, le compilateur générerait du code pour capturer la valeur/les références.

En tant que coin, pour les lambdas comme ci-dessus, dans lesquels aucun état n'est capturé, le langage permet une conversion du type lambda en un pointeur pour fonctionner avec la signature de la fonction operator() (moins la partie this), le lambda ci-dessus peut être implicitement converti en un pointeur vers une fonction prenant int et ne renvoyant rien:

void (*f)(int) = [](int i) { std::cout << i; }

Maintenant que les bases ont été énoncées, votre code contient le lambda:

auto f = [x,y](double (func)(int)) -> double {func(0); return 0.0;};

Les règles relatives aux paramètres de fonctions (qui s’appliquent également à lambdas) déterminent qu’un argument ne peut pas être de type function, de sorte que l’argument de lambda se décompose en un pointeur vers fonction (de la même manière qu’un argument de type array se désintègre en un type de pointeur):

auto f = [x,y](double (*func)(int)) -> double {func(0); return 0.0;};

Plus tard, vous essayez de passer un lambda dont la capture est un argument. Comme il y a une capture, la règle spéciale ne s'applique pas et le lambda n'est pas convertible en un pointeur sur une fonction générant l'erreur de compilateur que vous voyez.

Dans la norme actuelle, vous pouvez choisir l'une des deux méthodes. Vous pouvez utiliser type-effacement pour supprimer le type exact de l'entité appelable de la signature:

auto f = [x,y](std::function<double(int)> func) -> double {func(0); return 0.0;};

Puisqu'un std::function<double(int)> peut être initialisé avec n'importe quelle entité callable avec la signature appropriée, cela acceptera les lambdas dans le code ci-dessous, au prix de l'effacement de type qui implique généralement une allocation et une répartition dynamiques.

Alternativement, vous pouvez supprimer le sucre syntaxique et lancer le premier équivalent lambda manuellement, mais le rendre générique. Dans ce cas, où le lambda est simple, cela pourrait être une option valide:

struct mylambda {
   template <typename F>
   double operator()(F fn) const {
      fn(0); return 0.0;
   }
} f;
// then use the non-lambda as you tried:
f([x](int i) -> double {return 0.0;});

Enfin, si vous êtes assez patient, vous pouvez attendre C++ 14, où (le plus probablement, il n'a pas encore été ratifié), il y aura un support pour polymorphic lambdas, ce qui simplifie la création de la classe ci-dessus:

auto f = [](auto fn) { fn(0.0); return 0.0; } // unrolls to 'mylambda' above

Essayez d'utiliser std :: function:

#include <functional>
int main()
{
    int x=0;
    int y=0;
    auto f = [x,y](std::function<double(int)> func) -> double
             {func(0); return 0.0;};

    f([x](int i) -> double {return 0.0;});
}
5
DanielKO

Vous devrez peut-être simplement mordre la balle et mettre en place vos propres foncteurs, comme nous l’avions fait à l’époque sombre:

struct F {
    int x;
    int y;

    F(int x_, int y_) : x(x_), y(y_) {}

    template <typename G>
    double operator() (G&& g) const {
        g(0);
        return 0.0;
    }
};

#include <iostream>

int main()
{
    int x = 0;
    int y = 0;
    auto f = F(x, y);

    f([x](int i){return 0.0;});
    f([](int i){std::cout << i << std::endl;});
}

Cela devrait vous garder jusqu’à ce que votre compilateur prenne en charge les lambdas génériques C++ 14.

3
Casey

Vous pouvez essayer quelque chose comme ce qui suit si vous connaissez le type de lambda à l'avance, par exemple:

int main()
{
    int x = 0, y = 0;

    auto f = [x]( int i )->double {
        return (double)x;
    };

    auto f2 = [x,y]( decltype(f) func )->double {
        return func( 0 );
    };

    f2( f );

    return 0;
}

Vous pouvez également utiliser la bibliothèque <functional> pour une solution plus générique, par exemple:

auto f = [x,y]( std::function<double(int)> func ) { /* Do stuff */ };
1
Thomas Russell

Vous pouvez définir un lambda de capture, mais cette solution a ses limites:

#include <new>

#include <utility>

namespace
{

template <typename F, int I, typename L, typename R, typename ...A>
inline F cify(L&& l, R (*)(A...) noexcept(noexcept(
  std::declval<F>()(std::declval<A>()...))))
{
  static L l_(std::forward<L>(l));
  static bool full;

  if (full)
  {
    l_.~L();

    new (static_cast<void*>(&l_)) L(std::forward<L>(l));
  }
  else
  {
    full = true;
  }

  return [](A... args) noexcept(noexcept(
      std::declval<F>()(std::forward<A>(args)...))) -> R
    {
      return l_(std::forward<A>(args)...);
    };
}

}

template <typename F, int I = 0, typename L>
inline F cify(L&& l)
{
  return cify<F, I>(std::forward<L>(l), F());
}


int main()
{
    int x=0;
    int y=0;
    auto f = [x,y](double (func)(int)) -> double
    {func(0); return 0.0;};

    f(cify<double(*)(int i)>([x](int i) -> double    //works now
    {return 0.0;}));
}

Cliquez sur pour un exemple de travail.

0
user1095108