0

Let's say (for simplicity) that I want to pass collection to method, the method will apply some func to every element of collection and return this collection. E.g. in C# this will looks like this

IEnumerable<Tout> Transform<Tin, Tout>(IEnumerable<Tin> collection, 
    Func<Tin, Tout> func)
{
    return collection.Select(x => func(x));
}

My goal is to write equivalent function in C++. According to this question I should pass two iterators to function which will represent boundaries of input collection. This solves problem of passing collection. But how to return collection?

I was thinking that I should apply same logic and return pair of iterator from function which will represent returning collection.

Here is how I tried to write equivalent function:

template<typename ForwardIterator, typename ReturnIterator>
std::pair<ReturnIterator, ReturnIterator> Transform(
    ForwardIterator begin, ForwardIterator end,
    std::function <
        typename std::iterator_traits<ReturnIterator>::value_type      
       (typename std::iterator_traits<ForwardIterator>::value_type) 
    > func)
{
    using InputType = std::iterator_traits<ForwardIterator>::value_type;
    using RetType = std::iterator_traits<ReturnIterator>::value_type;   
    std::vector<RetType> ans;
    std::transform(begin, end, std::back_inserter(ans),
        [&](InputType el) -> RetType { return func(el); } );    
    return { std::begin(ans), std::end(ans) };
}

int main()
{
    // Simple example -> converts every int to string from inputCollection
    std::vector<int> inputCollection = { 1,2,3 };
    auto retCollecction = Transform<std::vector<int>::iterator, std::vector<std::string>::iterator>
        (std::begin(inputCollection),
            std::end(inputCollection),
            [](int el)-> std::string {return std::to_string(el); });
}

Obviously this is not good since output collection is disposed as soon as I exit function and iterators points to nothing. So, how to fix this, and what should be the best way to write this in C++.

Note: I don't want to pass and return vector<T> or some other specific collection. I would like a general approach which can deal with any type of collections.

Dejan
  • 966
  • 1
  • 8
  • 26

3 Answers3

1

The general C++ approach is to accept an output iterator. Let the caller decide where the output should go.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • A simple example like the one I provided would be very useful. – Dejan Nov 08 '18 at 15:31
  • @Dejan E.g. https://en.cppreference.com/w/cpp/algorithm/copy look at example at the bottom of that page and likely of all the other pages about `` functions. – Bob__ Nov 08 '18 at 15:48
  • @Bob__ From the copy example: I should pass beginning of output iterator to function and function will return end of output iterator. So my mistake was because I was trying to return begin and end of output iterator as a pair? – Dejan Nov 08 '18 at 15:57
  • @Dejan: Given that your example was `Transform`, you may want to peek at [`std::transform`](https://en.cppreference.com/w/cpp/algorithm/transform). There's no need to write this, it's already part of the Standard Library – MSalters Nov 08 '18 at 18:07
  • The difference is: `std::transform` accept `class UnaryOperation` as a template parameter. I'm trying here not to introduce that template parameter, but to use `std::function` parameter which types can be deducted based on iterator types. I do not understand the need of the third template parameter. However, I am still struggling to create workable example since `std::iterator_traits::value_type` is not working when `ReturnIterator` is output iterator, i.e. `back_insert_iterator`. – Dejan Nov 08 '18 at 18:24
  • @Dejan: I'm probably missing what you're trying to say here. `std::transform` will accept a `std::function`. As for the return type, you're being a bit picky there. You want to accept anything that can be written to that output iterator. And that might be more than one type. – MSalters Nov 08 '18 at 18:28
  • What I want to say is that `std::transform` have `template< class InputIt, class OutputIt, class UnaryOperation >`. I am trying to make template with the only first two types:InputIt and OutputIt. I hope that `std::function` input type can be automatically deduced from the `InputIt` type and the function output type can be deduced from the `OutputIt` type. For example, if the output iterator is created on vector of `string`s and input iterator is created on vector of `int`s, then I already know that function must accept `int` and return `string`, or I'm missing something? – Dejan Nov 08 '18 at 18:43
  • @Dejan: Yes, you miss implicit conversions. Also, why the needless restriction to `std::function`? – MSalters Nov 08 '18 at 21:59
1

The ranges library uses the concept of, well, ranges - a pair of start and end iterator. If you plan to write LINQ-like code a lot, you should probably look into it and base your code around its concepts:

https://github.com/ericniebler/range-v3

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
0

The (current standard library) C++ way is to accept an output iterator, see std::transform.

The (future) C++ way is to return a Range value, see ranges::transform

Note that you can wrap an output iterator algorithm in an asymmetric coroutine to get a range-like value.

template<typename InputIterator, typename Func>
auto transform(InputIterator begin, InputIterator end, Func func)
{
    using coro_t = boost::coroutines2::coroutine<decltype(func(*begin))>;
    return coro_t::pull_type([=](coro_t::push_type& yield)
    {
        std::transform(begin, end, yield, func);
    });
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • Tnx for the answer. Actually, I do not use boost. I am trying to create working version using output iterators. – Dejan Nov 08 '18 at 16:16
  • @Dejan the real problem with output iterators is that they don't compose – Caleth Nov 08 '18 at 16:17
  • What do you mean by "they don't compose"? – Dejan Nov 08 '18 at 16:20
  • you can't `std::transform(???, std::copy_if(b, e, ???, pred), out, func)` or even `auto mid_e = std::copy_if(b, e, mid_b, pred); std::transform(mid_b, mid_e, out, func)` without a container providing intermediate storage – Caleth Nov 08 '18 at 16:26
  • you *can* `ranges::transform(ranges::filter(foo, pred), func)`. Or in C# `foo.Where(pred).Select(func)` – Caleth Nov 08 '18 at 16:28
  • Oh, that's what you mean by compose. Actually I really miss that feature from C# Linq. With C++ iterators everything is super complicated. – Dejan Nov 08 '18 at 16:32