How to Send an STL Collection to a Curried Object
After seeing how to send individual objects to a curried object, let’s see how we can haul a whole collection into one of those curried creatures. One use case for this is to intersperse a collection of strings with commas.
If you’re jumping in the topic of curried objects just now, a curried object is an intermediary object that facilitates the dialogue between a caller and a callee by fixing some parameters and potentially adapting other parameters.
We’ve seen that those objects can carry some logic that ends up simplifying the application code. If you want to catch up on the previous episodes, the series on curried objects contains:
- Part 1: Constant curried objects
- Part 2: How to Output Strings Separated by Commas in C++ (Mutable curried objects)
- Part 3: Sending an STL Collection to a Curried Object
Motivating example
Let’s pick up our curried object CSVPrinter
. It accepts successive strings and sends them to a stream by alternating them with commas, and makes sure not to write a trailing comma at the end of the stream.
Here is the implementation of CSVPrinter
:
#include <iostream> class CSVPrinter { public: explicit CSVPrinter(std::ostream& output) : output_(output), isFirst_(true) {} friend CSVPrinter& operator<<(CSVPrinter& csvPrinter, std::string const& value) { if (csvPrinter.isFirst_) { csvPrinter.isFirst_ = false; } else { csvPrinter.output_ << ','; } csvPrinter.output_ << value; return csvPrinter; } private: std::ostream& output_; bool isFirst_; };
Note that this is just one particular case of curried object. Curried object don’t have to have an operator<<
.
Here is some calling code to exerce it:
CSVPrinter csvPrinter{std::cout}; csvPrinter << "tick"; csvPrinter << "tack"; csvPrinter << "toe";
This code outputs:
tick,tack,toe
Now let’s take a collection of strings:
static std::vector<std::string> getSentence() { return {"And", "then", "there", "were", "none"}; }
And let’s send all the objects of this collection to our curried object. In our case, it will print them by interspersing them with commas:
CSVPrinter csvPrinter{std::cout}; auto sentence = getSentence(); for (auto const& word : sentence) { csvPrinter << word; }
Indeed, the following code outputs:
And,then,there,were,none
Now we have a very simple operation, that has a very simple code and that’s all well.
So what the point of going further?
It is to integrate curried objects with STL algorithms, to let the algorithms send their outputs into a curried object.
Not that it would be useful in this particular case, because the code is so simple here. But working on such a basic case will let us focus on the integration of the curried object with STL algorithms in general (to easily intersperse their outputs with commas, for one example).
So let’s get into this.
First (bad) attempt: using a function object
To turn this piece of code into an algorithm call:
for (auto const& word : sentence) { csvPrinter << word; }
A intuitive option could be to use std::for_each
:
auto sendToCsvPrinter = [&csvPrinter](std::string const& word) {csvPrinter << word;}; std::for_each(begin(sentence), end(sentence), sendToCsvPrinter);
Granted, this may not be an improvement to the code because it was so simple, but we’re just studying how to connect an algorithm with a curried object in a simple case.
Let’s run the code:
And,then,there,were,none
The result is correct. But is this the right way to integrate the curried object with the STL? Can we generalize it to other algorithms than for_each
?
The answer is No, for at least two reasons. One is that all algorithms don’t take a function object, to begin with. Take set_difference
, or partial_sum
, or rotate_copy
for example.
The other reason is that even for the algorithms that do take a function object, such as std::transform
for instance, some don’t guarantee that they will traverse the input range in order. So the algorithm may call the function object in any order and send the result to our curried object in an order different from the input, that could lead to, for example:
then,none,there,were,And
std::for_each
guarantees to traverse the input collection in order tough.
Note that in general, carrying a mutable state inside of a function object can lead to incorrect results because most algorithms are allowed to make internal copies of the function object (std::for_each
guarantees that it won’t, though). This leads to the mutable state being located in different object, that could lose consistency with each other (this is why in the STL function objects, stateless is stressless). However, here we don’t have this problem since the function object only has a reference to the state, and not the state itself.
Anyway, for the above two reasons using a function object to connect an STL algorithm to a curried object is not a good idea.
So what to do then?
A better solution: using the output iterator
Going back to our initial code:
for (auto const& word : sentence) { csvPrinter << word; }
Another way to see the situation is that we’re sending data to the CSVPrinter
, or said differently, that we’re copying data from the sentence
container over to the CSVPrinter
. So instead of std::for_each
, we could use std::copy
.
But then, we need something to make std::copy
send the data to the curried object. std::copy
uses an output iterator to emit its output data. So we need an custom output iterator that we could customize and plug to CSVPrinter
.
A custom inserter? Let’s use custom_inserter
!
As a reminder, the definition of custom_inserter
looked like this:
template<typename OutputInsertFunction> class custom_insert_iterator { public: using iterator_category = std::output_iterator_tag; using value_type = void; using difference_type = void; using pointer = void; using reference = void; 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_; }; template <typename OutputInsertFunction> custom_insert_iterator<OutputInsertFunction> custom_inserter(OutputInsertFunction insertFunction) { return custom_insert_iterator<OutputInsertFunction>(insertFunction); }
The most important part in custom_inserter
is this:
custom_insert_iterator& operator=(T const& value) { insertFunction_(value); return *this; }
It is an iterator that, when an algorithm sends data to it, passes on this data to a custom function (insertFunction_
in the above code).
Here is how custom_inserter
can help us connect std::copy
to our curried object CSVPrinter
:
auto sendToCsvPrinter = custom_inserter([&csvPrinter](std::string const& word) {csvPrinter << word;}); std::copy(begin(sentence), end(sentence), sendToCsvPrinter);
which outputs:
And,then,there,were,none
We had encountered custom_inserter
when making STL algorithms output to legacy collections, but we see here another usage: outputting to a curried object.
In a more elaborate operation on a collection, such as std::set_difference
for example, we can use custom_inserter
to send the output of the algorithm to the curried object in a similar way:
std::set_difference(begin(set1), end(set1), begin(set2), end (set2), sendToCsvPrinter);
Using the channel of the output iterators doesn’t suffer from the two issues that we raised when attempting to use the function object. Indeed:
- all the algorithms that output a range do have one (or more) output iterators,
- even if some algorithms don’t treat the input in order, they all send data to the output in order.
It’s not just about interspersing with commas
All the way through this example, we’ve used a curried object to intersperse the words of a sentence with commas. Note that this curried object wrapped into an output iterators is in the technical specification for the future standard library under the name of std::ostream_joiner
. But this is just one specific case of curried objects, and there are other ways than curried objects to fill this need.
As a side note, the most elegant way I know of to intersperse a sentence with commas is by using the range-v3 library:
#include <iostream> #include <string> #include <vector> #include <range/v3/to_container.hpp> #include <range/v3/view/intersperse.hpp> int main() { std::vector<std::string> numbers = {"And", "then", "there", "were", "none"}; std::vector<std::string> results = numbers | ranges::view::intersperse(",") | ranges::to_vector; for (auto const& result : results) std::cout << result; }
Which outputs:
And,then,there,were,none
Isn’t it beautiful? However if you don’t have range-v3 available, a curried object is a nice way to do the job, in my opinion.
Conversely, curried objects can be used for so much more. They make application code (and therefore, life) easier to read and write and, as an icing on the cake they can be integrated with the STL by using smart output iterators.
That’s it for our series on curried objects. Your reactions are, as usual, welcome.
Related articles:
- Constant curried objects
- How to Output Strings Separated by Commas in C++ (Mutable curried objects)
- Smart Output Iterators: A Symmetrical Approach to Range Adaptors
- How to Use the STL With Legacy Output Collections
Share this post!