Introduction:
Motivation
Sometimes there is a need to merge N > 1 ranges, and they don't have equal length, but there is a value which can be used in case the range is already exhausted. It is especially apparent in this question, in which most of the complexity comes from handling the nullness of either or both lists.
Solution
The solution constitutes a parody to std::optional
's value_or()
, but being the only option to retrieve a value. The idea is that users don't care if it is value from the range or it is default value. The stream will keep issuing values from range, until iterator hits end iterator, then it will return default value.
Problems by design
- Not modelled after standard library iterator
The problem lies in the ValueType
. I don't want to apply greater constraints on it, other than being constructible and destructible. One would think that since there is next()
, the value should copy/move constructible.
- No support for
std::vector<bool>
std::vector<bool>
for C++ is like older versions of IE for javascript. To return dereferenced value from iterator, the reference
must be constructible from outside of std::vector<bool>
, but that is not possible, so no, no support for std::vector<bool>
.
- Not compatible with standard library at all
The reason is that I didn't see much usage and discussion about this, thus I decided to take opportunity to look at more usage examples, before making more concrete interface.
Code
#include <utility>
#include <iterator>
namespace shino {
template <typename InputIterator,
typename Sentinel = InputIterator,
typename ValueType = typename std::iterator_traits<InputIterator>::value_type>
class endless_stream {
InputIterator first;
Sentinel last;
ValueType default_value;
public:
template <typename ... ArgTypes>
endless_stream(InputIterator first, Sentinel last, ArgTypes&& ... args) :
first(first),
last(last),
default_value(std::forward<ArgTypes>(args)...)
{}
ValueType& next() {
if (first == last) {
return default_value;
} else {
return *first++;
}
}
bool has_next() const {
return first != last;
}
InputIterator current_position() const {
return first;
}
Sentinel end() const {
return last;
}
};
}
Usage example
As it was born from this question, I thought that it would be only natural to target the problem in the demo on Wandbox:
using iterator = std::list<int>::iterator;
void add_digit_lists(shino::endless_stream<iterator> left,
shino::endless_stream<iterator> right,
std::list<int>& output) {
bool has_carry = false;
std::stack<int, std::vector<int>> prev_sums; //default is std::deque, not desired
while (left.has_next() || right.has_next() || has_carry) {
auto sum = left.next() + right.next() + has_carry;
has_carry = false;
if (sum > 9) {
sum -= 10;
has_carry = true;
}
prev_sums.push(sum);
}
while (not prev_sums.empty()) {
output.push_back(prev_sums.top());
prev_sums.pop();
}
}
Concerns
- Non-descriptive name of the class
One would need to read the docs to figure out the exact behavior of the facility. Is there a better one?
- Is it C++14 compatible?
I tried to keep the code C++14 friendly, but still allow C++17 users to not type too much.
- Is implicit class template argument deduction from constructor sufficient?
I'm worried that in some cases compiler won't be able to deduce some of the types if they are not provided and C++17 mechanism is used.
Any other suggestions are welcome.
iter::zip_longest
? For your specific use-case, chain the range with a repeat range may be more appropriate. \$\endgroup\$ – Snowhawk Aug 20 '18 at 22:31