web-dev-qa-db-fra.com

Comment convertir un lambda en std :: function en utilisant des templates

Fondamentalement, ce que je veux pouvoir faire est de prendre un lambda avec un nombre quelconque de paramètres et de le convertir en std :: function . J'ai essayé ce qui suit et aucune méthode 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, car il faut explicitement indiquer les paramètres du modèle, ce qui ne fonctionne pas pour le code générique.

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

J'ai passé toute la soirée à bricoler avec des fonctions et des modèles et je ne peux tout simplement pas comprendre cela, alors toute aide serait très appréciée.

Comme mentionné dans un commentaire, j'essaie de le faire parce que j'essaie d'implémenter le currying en C++ à l'aide de modèles variadiques. Malheureusement, cela échoue terriblement lors de l'utilisation de lambdas. Par exemple, je peux transmettre une fonction standard à l'aide d'un pointeur de fonction.

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

Cependant, je ne vois pas comment passer d’un lambda à une fonction aussi variadique. Pourquoi m'intéresser de convertir un lambda générique en std :: function est-ce parce que je peux faire ce qui suit, mais cela finit par nécessiter que je déclare explicitement les paramètres du modèle à std :: function, ce que j'essaie d'éviter.

template <typename R, typename...A>
void foo(std::function<R(A...)>) {}
int main() {
    foo(std::function<void()>([](){}));
}
56
retep998

Vous ne pouvez pas passer un objet de fonction lambda en tant qu'argument de type std::function<T> sans spécifier explicitement l'argument de modèle T. La déduction de type de modèle tente de faire correspondre le type de votre fonction lambda au std::function<T>, ce qu’elle ne peut tout simplement pas faire dans ce cas - ces types ne sont pas les mêmes. La déduction de type de modèle ne prend pas en compte les conversions entre types.

C'est possible si vous pouvez lui donner un autre moyen de déduire le type. Vous pouvez le faire en encapsulant l’argument de la fonction dans un type identity afin qu’il ne manque pas d’essayer de faire correspondre le lambda à std::function (car les types dépendants sont simplement ignorés par déduction de type) et en donnant d’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;
}

Cela n’est évidemment pas utile dans votre situation, car vous ne voulez transmettre les valeurs que plus tard.

Comme vous ne souhaitez pas spécifier les paramètres de modèle ni transmettre d’autres arguments permettant de déduire les paramètres de modèle, le compilateur ne pourra pas en déduire le type de votre argument std::function.

42
Joseph Mansfield

Vous pouvez utiliser une 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 pour les convertir en une 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;
}

Afficher

20
Nikos Athanasiou

Comme indiqué à en déduisant la signature d'appel d'un lambda ou appelable arbitrairement pour "make_function" , vous pouvez déduire la signature d'un lambda (ou de tout autre foncteur avec une seule signature) de son (unique) 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, cela ne fonctionnera pas pour les foncteurs avec plusieurs operator()s, ni pour les foncteurs avec modèles operator() ou pour (C++ 14) lambdas polymorphes. C'est pourquoi bind diffère l'inférence de son type de résultat jusqu'à la tentative d'appel éventuelle.

13
ecatmur

Il est possible d’obtenir le type de std :: function nécessaire pour lambda à l’aide des méthodes de dérivation, decltype, variadic et de quelques caractères 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, j’enregistre ensuite cet objet std :: function et ses arguments à l’intérieur d’un noyau générique que je peux exécuter ultérieurement à la demande via des fonctions virtuelles. 

 

7
Alex Kosenkov

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. L’objectif était de créer un modèle C++ de type foncteur qui émule les fermetures d’appels partiels de Haskell, c’est-à-dire la création automatique d’une fermeture de m-n arguments lorsque vous appelez avec n arguments d’une fonction avec m

Voici un exemple de ce que cette expérience peut 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 caractères de type pour prendre en charge les différents types d'entités de fonction, dont lambdas:

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 constater, j’utilise opérateur virgule pour mmimic Haskell, mais vous pouvez le changer en opérateur d’appel pour obtenir la syntaxe de votre objectif.

Vous êtes totalement libre de faire ce que vous voulez avec le code (Vérifiez la licence).

3
Manu343726

Currying déjà n'est-il pas 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
xtofl

En C++ 17, il y a la déduction de type constructeur. Ainsi, vous pouvez enregistrer quelques données pour les arguments du modèle std :: function Ce n'est pas tout à fait rien, mais un peu moins.

template <typename R, typename...A>
void foo(std::function<R(A...)>) {}
int main() {
   foo(std::function([](){}));
}    
0
user2281723