Search code examples
c++functional-programminglambdac++11tr1

Converting a lambda to a std::tr1::function


Using visual studio 2008 with the tr1 service pack and Intel C++ Compiler 11.1.071 [IA-32], this is related to my other question

I'm attempting to write a functional map for c++ which would work somewhat like the ruby version

strings = [2,4].map { |e| e.to_s }

So i've defined the following function in the VlcFunctional namespace

template<typename Container, typename U>
vector<U> map(const Container& container, std::tr1::function<U(Container::value_type)> f)
{
    vector<U> transformedValues(container.size());
    int index = -1; 
    BOOST_FOREACH(const auto& element, container)
    {
        transformedValues.at(++index) = f(element);
    }
    return transformedValues; 
}

and you can call this like so (Note that the function template arguments are defined explicitly):

vector<int> test;
test.push_back(2); test.push_back(4); 
vector<string> mappedData2 = VlcFunctional::map<vector<int>,string>(test, [](int i) -> string
{
    return ToString(i);
});

Or like so (Note that the function template arguments aren't defined explicitly)

std::tr1::function f = [](int i) -> string { return ToString(i); };
vector<string> mappedData2 = VlcFunctional::map<vector<int>,string>(test, f);

But crucially, NOT LIKE THIS

vector<string> mappedData2 = VlcFunctional::map(test, [](int i) -> string { return ToString(i); });

Without the explicit definition of hte template arguments, it doesn't know which template to use and falls over with a compile error

 ..\tests\VlcFunctional_test.cpp(106): error: no instance of function template "VlcFunctional::map" matches the argument list, argument types are: (std::vector<int, std::allocator<int>>, __lambda3)

Having to define the template arguments makes it a much more bulky syntax and I'm aiming for minimal cruft at the call site - any ideas on why it doesn't know how do the conversion? Is this a compiler issue or does the language not allow for this type of template argument inference?


Solution

  • The problem is that a lambda is not a std::function even if it can be converted. When deducing type arguments, the compiler is not allowed to perform conversions on the actual provided arguments. I would look for a way to have the compiler detect the type U and let the second argument free for the compiler to deduce:

    template <typename Container, typename Functor>
    std::vector< XXX > VlcFunctional::map( Container &, Functor )...
    

    Now the issue is what to write in XXX. I don't have the same compiler that you do, and all C++0x features are still a little tricky. I would first try to use decltype:

    template <typename Container, typename Functor>
    auto VlcFunctional::map( Container & c, Functor f ) -> std::vector< decltype(f(*c.begin())) > ...
    

    Or maybe type traits if the compiler does not support decltype yet.

    Also note that the code you are writting is quite unidiomatic in C++. Usually when manipulating containers the functions are implemented in terms of iterators, and your whole map is basically the old std::transform:

    std::vector<int> v = { 1, 2, 3, 4, 5 };
    std::vector<std::string> s;
    std::transform( v.begin(), v.end(), std::back_inserter(s), [](int x) { return ToString(x); } );
    

    Where std::transform is the C++ version of your map function. While the syntax is more cumbersome, the advantage is that you can apply it to any container, and produce the output to any other container, so the transformed container is not fixed to std::vector.

    EDIT: A third approach, probably easier to implement with your current compiler support is manually providing just the return type of the lambda as template argument, and letting the compiler deduce the rest:

    template <typename LambdaReturn, typename Container, typename Functor>
    std::vector<LambdaReturn> map( Container const & c, Functor f )
    {
       std::vector<LambdaReturn> ret;
       std::transform( c.begin(), c.end(), std::back_inserter(ret), f );
       return ret;
    }
    int main() {
       std::vector<int> v{ 1, 2, 3, 4, 5 };
       auto strs = map<std::string>( v, [](int x) {return ToString(x); });
    }
    

    Even if you want to add syntactic sugar to your map function, there is no need to manually implement it when you can use existing functionality.