Functional C ++ card combinator using auto

I am trying to use the fancy -std = C ++ 14 functions to implement the map combinator that you see in functional languages โ€‹โ€‹(not to be confused with std :: map). My ultimate goal is to write a โ€œfacade templateโ€ heading for functional programming that lets me forget about side effects and iterators most of the time. I found a like-minded post at https://gist.github.com/phatak-dev/766eccf8c72484ad623b . The version of the Madhukara map looks like

template <typename Collection,typename unop> Collection map(Collection col,unop op) { std::transform(col.begin(),col.end(),col.begin(),op); return col; } 

Everything seems to work fine until you ask for something stupid, but the return type should be the same as the input collection. My attempt to generalize to the presence of a domain and a range of different types is as follows:

 template <typename Collection, typename function> auto map(function f, Collection c) { auto result; std::transform(c.begin(),c.end(),result.begin(),f); return result; } 

This does not compile, but I hope someone understood what I am trying to do ... I want to initialize an empty container-type-of-type-of-type of type output-type-of-f, then put f(c[i]) . The compiler complains that the declaration of "automatic result" does not have an initializer, but I do not know how to ask that everything be empty. Is there a way to tune this line to make it do what I'm trying to do? I have never tried to do this exotic with a car before, so any additional suggestions are welcome.

Thanks!

John

Edit: here you can use a reliable example:

 auto first_letter = [](string s) { return s[0]; } vector<string> words; words.push_back("hello"); words.push_back("world"); vector<char> first_letters = map(first_letter, words); // {'h','w'} 

Editing 2: Here, a different approach is used that uses a supercomputer "threading" library (not to be confused with I / O streams) to implement an "iterator pattern", for example Java streams:

http://jscheiny.imtqy.com/Streams/

Java approach: http://tutorials.jenkov.com/java-collections/streams.html

This threading approach allows more freedom of choice by container type (as several respondents say) and lazy pricing.

+5
source share
3 answers

Just use boost :: adapters :: tranformed :

 #include <boost/range/adaptor/transformed.hpp> template <typename Collection, typename function> auto map(function f, Collection c) { return c | boost::adaptors::transformed(f); } 

In this range, you can create any container you want.

 char first_letter(string s) { return s[0]; } vector<string> words; words.push_back("hello"); words.push_back("world"); auto transformed_range = map(first_letter, words); vector<char> first_letters(begin(transformed_range ), end(transformed_range )); 

If you insist that the map function return a container, not a range, add another parameter to this function template:

 #include <boost/range/adaptor/transformed.hpp> template <typename Result, typename Collection, typename function> auto map(function f, Collection c) { auto transformed_range = c | boost::adaptors::transformed(f); return Result(begin(transformed_range), end(transformed_range)); } char first_letter(string s) { return s[0]; } vector<string> words; words.push_back("hello"); words.push_back("world"); vector<char> first_letters = map<vector<char>>(first_letter, words); 

But if you really insist on having the exact behavior as you want, you must have some traits in knowing how to convert a collection type to another collection type with a converted value.

First up is the way to have new_value_type:

 template <typename Function, typename OldValueType> struct MapToTransformedValue { using type = decltype(std::declval<Function>()(std::declval<OldValueType>())); }; 

Common feature:

 template <typename Function, typename Container> struct MapToTransformedContainer; 

The simplest case is for std::array :

 // for std::array template <typename Function, typename OldValueType, std::size_t N> struct MapToTransformedContainer<Function, std::array<OldValueType, N>> { using value_type = typename MapToTransformedValue<Function, OldValueType>::type; using type = std::array<value_type, N>; }; 

For std::vector - a little more complicated - you need to provide a new dispenser, for std allocators - you can use its reprocessing template:

 // for std::vector template <typename Function, typename OldValueType, typename OldAllocator> struct MapToTransformedContainer<Function, std::vector<OldValueType, OldAllocator>> { using value_type = typename MapToTransformedValue<Function, OldValueType>::type; using allocator = typename OldAllocator::template rebind<value_type>::other; using type = std::vector<value_type, allocator>; }; 

So your function will look like this:

 template <typename Collection, typename function> auto map(function f, Collection c) { using NewCollectionType = typename MapToTransformedContainer<function, Collection>::type; auto transformed_range = c | boost::adaptors::transformed(f); return NewCollectionType (begin(transformed_range), end(transformed_range)); } 

Now - your main() optionally:

 char first_letter(std::string const& s) { return s[0]; } int main() { std::vector<std::string> words; words.push_back("hello"); words.push_back("world"); auto first_letters = map(first_letter, words); std::cout << first_letters[0] << std::endl; } 

Beware that for other containers where value_type consists of Key,Value , a pair like std::map , std::set (and their unordered _... siblings), you must define a different specialization MapToTransformedContainer ...

+2
source

Just for fun, here is my problem:

 #include <iostream> #include <algorithm> #include <set> #include <vector> template <template<typename...> class Collection> struct mapper { template<typename function, typename T, typename... Rest> static auto map(function f, const Collection<T, Rest...>& c) { Collection<decltype(f(*c.begin()))> result; std::transform(c.begin(),c.end(),std::inserter(result, result.end()),f); return result; } }; int main() { // Example 1 std::vector<int> v{0, 1}; auto fv = mapper<std::vector>::map([](const auto& val) { return val + 0.1f; }, v); for (const auto& f : fv) { std::cout << f << " "; } std::cout << "\n"; // Example 2 std::set<float> m{1, 2, 3, 4}; auto fm = mapper<std::set>::map([](const auto& val) { return static_cast<int>(val / 2.0f); }, m); for (const auto& f : fm) { std::cout << f << " "; } } 

Note that this will only work as long as you are satisfied with the default values โ€‹โ€‹for all but the output container type parameter.

0
source

I would take advantage of the fact that most containers have a constructor that takes a pair of iterators.

 #include <boost/iterator/transform_iterator.hpp> template <typename Function, typename Collection> struct map_to { Function f; const Collection& c; template <typename T> operator T() && { using std::begin; using std::end; return { boost::make_transform_iterator(begin(c), f) , boost::make_transform_iterator(end(c), f) }; } }; template <typename Function, typename Collection> map_to<Function, Collection> map(Function f, const Collection& c) { return { f, c }; } 

Tests:

 int main() { std::vector<std::string> words; words.push_back("hello"); words.push_back("world"); auto first_letter = [](std::string s) { return s[0]; }; std::vector<char> v = map(first_letter, words); std::set<char> s = map(first_letter, words); std::forward_list<char> f = map(first_letter, words); } 

Demo

0
source

Source: https://habr.com/ru/post/1233477/


All Articles