How to make C++11 functions taking function<> p

2020-02-06 02:21发布

C++11 has both lambda's and std::function<>, but unfortunately, they have different types. One consequence is that one cannot directly use lambda's in higher order functions such as map in lisp. For example, in the following code

 #include <vector>
 #include <functional>
 using namespace std;

 template <typename A,typename B> 
 vector<B> map(std::function<B (A)> f, vector<A> arr) {
       vector<B> res;
       for (int i=0;i<arr.size();i++) res.push_back(f(arr[i]));
       return res;
}

int main () {
    vector<int> a = {1,2,3};
    map([](int x) -> int { return x;},a); //not OK

    auto id_l = [](int x) -> int { return x;};
    map(id_l,a); //not OK;

    function<int (int)> id_f = id_l;
    map(id_f,a); //OK
return 0;
}

, directly using lambda as in line 2 of main() won't work. g++ -std=c++11 testfunc.cpp returns `... testfunc.cpp:14:37: note: 'main()::__lambda0' is not derived from 'std::function'.

C++11 type inferencing fails as well, as you can see if one stores the lambda to an auto variable and then use it, the type information is still lost, probably due to type erasure and reasons of small performance penalty (as I was told: why do lambda functions in c++11 not have function<> types?).

What does work is to store the lambda in a std:function<> typed variable and use that variable. This is rather inconvenient and kind of defeats the purpose of using lambda's in functional programming in C++11. For example, one cannot manipulate lambda's in place with stuff like bind or flip, and instead has to store the lambda to a variable first.

My question is, is it possible (and how) to overcome this issue and make line#2 of main() legal, e.g. by overwriting some typecast operators? (Of course, this means I don't care about the small performance penalty involved with using/not using type erasure.)

thanks in advance.

--- EDIT ---

To clarify, the reason I use std::function rather than a generic type parameter for the functional parameter is that std::function has exact type information, while a generic type parameter as in template <typename F> map(F f, ...) contains no type information. Also, as I finally figured out, each lambda is its own type. So type erasure wasn't even a issue in the incompatibility between a lambda and its matching std::function object.

---Update---

There are already two answers about how to make the map function above work or how to improve them. Just to clarify. My question isn't about how to make map work. There are plenty of other use cases involving using the std::function<> typed parameters, which I think can at least make the code more readable and make type inferencing easy. The answers so far are about how not to use std::function<> as parameters. My question was about how to make such a function (with std::function<> typed parameters) accept lambda's automatically.

-- Update 2 ---

In response to comments, here is a example of practical case where the type information in std::function<> COULD be useful. Suppose we want to implement a C++ equivalent of fold_right : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b in OCaml (http://caml.inria.fr/pub/docs/manual-ocaml/libref/List.html).

With std::function<>, one can do

 //approach#1
 template <typename A,typename B> 
 B fold_right(std::function<B (A, B)> f, vector<A> arr, B b) {
     ...
 }

It is clear from above what f is, and what it can or cannot take. Maybe, one can also use

 //approach#2
 template <typename A,typename B, typename F> 
 auto fold_right2(F f, vector<A> arr, B b) -> decltype(f(???)) {
      ...
 }

But, this is becoming kind of ugly as you try to figure out what to put in the decltype. Also, what exactly does f take, and what's the correct way to use f? From the point view of readability, I guess the reader of the code can only figure out what is f (a function or scalar) and the signature of f by INTERPRETING the implementation in the function body.

That is what I don't like and that's where my question comes from. How to make approach#1 work conveniently. For example, if f represents addition of two numbers, approach#1 works if you create a function object first:

std::function<int (int, int)> add = [](int x, int y) -> int { return x + y; }
fold_right(add,{1,2,3},0);

Efficiency issues aside, the above code is inconvenient BECAUSE std::function cannot accept lambda's. So,

fold_right([](int x, int y) -> int { return x + y; },{1,2,3},0);

will not work currently in C++11. My question is specifically about if it is possible to make functions like fold_right defined above accept lambda's directly. Maybe it's too much to hope for. I hope this clarifies the question.

标签: c++ c++11 lambda
5条回答
We Are One
2楼-- · 2020-02-06 02:39

Why would you want to create a dynamic indirection via std::function<...> in the first place? Just templatize on the function object and you are sorted:

template <typename A, typename F> 
auto map(F f, std::vector<A> arr) -> std::vector<decltype(f(arr[0]))> {
    std::vector<decltype(f(arr[0]))> res;
    for (int i=0; i<arr.size(); ++i)
        res.push_back(f(arr[i]));
    return res;
}

In fact, there isn't really any need for nailing the container type either and you probably want to pass it by [const] reference as well:

template <typename C, typename F> 
auto map(F f, C const& c) -> std::vector<decltype(f(*c.begin()))> {
    std::vector<decltype(f(*c.begin()))> res;
    for (auto const& value: c)
        res.push_back(f(value));
    return res;
}

Finally, please note that the standard C++ library already as a "map" function. It just happens to be spelled std::transform() and has an interface which fits the generic approach in C++ better:

std::vector<int> result;
std::transform(a.begin(), a.end(), std::back_inserter(result),
               [](int x){ return x;});
查看更多
▲ chillily
3楼-- · 2020-02-06 02:46

Your map function is broken. Do not use std::function unless you cannot use a template; and in this instance, you most assuredly can. You don't need B as a template parameter because decltype can give it to you, and you don't need the argument type to actually be a std::function at all.

template <typename A, typename F> auto map(F f, vector<A> arr) -> std::vector<decltype(f(arr.front())> {
    std::vector<decltype(f(arr.front())> res;
    for (int i=0;i<arr.size();i++) res.push_back(f(arr[i]));
    return res;
}

For the record, this is ignoring everything else wrong with your map function.

查看更多
我命由我不由天
4楼-- · 2020-02-06 02:47

Actually you could do it and even better (faster, cheaper) than with std::function. It has a heap alloc and a virtual function call. Its needed only for type erasure (to accept ANY CALLABLE with same signature). But for lambdas you don't need this (costly) flexibility. Just use lambda wrapper class

#include <iostream>
#include <functional>
#include <vector>

template <typename T, typename ... Args>
struct lambda_wrapper : public lambda_wrapper<decltype(&T::operator())(Args...)> {
    using RetType = decltype(&T::operator())(Args...);
};

template <typename L>
struct lambda_wrapper<L> {
private:
    L lambda;

public:
    lambda_wrapper(const L & obj) : lambda(obj) {}

    template<typename... Args>
    typename std::result_of<L(Args...)>::type operator()(Args... a) {
        return this->lambda.operator()(std::forward<Args>(a)...);
    }

    template<typename... Args> typename
    std::result_of<const L(Args...)>::type operator()(Args... a) const {
        return this->lambda.operator()(std::forward<Args>(a)...);
    }
};
template <typename T>
auto make_lambda_wrapper(T&&t) {
    return lambda_wrapper<T>(std::forward<T>(t));
}

template <typename A, typename F>
auto map(F f, std::vector<A> arr)
{
    std::vector < decltype(f(arr.front())) > res;
    for (int i=0;i<arr.size();i++) res.push_back(f(arr[i]));
    return res;
}

int main(int argc, char ** argv) {

    std::vector<int> a = {1,2,3};

    map(make_lambda_wrapper([](int a) -> int { return a*2;} ),a);

}
查看更多
Rolldiameter
5楼-- · 2020-02-06 02:58

Finally figured out a generic wrapper function make_function (in current c++11) for converting any lambda to its corresponding std::function object with type deduction. Now instead of using ctor:

map(function<int (int)>( [](int x) -> int { return x;} ), {1,2,3});

which requires giving the same type information twice, the following succinct form works

map(make_function([](int x) -> int { return x;}),a); //now OK

Code is below:

 #include <vector>
 #include <functional>
 using namespace std;

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

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

 template <typename L> 
 typename function_traits<L>::f_type make_function(L l){
   return (typename function_traits<L>::f_type)(l);
 }

 template <typename A,typename B> 
 vector<B> map(std::function<B (A)> f, vector<A> arr) {
       vector<B> res;
       for (int i=0;i<arr.size();i++) res.push_back(f(arr[i]));
       return res;
}

int main () {
    vector<int> a = {1,2,3};
    map(make_function([](int x) -> int { return x;}),a); //now OK
    return 0;
}

--original answer--

To answer my own question after a couple of weeks' search (and getting chastised for using std::function<> as parameters), probably the best way I can find to have function<>-typed parameters accept lambda's (in c++11) is simply via explicit cast:

map((function<int (int)>) ([](int x) -> int { return x;} ), {1,2,3});

Or using ctor:

map(function<int (int)>( [](int x) -> int { return x;} ), {1,2,3});

For comparison, if you have a function taking std::string (e.g. void ff(string s) {...}), it can take const char* automatically. (ff("Hi") would work). The automatic conversion from lambda to std::function<> does not similarly work in c++11 (, which is unfortunate, IMO).

Hopefully, things will improve in c++14/1y when lambdas can be properly typed or better type-deduced.

查看更多
爷的心禁止访问
6楼-- · 2020-02-06 02:58

My question was about how to make such a function (with std::function<> typed parameters) accept lambda's automatically.

You can't. Why do you suppose this is possible? std::function is part of the standard library, and it has no capabilities beyond what is possible with other class types.

Moreover, by artificially restricting the solution space to function calls with a lambda as the argument and a std::function<T> as the parameter with deduced T, there is nothing to possibly change. The argument will not match the parameter, and you've arbitrarily decided to forbid changing either.

Given a function dynamic_function_from_lambda to encapsulate any lambda in a std::function, you could perform the conversion explicitly either in the function call or the body of a function accepting lambda objects by deduction.

Alternative A:

map( dynamic_function_from_lambda( []( int a ){ return a + 1 } ), v );

Alternative B:

template< typename F, typename V >
std::vector< typename std::result_of< F( typename V::value_type ) >::type >
map( F f, V v )
    { return map( dynamic_function_from_lambda( f ), std::move( v ) ); }

The whole point of std::function is runtime polymorphism though, so if you're not using that, it is just wastefully inefficient.

查看更多
登录 后发表回答