Search code examples
c++boostiteratorstandard-libraryboost-range

C++ algorithms that create their output-storage instead of being applied to existing storage?


The C++ std algorithms define a number of algorithms that take an input and an output sequence, and create the elements of the output sequence from the elements of the input sequence. (Best example being std::transform.)

The std algorithms obviously take iterators, so there's no question that the container for the OutputIterator has to exist prior to the algorithm being invoked.

That is:

std::vector<int> v1; // e.g. v1 := {1, 2, 3, 4, 5};

std::vector<int> squared;
squared.reserve(v1.size()); // not strictly necessary
std::transform(v1.begin(), v1.end(), std::back_inserter(squared), 
               [](int x) { return x*x; } ); // λ for convenience, needn't be C++11

And this is fine as far as the std library goes. When I find iterators too cumbersome, I often look to Boost.Range to simplify things.

In this case however, it seems that the mutating algorithms in Boost.Range also use OutputIterators.

So I'm currently wondering whether there's any convenient library out there, that allows me to write:

std::vector<int> const squared = convenient::transform(v1, [](int x) { return x*x; });

-- and if there is none, whether there is a reason that there is none?

Edit: example implementation (not sure if this would work in all cases, and whether this is the most ideal one):

template<typename C, typename F>
C transform(C const& input, F fun) {
   C result;
   std::transform(input.begin(), input.end(), std::back_inserter(result), fun);
   return result;
}       

(Note: I think convenient::transform will have the same performance characteristics than the handwritten one, as the returned vector won't be copied due to (N)RVO. Anyway, I think performance is secondary for this question.)


Edit/Note: Of the answers(comments, really) given so far, David gives a very nice basic generic example.

And Luc mentions a possible problem with std::back_inserter wrt. genericity.

Both just go to show why I'm hesitating to whip this up myself and why a "proper" (properly tested) library would be preferable to coding this myself.

My question phrased in bold above, namely is there one, or is there a reason there is none remains largely unanswered.


Solution

  • The Boost.Range.Adaptors can be kind of seen as container-returning algorithms. Why not use them?

    The only thing that needs to be done is to define a new range adaptor create<T> that can be piped into the adapted ranges and produces the desired result container:

    template<class T> struct converted{}; // dummy tag class
    
    template<class FwdRange, class T>
    T operator|(FwdRange const& r, converted<T>){
      return T(r.begin(), r.end());
    }
    

    Yep, that's it. No need for anything else. Just pipe that at the end of your adaptor list.

    Here could be a live example on Ideone. Alas, it isn't, because Ideone doesn't provide Boost in C++0x mode.. meh. In any case, here's main and the output:

    int main(){
      using namespace boost::adaptors;
      auto range = boost::irange(1, 10);
      std::vector<int> v1(range.begin(), range.end());
    
      auto squared = v1 | transformed([](int i){ return i * i; });
      boost::for_each(squared, [](int i){ std::cout << i << " "; });
      std::cout << "\n========================\n";
      auto modded = squared | reversed
                            | filtered([](int i){ return (i % 2) == 0; })
                            | converted<std::vector<int>>(); // gimme back my vec!
      modded.push_back(1);
      boost::for_each(modded, [](int i){ std::cout << i << " "; });
    }
    

    Output:

    1 4 9 16 25 36 49 64 81
    ========================
    64 36 16 4 1