views:

88

answers:

1

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?

+2  A: 

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.

David Rodríguez - dribeas
+1 for the `transform` idiom.
Matthieu M.
David, thanks for the reply. I'm aware of the transform function and it's the cumbersome calling that i'm trying to avoid - while at the same time improving my understanding of C++.
Jamie Cook
I'm not really sure of the syntax you propose with the decltype approach... I'm using the intel 11.1 c++ compiler which is supposed to support decltype but I get a lot of compile errors using your syntax - where could I find more info on this approach?
Jamie Cook
@Jamie Cook: I guess I should have said "support for trailing return type"... The simple to read reference would be the [wikipedia](http://en.wikipedia.org/wiki/C%2B%2B0x#Alternative_function_syntax), in the standard you will have to go to section 8.5[dcl.decl]/4 for the syntax, /5 providing a use case.
David Rodríguez - dribeas
David, thanks for your help - I know that intel c++ compiler works with the regular [auto variableName = functionCall();] but it doesn't appear to support it for return type.
Jamie Cook
also I didn't realise that you could partially define the argument list - this is the perfect solution for the time being. Hopefully the intel guys will get their act together and allow me to play around with your decltype solution at some stage - for now I'm happy with what we've come up with. Thanks again!
Jamie Cook
I have deleted the previous comment. I failed to provide the -std=c++0x argument, so the errors I reported were not appropriate. Still the trailing return type is not yet supported, and it is a nice feature to have...
David Rodríguez - dribeas