Как преобразовать лямбда в std:: функция с помощью шаблонов


в принципе, то, что я хочу сделать, это взять лямбду с любым количеством параметров любого типа и преобразовать ее в функцию std::. Я пробовал следующее, И ни один из методов не работает.

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

однако следующий код работает, но это не то, что я хочу, потому что он требует явного указания параметров шаблона, которые не работают для общего кода.

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

Я весь вечер возился с функциями и шаблонами, и я просто не могу понять этого, поэтому любая помощь будет очень признательна.

как уже упоминалось в комментарии, причина, по которой я пытаюсь это сделать, заключается в том, что я пытаюсь реализовать карринг в C++ с использованием вариативных шаблонов. К сожалению, это ужасно не удается при использовании лямбд. Например, я могу передать стандартную функцию с помощью указателя функции.

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

однако, я не могу понять, как передать лямбду в такую вариативную функцию. Почему меня интересует преобразование общей лямбды в std:: function это потому, что я могу сделать следующее, но в конечном итоге требуется, чтобы я явно указывал параметры шаблона в std::function, чего я пытаюсь избежать.

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

6 ответов:

вы не можете передать объект лямбда-функции в качестве аргумента типа std::function<T> без явного указания аргумента шаблона T. Тип шаблона вычитание пытается соответствовать типу вашей лямбда-функции к std::function<T> что это просто не может сделать в этом случае - эти типы не совпадают. Вычет типа шаблона не учитывает преобразования между типами.

это возможно, если вы можете дать ему какой-то другой способ определить тип. Вы можете сделать это, обернув функцию аргумент в identity введите так, чтобы он не потерпел неудачу при попытке сопоставить лямбду с std::function (потому что зависимые типы просто игнорируются вычитанием типа) и дают некоторые другие аргументы.

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;
}

это явно не полезно в вашей ситуации, хотя, потому что вы не хотите передать значения позже.

поскольку вы не хотите указывать параметры шаблона и не хотите передавать другие аргументы, из которых могут быть выведены параметры шаблона, то компилятор не сможет вывести тип

можно использовать посвященный / ретроспективный бросок. Как только у вас есть такой инструмент

#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;
}

можно сказать FFL() для всех типов лямбда пусть они преобразуются в то, что было бы правильной версией 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;
}

дисплей

как показано на вывод сигнатуры вызова лямбды или произвольного вызываемого объекта для "make_function", вы можете вывести вызывающую подпись лямбды (или любого другого функтора с одной вызывающей подписью) из его (single)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;

это довольно негибкий подход, хотя; как говорит Р. Мартиньо Фернандес, он не будет работать для функторов с несколькими operator()s, ни для функторов с шаблонаoperator() или для (C++14) полиморфного лямбды. Вот почему bind откладывает вывод своего типа результата до конечной попытки вызова.

можно получить необходимый тип std:: function для lambda с помощью derivation, decltype, variadic templates и нескольких признаков типа:

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);
    }

}

Я использую его в мой код вроде этого:

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

PS: В моем реальном случае я затем сохраняю этот объект std:: function и его аргументы внутри общих объектов ядра, которые я могу выполнить позже по требованию с помощью виртуальных функций.

не карринг уже реализовано с помощью std::bind?

auto sum = [](int a, int b){ return a+b; };
auto inc = std::bind( sum, _1, 1 );
assert( inc(1)==2 );

это может быть интересно для вас: https://gist.github.com/Manu343726/94769034179e2c846acc

это эксперимент, который я написал месяц назад. Цель состояла в том, чтобы создать функтор-подобный шаблон C++, который эмулирует частичные замыкания вызовов Haskell, т. е. автоматическое создание замыкания m-n споры, когда вы звоните с n аргументирует функцию с m параметры.

это один из примеров того, что этот эксперимент cappable делать:

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 использует некоторые черты типа, чтобы заботиться о различных типах сущностей функции, лямбды входят:

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

как вы можете видеть, я использую оператор запятой для синтаксиса mmimic Haskell, но вы можете изменить его на оператор вызова для достижения синтаксиса цели.

вы совершенно свободны делать все, что вы хотите с кодом (Проверьте лицензию).