Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to allow one std::function type accept lambdas with different signatures

I have a higher order function map which is similar to STL for_each, and maps a std::function object over a vector of things.

template<class T, class U>
vector<U> map(function<U (T)> f, vector<T> xs) {
  vector<U> ret;
  for (auto &x: xs)
    ret.push_back(f(x));
  return ret;
}

Now, I want to have this higher order function take both objects of types function<int (const vector<T>&)> and function<int (vector<T>)>, as shown in the attached minimal example.

The problem is that function<int (const vector<T>&)> and function<int (vector<T>)> seem to be convertible to each other (see head and head2), but map won't take the const references version function<int (const vector<int>&)> (see Q1).

It is possible to tell map to accept the const reference version with explicit conversion (Q2), but this is rather cumbersome.

I was wondering if, in general, it is possible to write a function deref that removes the const reference from function<int (const vector<T>&)> and returns a function<int (vector<T>)>?

(If above is possible, then I won't have to write two identical overloads/implementations of map for const refs).

Thanks.

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

template<class T, class U>
vector<U> map(function<U (T)> f, vector<T> xs) {
  vector<U> ret;
  for (auto &x: xs)
    ret.push_back(f(x));
  return ret;
}

int main() {
  vector<vector<int>> m;
  function<int (const vector<int>&)> head  =  [](const vector<int>& a) {return a[0];};
  function<int (const vector<int>&)> head1 =  [](vector<int> a) {return a[0];}; //conversion OK
  function<int (vector<int>)> head2 =  [](const vector<int>& a) {return a[0];}; //conversion OK
  map(head2,m); //OK

  map(head,m); //Q1: problem line, implicit conversion NOT OK
  map(function<int (vector<int>)>(head),m); //Q2: explicit conversion OK
  map(deref(head),m); //Q3: ??How-to, deref takes a std::function f and returns a function with const ref removed from its signature

  return 0;
}

--- EDIT ---

I am particularly interested in a deref like function or a meta-function that can remove the const ref from the type signature of a std::function object, so that I can at least do Q2 automatically.

I know that, as @Brian and @Manu correctly pointed out, the use of std::function to specify types is not conventional, but I wonder what I asked above is even feasible. Personally, I think code with std::function has greater clarity, considering how generic function types Func<T1, T2, T3, ...,Tn, Tresult> are used in C#. This is if the cost of type erasure is tolerable.

I fully agree that c++ can infer return types and give an error message when type is wrong. Maybe it's just a matter of taste and I would prefer to spell it out when writing function signatures.

like image 374
thor Avatar asked Jul 03 '14 19:07

thor


People also ask

Can a Lambda be assigned to a function?

It is true that a lambda can be assigned to a std::function, but that is not its native type. We’ll talk about what that means soon. As a matter of fact, there is no standard type for lambdas. A lambda’s type is implementation defined, and the only way to capture a lambda with no conversion is by using auto:

What is the difference between a global function and a Lambda?

This means that calling a lambda many times (such as with std::sort or std::copy_if) is much better than using a global function. This is one example of where C++ is actually faster than C. std::function is a templated object that is used to store and call any callable type, such as functions, objects, lambdas and the result of std::bind.

Why are lambda functions faster in C++ than C?

Because they are objects rather than pointers they can be inlined very easily by the compiler, much like functors. This means that calling a lambda many times (such as with std::sort or std::copy_if) is much better than using a global function. This is one example of where C++ is actually faster than C.

What is an empty Lambda in C++?

Lambdas are a fancy name for anonymous functions. Essentially they are an easy way to write functions (such as callbacks) in the logical place they should be in the code. My favorite expression in C++ is [] () {} ();, which declares an empty lambda and immediately executes it. It is of course completely useless. Better examples are with STL, like:


1 Answers

I understand why you are using std::function: You have to know the return type of the transformation to create the vector, right?

But consider a completely different approach. Given the metafunction std::result_of you could compute the result type of a function call, so just write:

template<typename F , typename CONTAINER , typename T = typename std::result_of<F(typename CONTAINER::value_type)>::type>
std::vector<T> map( F f , CONTAINER&& container )
{
    std::vector<T> result;

    for( auto& e : container )
        result.emplace_back( f( e ) );

    return result;
}

Advantages:

  • No abuse of std::function: Always think what std::function does (i.e. type erasure), don't use it as an universal function type.

  • Rely on duck typing instead of coupling on the types: Don't worry, if something was wrong it wouldn't compile neither.

  • Works for any Standard Library Container since we extracted the element type with the value_type trait, instead of using std::vector directly.

  • The code is much more clear and efficient, both because the reduction of std::function usage.

Regarding the question "Its possible to write a function that accepts lambdas of multiple signatures?"

Using std::function you could write something similar to Boost.OverloadedFunction in a couple of lines:

template<typename F , typename... Fs>
struct overloaded_function : public std_function<F> , public std_function<Fs>...
{
    overloaded_function( F&& f , Fs&&... fs ) :
        std_function<F>{ f },
        std_function<Fs>{ fs }...
    {}
};

Where std_function is a metafunction which given a function type F returns the std::function instance with the signature of F. I leave it as a game/challenge for the reader.

Thats all. Improve it with a make-like function:

template<typename F , typename... Fs>
overloaded_function<F,Fs...> make_overloaded_function( F&& f , Fs&&... fs )
{
    return { std::forward<F>( f ) , std::forward<Fs>( fs )... };
}

And you are ready to go:

auto f = make_overloaded_function( [](){ return 1; } ,
                                   [](int,int){ return 2; } ,
                                   [](const char*){ return 3; } );

f();        //Returns 1
f(1,2);     //Returns 2
f("hello"); //Returns 3

EDIT: "Thanks. But, what I am really looking for, is a meta-function that takes the signature of a callable, and removes the const refs from the signature."

Ok, let me try: The std::decay metafunction applies the decaying done when passing argumments by value to a given type. This includes removing cv qualifiers, removing references, etc. So a metafunction like yours could be something that takes a function signature type and applies decaying to all its argumments:

template<typename F>
struct function_decay;

template<typename R typename... ARGS>
struct function_decay<R(ARGS...)>
{
    using type = R(typename std::decay<ARGS>::type...);
};

That should do the work.

I have written this because you explicitly asked for it in the comment, but I strongly encourage you to use the alternative I showed you initially, because it has many advantages compared to your way.
That said, I hope this answer helped to solve your problem.

like image 186
Manu343726 Avatar answered Sep 24 '22 16:09

Manu343726