Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Map function with c++11 constructs

Both to teach myself about implementing more advanced template constructions than simply basic ones, and becouse they are useful in many circumstances, I'm trying to implement map, filter and similar functions common in functional programming using c++11 constructs like decltype.

I'm having trouble creating a function prototype that the compiler I use can handle, so I have to ask you how you would create something like this:

//
// Takes an iterable, applies a function to every element, and returns a vector of the results
//
template <typename T, typename Func>
auto map(const T& iterable, Func func) -> std::vector< decltype(  func( *iterable.cbegin() ) ) >
{
    // body snipped
}

That is, this function should take any iterable and a function that takes the iterables value type as an argument and returns some sort of value. The result of the function call will be a vector, regardless of the type of iterable passed in, of the type that the passed function returns.

The map function should accept any function with a valid prototype as argument, whether it is a functionpointer, a functor or lambda expression.

Using the function above with this test code:

std::vector<int> intVector;
intVector.push_back(1);
intVector.push_back(2);

map(intVector, [](int& value) { return value + 1; });

makes visual studio spit out a C2893 ("Failed to specialize function template") error and I'm not sure what's wrong.

Update: Applied changes suggested in comments and answers so far to question, new prototype tested but the same error remains.

like image 802
OnePie Avatar asked Feb 18 '13 21:02

OnePie


1 Answers

This might do what you want. It uses std::transform internally, which basically does the whole work. The function I wrote is nothing more than a simple wrapper for containers (not working with C-style arrays, that would require some additional type traits):

#include <vector>
#include <algorithm>
#include <type_traits>

//
// Takes an iterable, applies a function to every element, 
// and returns a vector of the results
//
template <typename T, typename Func>
auto map_container(const T& iterable, Func&& func) ->
    std::vector<decltype(func(std::declval<typename T::value_type>()))>
{
    // Some convenience type definitions
    typedef decltype(func(std::declval<typename T::value_type>())) value_type;
    typedef std::vector<value_type> result_type;

    // Prepares an output vector of the appropriate size
    result_type res(iterable.size());

    // Let std::transform apply `func` to all elements
    // (use perfect forwarding for the function object)
    std::transform(
        begin(iterable), end(iterable), res.begin(),
        std::forward<Func>(func)
        );

    return res;
}

However, notice that your lambda should take a reference to const, or better should take its argument by value in the case of int.

Also, I renamed the function from map into map_container: it is a bad programming practice to reuse names of standard containers of the C++ Standard Library for functions, variables, or anything else in your program.

For me, this gives the desired output:

#include <iostream>

int main()
{
    std::vector<int> intVector;

    intVector.push_back(1);
    intVector.push_back(2);

    auto v = map_container(intVector, [] (int value) { return value + 1; });

    for (int i : v) { std::cout << i << " "; }
}
like image 109
Andy Prowl Avatar answered Oct 05 '22 07:10

Andy Prowl