After tweaking semantics a lot, it ended up being a very poor object version of std::transform<>()
that can be used with other std algorithms.
Basically the iterator wraps a provided iterator, creating an output iterator and applies supplied functor when dereferenced and assigned to. The caveat is that users will need to identify parameter type themselves, which can be error prone, but might open opportunities for other usages. Input type should not be cv qualified, because it has overloads for both const lvalue reference and rvalue reference. If the user wishes to take parameter by value, they would need to write another functor for that.
#ifndef SUNRISE_PREPROCESSING_ITERATOR_HPP
#define SUNRISE_PREPROCESSING_ITERATOR_HPP
#include <iterator>
namespace shino
{
template<typename Functor, typename InputType, typename Iterator>
class preprocessing_iterator :
public std::iterator<std::output_iterator_tag,
InputType, void, void, void>
{
Functor functor;
Iterator iterator;
public:
preprocessing_iterator(const Functor &f, const Iterator &it) :
functor(f),
iterator(it)
{}
preprocessing_iterator(Functor &&f, Iterator &&it) :
functor(f),
iterator(it)
{}
class proxy
{
friend class preprocessing_iterator;
Iterator &iterator;
Functor &f;
public:
using value_type = InputType;
proxy &operator=(const value_type &value)
{
*iterator = f(value);
return *this;
}
proxy &operator=(value_type &&value)
{
*iterator = f(value);
return *this;
}
private:
proxy(Iterator &it, Functor &functor) :
iterator(it),
f(functor)
{}
};
proxy operator*()
{
return proxy(iterator, functor);
}
preprocessing_iterator &operator++()
{
++iterator;
return *this;
}
preprocessing_iterator operator++(int)
{
auto copy = *this;
++iterator; //might exhibit different behavior sometimes
return copy;
}
const Iterator& internal_iterator() const
{
return iterator;
}
const Functor& internal_functor() const
{
return functor;
}
void swap(preprocessing_iterator& other)
{
using std::swap;
swap(other.functor, functor);
swap(other.iterator, iterator);
}
};
template<typename Functor, typename InputType, typename Iterator>
bool operator==(const preprocessing_iterator<Functor, InputType, Iterator>& lhs,
const preprocessing_iterator<Functor, InputType, Iterator>& rhs)
{
return lhs.internal_iterator() == rhs.internal_iterator();
};
template <typename Functor, typename InputType, typename Iterator>
bool operator!=(const preprocessing_iterator<Functor, InputType, Iterator>& lhs,
const preprocessing_iterator<Functor, InputType, Iterator>& rhs)
{
return !(lhs == rhs);
};
}
namespace std
{
template <typename Functor, typename InputType, typename Iterator>
void swap(shino::preprocessing_iterator<Functor, InputType, Iterator>& lhs,
shino::preprocessing_iterator<Functor, InputType, Iterator>& rhs)
{
lhs.swap(rhs);
};
}
#endif //SUNRISE_PREPROCESSING_ITERATOR_HPP
Usage:
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
void convert_strings_to_ints()
{
std::vector<std::string> v{"23", "25", "27"}; //just random numbers
std::vector<int> output(v.size());
auto string_to_int = [](const std::string& x)
{
return std::stoi(x);
};
shino::preprocessing_iterator<decltype(string_to_int),
std::string, std::vector<int>::iterator> conversion_iterator(string_to_int, output.begin());
std::copy(v.begin(), v.end(), conversion_iterator);
std::cout << "Output after converting v to vector of ints\n";
for (const auto& element: output)
{
std::cout << element << '\n';
}
}
int main()
{
convert_strings_to_ints();
}
As it can be seen, the best usage case is writing adapters. I've tried to lower the overhead as much as possible, but if Functor
is not lightweight to copy, it might become a problem if iterators are copied too much.
My specific concerns are:
Easy of use. This is the main issue.
Obscure places that should be documented. From the first sight of my familiar nothing seems strange or ambiguous.
Any place where I could accidentally cause very big slow down.