How to Use the STL With Legacy Output Collections ⋆ Networkking4u

How to Use the STL With Legacy Output Collections

When you start using the STL and its algorithms in your code, it’s a bit of a change of habits. And then after a while you get used to it. Then it becomes a second nature. And then even your dreams become organized into beautifully structured ranges that fly in and out of well-oiled algorithms.

And when you reach that point, there is no coming back.

Until the day you come upon an old legacy structure that won’t let itself approached by the elegant and expressive way of coding that STL algorithms have. It’s a terrible encounter, where the beast tries to suck you back into the lengthy and dangerous quicksand of the raw for loops that now seemed so far away.

I’ve faced that day with my valiant colleague Gauthier, and together we drove an epic fight until we forced the beast into a several-inch thick STL prison, where it could no longer harm the rest of the code. Ok, it wasn’t that epic. But anyway, let me tell you that tale so that you can use it if you face a similar situation. We’ll see the main component that allowed us to do this, custom_inserter, so that you don’t need to dress up for this fight again (I later realized that something very close existed in Boost, boost function output iterator, so you’ll prefer that if you can use Boost in your code).

In other words, let’s see how to use the STL algorithms with legacy input and outputs.

We’ve already touched upon legacy or user-defined inputs, by studying the design of the STL. So now we’ll focus on how to output the results of an algorithm into a legacy structure that wasn’t designed to be compatible with the STL.

The case

I’m going to simplify the use case to the bare minimum to spend the less amount of time understanding it.

We have a collection of inputs, say in the form of a vector:

std::vector<Input> inputs = //…

1

std::vector<Input> inputs = //…

and a function f that we want to apply to each one of them:

Output f(Input const& input);

1

Output f(Input const& input);

This will result into as many Outputs. And we need to feed these outputs to an object that isn’t an STL container, and that doesn’t look like one. Maybe it’s an old C struct, or maybe it’s something more complicated. We’ll call this object legacyRepository, of type LegacyRepository. That’s the beast.

And legacyRepository comes with a function to add things into it:

void addInRepository(Output const& value, LegacyRepository& legacyRepository);

1

void addInRepository(Output const& value, LegacyRepository& legacyRepository);

It doesn’t have to be of that particular form, but I’m choosing this one to illustrate, because it really doesn’t look like STL containers’ typical interface.

If we could replace the old repository by an std::vector, then we’d have used std::transform with std::back_inserter and be done with it:

std::transform(begin(inputs), end(inputs), std::back_inserter(repository), f);

1

std::transform(begin(inputs), end(inputs), std::back_inserter(repository), f);

But you can’t always refactor everything, and in this case we couldn’t afford to refactor this right now. So, how should we proceed?

custom_inserter

So let’s create our custom_insert_iterator, that, instead of taking a container, takes a custom function (or function object) to replace the call to push_back:

template<typename OutputInsertFunction>

class custom_insert_iterator

{

public:

using iterator_category = std::output_iterator_tag;

explicit custom_insert_iterator(OutputInsertFunction insertFunction) : insertFunction_(insertFunction) {}

custom_insert_iterator& operator++(){ return *this; }

custom_insert_iterator& operator*(){ return *this; }

template<typename T>

custom_insert_iterator& operator=(T const& value)

{

insertFunction_(value);

return *this;

}

private:

OutputInsertFunction insertFunction_;

};

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

template<typename OutputInsertFunction>

class custom_insert_iterator

{

public:

using iterator_category = std::output_iterator_tag;

explicit custom_insert_iterator(OutputInsertFunction insertFunction) : insertFunction_(insertFunction) {}

custom_insert_iterator& operator++(){ return *this; }

custom_insert_iterator& operator*(){ return *this; }

template<typename T>

custom_insert_iterator& operator=(T const& value)

{

insertFunction_(value);

return *this;

}

private:

OutputInsertFunction insertFunction_;

};

And the custom_inserter helper function to avoid to specify template parameters at call site:

template <typename OutputInsertFunction>

custom_insert_iterator<OutputInsertFunction> custom_inserter(OutputInsertFunction insertFunction)

{

return custom_insert_iterator<OutputInsertFunction>(insertFunction);

}

1

2

3

4

5

template <typename OutputInsertFunction>

custom_insert_iterator<OutputInsertFunction> custom_inserter(OutputInsertFunction insertFunction)

{

return custom_insert_iterator<OutputInsertFunction>(insertFunction);

}

Here is how we can use it:

std::transform(begin(inputs), end(inputs),

custom_inserter([&legacyRepository](Output const& value){addInRepository(value, legacyRepository);}));

1

2

std::transform(begin(inputs), end(inputs),

custom_inserter([&legacyRepository](Output const& value){addInRepository(value, legacyRepository);}));

If you find this expression too cumbersome we can abstract the lambda:

auto insertInRepository(LegacyRepository& legacyRepository)

{

return [&legacyRepository](Output const& value)

{

addInRepository(value, legacyRepository);

};

}

1

2

3

4

5

6

7

auto insertInRepository(LegacyRepository& legacyRepository)

{

return [&legacyRepository](Output const& value)

{

addInRepository(value, legacyRepository);

};

}

in order to have a simpler call site:

std::transform(begin(inputs), end(inputs), custom_inserter(insertInRepository(legacyRepository)));

1

std::transform(begin(inputs), end

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: