Comment convertir un lambda en std:: fonction en utilisant des modèles

fondamentalement, ce que je veux être en mesure de faire est de prendre un lambda avec un certain nombre de n'importe quel type de paramètres et de le convertir en une fonction std::. J'ai essayé la méthode suivante et aucune ne fonctionne.

std::function([](){});//Complains that std::function is missing template parameters
template <typename T> void foo(function<T> f){}
foo([](){});//Complains that it cannot find a matching candidate

le code suivant fonctionne cependant, mais ce n'est pas ce que je veux parce qu'il exige l'indication explicite des paramètres du modèle qui ne fonctionne pas pour le code générique.

std::function<void()>([](){});

j'ai fouillé partout avec des fonctions et modèles toute la soirée et je ne peux pas comprendre cela, donc toute aide serait très apprécié.

comme mentionné dans un commentaire, la raison pour laquelle j'essaie de le faire est que j'essaie d'implémenter currying en C++ en utilisant des modèles variadiques. Malheureusement, cela échoue horriblement en utilisant lambdas. Par exemple, je peux passer une fonction standard en utilisant un pointeur de fonction.

template <typename R, typename...A>
void foo(R (*f)(A...)) {}
void bar() {}
int main() {
    foo(bar);
}

cependant, je ne peux pas comprendre comment passer un lambda à une telle variété fonction. Pourquoi je suis intéressé à convertir un lambda générique en une fonction std:: parce que je peux faire ce qui suit, mais il finit par exiger que je déclare explicitement les paramètres du modèle à la fonction std:: ce qui est ce que j'essaie d'éviter.

template <typename R, typename...A>
void foo(std::function<R(A...)>) {}
int main() {
    foo(std::function<void()>([](){}));
}
53
demandé sur retep998 2012-11-13 13:59:29

6 réponses

vous ne pouvez pas passer un objet de fonction lambda comme argument de type std::function<T> sans spécifier explicitement l'argument de modèle T . Template type de déduction essaie de faire correspondre le type de votre fonction lambda au std::function<T> ce qu'elle ne peut pas faire dans ce cas - ci-ces types ne sont pas les mêmes. La déduction de type de modèle ne tient pas compte des conversions entre types.

Il est possible, si vous pouvez lui donner une autre façon de déduire le type. Vous pouvez le faire en enveloppant l'argument de fonction dans un type identity de sorte qu'il ne manque pas en essayant de faire correspondre la lambda à std::function (parce que les types dépendants sont juste ignorés par déduction de type) et en donnant quelques autres arguments.

template <typename T>
struct identity
{
  typedef T type;
};

template <typename... T>
void func(typename identity<std::function<void(T...)>>::type f, T... values) {
  f(values...);
}

int main() {
  func([](int x, int y, int z) { std::cout << (x*y*z) << std::endl; }, 3, 6, 8);
  return 0;
}

ce n'est évidemment pas utile dans votre situation cependant parce que vous ne voulez pas passer les valeurs jusqu'à plus tard.

puisque vous ne voulez pas spécifier les paramètres du modèle, ni passer d'autres arguments à partir de laquelle les paramètres du modèle peuvent être déduits, le compilateur ne pourra pas déduire le type de votre argument std::function .

41
répondu Joseph Mansfield 2015-07-15 20:18:19

vous pouvez utiliser un distribution dédiée/rétrospective . Une fois que vous avez un outil comme celui-ci

#include <functional>

using namespace std;

template<typename T>
struct memfun_type
{
    using type = void;
};

template<typename Ret, typename Class, typename... Args>
struct memfun_type<Ret(Class::*)(Args...) const>
{
    using type = std::function<Ret(Args...)>;
};

template<typename F>
typename memfun_type<decltype(&F::operator())>::type
FFL(F const &func)
{ // Function from lambda !
    return func;
}

vous pouvez dire FFL() à tous les types lambda à les faire convertir en ce qui serait la version correcte de std::function

template <typename... Args> void Callback(std::function<void(Args...)> f){
    // store f and call later
}

int main()
{
    Callback(FFL([](int a, float b){
        // do something
    }));

    return 0;
}

Affichage

16
répondu Nikos Athanasiou 2014-06-05 19:13:34

comme indiqué à en déduisant la signature d'appel d'une lambda ou appelable arbitraire pour "make_function " , vous pouvez inférer la signature d'appel d'une lambda (ou de tout autre foncteur avec une seule signature d'appel) de son (simple) operator() :

template<typename T> struct remove_class { };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...)> { using type = R(A...); };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...) const> { using type = R(A...); };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...) volatile> { using type = R(A...); };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...) const volatile> { using type = R(A...); };

template<typename T>
struct get_signature_impl { using type = typename remove_class<
    decltype(&std::remove_reference<T>::type::operator())>::type; };
template<typename R, typename... A>
struct get_signature_impl<R(A...)> { using type = R(A...); };
template<typename R, typename... A>
struct get_signature_impl<R(&)(A...)> { using type = R(A...); };
template<typename R, typename... A>
struct get_signature_impl<R(*)(A...)> { using type = R(A...); };
template<typename T> using get_signature = typename get_signature_impl<T>::type;

c'est une approche plutôt inflexible, cependant; comme le dit R. Martinho Fernandes, elle ne fonctionnera pas pour les foncteurs avec plusieurs operator() s, ni pour les foncteurs avec templated operator() ou de C++14) polymorphe lambdas. C'est pourquoi bind diffère l'inférence de son type de résultat jusqu'à la tentative d'appel éventuelle.

12
répondu ecatmur 2017-05-23 12:34:28

il est possible d'obtenir le std::TYPE DE fonction nécessaire pour lambda en utilisant dérivation, décltype, modèles variadiques et quelques traits de type:

namespace ambient {

    template <typename Function>
    struct function_traits : public function_traits<decltype(&Function::operator())> {};

    template <typename ClassType, typename ReturnType, typename... Args>
    struct function_traits<ReturnType(ClassType::*)(Args...) const> {
        typedef ReturnType (*pointer)(Args...);
        typedef const std::function<ReturnType(Args...)> function;
    };

    template <typename Function>
    typename function_traits<Function>::function to_function (Function& lambda) {
        return static_cast<typename function_traits<Function>::function>(lambda);
    }

    template <class L>
    struct overload_lambda : L {
        overload_lambda(L l) : L(l) {}
        template <typename... T>
        void operator()(T&& ... values){
            // here you can access the target std::function with
            to_function(*(L*)this)(std::forward<T>(values)...);
        }
    };

    template <class L>
    overload_lambda<L> lambda(L l){
        return overload_lambda<L>(l);
    }

}

je l'utilise dans mon code comme ceci:

ambient::lambda([&](const vector<int>& val){ // some code here // })(a);

PS: dans mon cas réel, je sauvegarde alors cet objet std:: function et ses arguments à l'intérieur d'un objet générique du noyau que je peux exécuter plus tard sur demande via des fonctions virtuelles.

     

7
répondu Alex Kosenkov 2016-10-10 12:49:08

n'est-il pas déjà déjà implémenté avec std::bind ?

auto sum = [](int a, int b){ return a+b; };
auto inc = std::bind( sum, _1, 1 );
assert( inc(1)==2 );
3
répondu xtofl 2012-11-13 10:28:07

cela pourrait être intéressant pour vous: https://gist.github.com/Manu343726/94769034179e2c846acc

C'est une expérience que j'ai écrite il y a un mois. Le but était de créer un modèle c++ fonctionnel qui émule les fermetures partielles D'appels de Haskell, c'est-à-dire la création automatique d'une fermeture de m-n argumments quand vous appelez avec n argumments une fonction avec m paramètres.

C'est un exemple de ce que cette expérience ne peut pas faire:

int f( int a, int b, int c, int d)
{
    return a+b+c+d;
}

int main()
{
    auto foo = haskell::make_function( f );

    auto a = foo , 1 , 2 , 3; //a is a closure function object with one parameter

    std::cout << a , 4 << std::endl; //Prints 10
}

haskell::make_function utilise certains traits de type pour prendre soin des différents types d'entités de fonction, lambdas inclus:

auto f = haskell::make_function( []( int x, int y , int z ){ return x*y*z; } );

auto a = f(1,2); //a is functor with one parameter (Using the alternative C++-like syntax)
auto b = a(3); // b is 6

comme vous pouvez le voir, j'utilise la syntaxe comma operator to mmimic Haskell, mais vous pouvez la changer en Call operator pour obtenir la syntaxe de votre but.

vous êtes complètement libre de faire tout ce que vous voulez avec le code (Vérifiez la licence).

3
répondu Manu343726 2014-06-05 19:42:23