4

I have been trying to implement in C++11 the function map from Python. It seems to work for any kind of callable objet, but I have to specify the template type parameter if I want it to work with function templates. Example:

#include <iostream> #include <list> template<typename T> T abs(T x) { return x < 0 ? -x : x; } int main() { std::list<int> li = { -1, -2, -3, -4, -5 }; for (auto i: map(&abs<int>, li)) { std::cout << i << std::endl; } } 

It works fine, but I would like it to deduce the int parameter from the second argument of the function, and hence be able to write:

for (auto i: map(&abs, li)) { std::cout << i << std::endl; } 

My map function is written as:

template<typename Callable, typename Container> auto map(const Callable& function, Container&& iter) -> MapObject<Callable, Container> { return { function, std::forward<Container>(iter) }; } 

where MapObject is part of the implmentation and not a real problem here. How could I change its definition so that the template type of the Callable object can be deduced from the Container object? For example, how can map know that we have to use abs<int> for a given abs when a list<int> is given?

8
  • You could use T = Container::value_type and add a specialization for T(T)... Commented Apr 27, 2013 at 19:22
  • @KerrekSB I would like to keep the current compatibility with C arrays. Commented Apr 27, 2013 at 19:25
  • 1
    I don't think it is possible in this form, because std::abs is not even a callable, it is just template. You can do some wired things like map<std::abs>(li), but I don't think it would be better. Commented Apr 27, 2013 at 19:26
  • std::abs is a template? Defined in a C header? Commented Apr 27, 2013 at 19:42
  • 1
    Uhm... std::transform(li.begin(),li.end(),std::ostream_iterator<int>(std::cout,"\n"), [](int x){ return abs(x); }); Commented Apr 27, 2013 at 19:58

2 Answers 2

5

It works fine, but I would like it to deduce the int parameter from the second argument of the function, and hence be able to write:

for (auto i: map(&abs, li)) { std::cout << i << std::endl; } 

The problem is that abs is not a function, but a function template, and thus there is no address-of abs, although there is &abs<int>, since abs<int> (specialization) is indeed a function (generated from a template).

Now the question is what you really want to solve, and in particular you must realize that C++ is a statically typed language where python is a dynamically typed language. It is unclear to me what you are trying to achieve here on different levels. For example, the function map in python has an equivalent in std::transform in C++:

a = [ 1, 2, 3 ] a = map(lambda x: 2*x, a) std::vector<int> v{1,2,3}; std::transform(v.begin(),v.end(),v.begin(),[](int x){ return 2*x; }); 

Where I have cheated slightly because in python it will create a different container yet in C++ transform works at the iterator level and knows of no container, but you can get the same effect similarly:

std::vector<int> v{1,2,3}; std::vector<int> result; // optionally: result.reserve(v.size()); std::transform(v.begin(),v.end(), std::back_inserter(result), [](int x) { return 2*x; }); 

I'd advice that you learn the idioms in the language rather than trying to implement idioms from other languages...

BTW, if you are willing to have the user specify the type of the functor that is passed to the map function, then you can just pass the name of the template and let the compiler figure out what specialization you need:

template <typename Container> auto map(Container && c, typename Container::value_type (*f)(typename Container::value_type)) -> MapObject<Callable<T>,Container>; template <typename T> T abs(T value); int main() { std::vector<int> v{1,2,3,4}; map(v,abs); } 

This is less generic than what you were trying to do, as it only accepts function pointers and of concrete type (this is even less generic than std::transform) and it works as when the compiler sees abs (without the &) it will resolve it to the template, and thus to the set of specializations. It will then use the expected type to select one specialization and pass it in. The compiler will implicitly do &abs<int> for you in this case.

Another more generic alternative is not using functions, but functors. With this in mind you can define abs as:

struct abs { template <typename T> T operator()(T t) { ...} }; 

And then pass a copy of the functor in instead of the function pointer. There is no need to determine the overload to be used where you pass the object abs into the map function, only when it is used. The caller side would look like:

for (auto& element : map(container,abs())) 

Where the extra set of parenthesis is creating an object of type abs and passing it in.

Overall, I would try to avoid this. It is a fun thing to do, and you can probably get to a good solution, but it will be hard and require quite a bit of c++ expertise. Because it is not supported by the language, you will have to design something that works within the language and that requires compromises on different features or syntax. Knowing the options is a hard problem in itself, understanding the compromises even harder and getting to a good solution much harder. And the good solution will probably be worse than the equivalent idiomatic C++ code.

If you program in C++, program C++. Trying to code python through a C++ compiler will probably give you the pain of C++ and the performance of python.

Sign up to request clarification or add additional context in comments.

16 Comments

+1 I didn't know it was possible to pass a template name and let the compiler deduce the specialization.
@DyP: That is just imprecise wording on my side :) You are really passing a function, the compiler is just picking one for you. And while most people is not aware of it, it is done very often: std::cout << std::endl;
Great answer, I really do like it! I learnt several things that I did not know before. Thanks a lot!
Do you really just pass a function? I mean, you're passing the name of a template, so the compiler might have to instantiate it before using it; that is, it's not just overload resolution on a fixed set of functions.
"I'd advice that you learn the idioms in the language" -- I think that trying to get functional idions to work in C++ is a good goal, since C++ can certainly be used in a functional way - it's just that the support isn't quite there yet (something I hope to redeem). Also, ranges will drive more functional-style too (like Boost.Range already does).
|
-1

It doesn't deduce it because you never specified that Callable is a template. You make Callable a template template parameter and it should deduce its type for you.

template<template <typename T> typename Callable, typename Container> auto map(const Callable<T>& function, Container&& iter) -> MapObject<Callable<T>, Container> { return { function, std::forward<Container>(iter) }; } 

You might get bitten though as you can't take the address of a template still to be instantiated. Not sure why you need the address-of though...

1 Comment

If i remember well, we can't do anything with that T parameter in real code.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.