Simple stream iterator in C++ - c++

Consider a Java-like streaming iterator:
template<class V>
struct Iterator
{
V& next();
bool hasNext();
}
template<class V>
struct Iterable
{
Iterator<V> iterator();
}
How would you cast this into a std::iterator so that you can use it in find, for-loops, etc.
I think I need to start with an InputIterator, but I am struggling with the following things here:
meaningful end()?
when are they equal()?
what about Distance?

It is quite doable, but a pain.
You would be better off doing a generator-based iterator, where the backing operation is a std::function< optional<T> >. Here is an example of such a generator-iterator:
template<class T>
struct generator_iterator {
using difference_type=std::ptrdiff_t;
using value_type=T;
using pointer=T*;
using reference=T;
using iterator_category=std::input_iterator_tag;
std::optional<T> state;
std::function< std::optional<T>() > operation;
// we store the current element in "state" if we have one:
T operator*() const {
return *state;
}
// to advance, we invoke our operation. If it returns a nullopt
// we have reached the end:
generator_iterator& operator++() {
state = operation();
return *this;
}
generator_iterator operator++(int) {
auto r = *this;
++(*this);
return r;
}
// generator iterators are only equal if they are both in the "end" state:
friend bool operator==( generator_iterator const& lhs, generator_iterator const& rhs ) {
if (!lhs.state && !rhs.state) return true;
return false;
}
friend bool operator!=( generator_iterator const& lhs, generator_iterator const& rhs ) {
return !(lhs==rhs);
}
// We implicitly construct from a std::function with the right signature:
generator_iterator( std::function< std::optional<T>() > f ):operation(std::move(f))
{
if (operation)
state = operation();
}
// default all special member functions:
generator_iterator( generator_iterator && ) =default;
generator_iterator( generator_iterator const& ) =default;
generator_iterator& operator=( generator_iterator && ) =default;
generator_iterator& operator=( generator_iterator const& ) =default;
generator_iterator() =default;
};
Doing so with your Java-like iterface can still be done.
There are going to be solutions in boost that make this easier. But I'll write it "in the raw" based off C++17 concepts, which can be backported to C++11 if you need them (or extracted from boost or other sources).
You'd be generating an input iterator, because that is what Java-like interface supports.
First I'd write a helper. The helper would hold a optional< Iterator<V> > and an optional<V>.
It would support ++. ++ would advance the iterator and read the value into the optional<V>.
It would support unary *. * would return the value in the optional.
bool is_end() returns true if the optional<Iterator<V>> is empty, or if it !.hasNext().
== returns true if and only if both arguments .is_end(). != would just be ! applied to ==.
This helper isn't yet an iterator, but it has most of the key operations.
Then we use this poly_iterator which type erases anything iterator-like. The operations we wrote above on the helper are sufficient.
Then we write a function that takes a Iterable<V> and produces a range<poly_iterator<T>> of the type erasure class above, using the helper pseudo-iterator above. A range<It> is a class that looks like:
template<class It>
struct range {
It b; It e;
It begin() const { return b; }
It end() const { return e; }
};
plus maybe other utility helpers.
The type erasure step could be skipped if you chose. Just augment the helper written above to be a full-fledged input iterator. Boost has helpers that make this a touch easier. Have the range-returner return that full-fledged input iterator. I happened to have the poly_iterator<T> lying around in another answer, and if you have a hammer that problem sure looks like a nail.
Note that for the code to be really Java-like, we'd want to actually have (smart) pointers to the objects not copies.

Related

Do custom iterators always need to explicitly specify value_type?

Since iterator is deprecated, I began converting iterators in my code base to use non-deprecated constructs. I could not seem to make my indirect iterator compliant with the std::forward_iterator concept unless I explicitly specified value_type. I would like to know if this is expected.
Based on the definition of iter_value_t and indirectly_readible_traits, it seems like there is no automatic inference of std::iter_value_t. Naively, I would have expected std::iter_value_t<Itr> to be defined as std::remove_cvref_t<std::iter_reference_t<Itr>> if no definition for value_type is present (which is checked via has-member-value-type in indirectly_readible_traits).
#include <vector>
template <std::forward_iterator Itr>
class IndirectItr {
public:
using value_type = std::iter_value_t<Itr>; // **do I need this?**
explicit IndirectItr(Itr itr = {}) : m_itr{itr} {}
bool operator==(const IndirectItr& rhs) const { return m_itr == rhs.m_itr; }
bool operator!=(const IndirectItr& rhs) const { return m_itr != rhs.m_itr; }
typename std::iter_reference_t<Itr> operator *() const { return *m_itr; }
IndirectItr& operator++() { ++m_itr; return *this; }
IndirectItr operator++(int) { auto ret = *this; ++(*this); return ret; }
typename std::iter_difference_t<Itr> operator-(const IndirectItr& rhs) const { return m_itr - rhs.m_itr; }
private:
Itr m_itr;
};
using Base = std::vector<int>::iterator;
static_assert(std::forward_iterator<IndirectItr<Base>>);
static_assert(std::same_as<std::iter_value_t<Base>, std::remove_cvref_t<std::iter_reference_t<Base>>>);
P.S. I have several indirect iterator definitions that wrap other iterators. The example above is representative of a custom indirect iterator. I don't have this exact class in my code.
You don't have to have a member value_type on your iterator. But your only alternative is to specialize iterator_traits<T> for your iterator type and provide a value_type alias there. So you may as well make it a member of the iterator.
The value_type cannot be computed from something else, as it may have no obvious relation to reference or any other operation on the iterator. This is one of the things that allows for proxy iterators, which pre-C++20 concepts did not.
std::forward_iterator includes std::input_iterator, which includes std::indirectly_readable, which contains:
requires(const In in) {
typename std::iter_value_t<In>;
typename std::iter_reference_t<In>;
typename std::iter_rvalue_reference_t<In>;
{ *in } -> std::same_as<std::iter_reference_t<In>>;
{ ranges::iter_move(in) } -> std::same_as<std::iter_rvalue_reference_t<In>>;
}
(where In is std::remove_cvref_t<IndirectItr<Base>>).
That typename std::iter_value_t<In>; line requires you to declare a value_type or to specialize std::iterator_traits<IndirectItr<Base>> (and provide value_type there), as explained here.
You cannot specialize std::iterator_traits<IndirectItr<T>> for all T (see also), so you can either pick the first and very reasonable option, or fully specialize for each IndirectItr you intend to use.

How can I write a custom STL iterator for a class type without using raw pointers? Is there a practical advantage to this?

The title of this question used to be: Are there practical advantages to creating an iterator class compared to returning raw pointers from begin and end functions?
Recently I have been working on a code base which uses MFC and objects such as CArray<T, U>.
Some parts of new code which has been written make use of the STL and <algorithm> library.
For example
CArray<int int> carray;
carray // do stuff
std::vector<int> stlvector(begin(carray), end(carray));
stlvector.dostuff() // do stuff
I recently asked a question about creating iterators for a class such as CArray, which I do not have access to.
I now have some further questions about this. The first question can be found here. Here is my second question:
Should the begin and end functions return raw pointers or iterators?
In the linked question above, an example was provided as an answer which returns raw pointers. This answer was very similar to the implementation I used.
template<typename T, typename U>
auto begin(const CArray<T, U> &array>)
{
return &array[0];
}
template<typename T, typename U>
auto end(const CArray<T, U> &array>)
{
return (&array[array.GetCount() - 1]) + 1;
}
These functions return raw pointers. However I attempted to implement an iterator solution. So far I have not been successful.
The main reference which I used during my research can be found here:
https://internalpointers.com/post/writing-custom-iterators-modern-cpp
First attempt
This is the first attempt that I made in finding a solution.
You can play with this code here.
#include <iostream>
#include <iterator>
#include <algorithm>
template <typename U>
class CArrayForwardIt
{
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = U;
using pointer = U*;
using reference = U&;
public:
CArrayForwardIt(pointer ptr)
: m_ptr(ptr)
{
}
// = default?
//CArrayForwardIt(CArrayForwardIt<U> other)
// : m_ptr(ptr)
// {
// }
reference operator*() const
{
return *m_ptr;
}
// what does this do, don't understand why operator-> is needed
// or why it returns a U* type
pointer operator->()
{
return m_ptr;
}
CArrayForwardIt& operator++()
{
++ m_ptr;
return *this;
}
CArrayForwardIt operator++(int)
{
CArrayForwardIt tmp(*this);
++ (*this);
return tmp;
}
friend bool operator==(const CArrayForwardIt& lhs, const CArrayForwardIt& rhs)
{
return lhs.m_ptr == rhs.m_ptr;
}
friend bool operator!=(const CArrayForwardIt& lhs, const CArrayForwardIt& rhs)
{
return !(lhs == rhs);
}
private:
pointer m_ptr;
};
template<typename T, typename U>
auto begin(const CArray<T, U> &array)
{
return CArrayForwardIt<U>(&array[0]);
}
template<typename T, typename U>
auto end(const CArray<T, U> &array)
{
return CArrayForwardIt<U>((&array[array.GetCount() - 1]) + 1);
}
int main()
{
CArray<int, int> c;
// do something to c
std::vector<int> v(begin(c), end(c));
return 0;
}
This is what happens when I try to compile this (with Visual Studio 2019 Pro).
no instance of constructor "std::vector<_Ty, _Alloc>::vector [with _Ty=int, _Alloc=std::allocator<int>]" matches argument list
'<function-style-cast>': cannot convert from 'contt TYPE*' to 'std::CArrayForwardIt<U>'
'std::vector<int, std::allocator<int>>::vector(std::vector<int, std::allocator<int>> &&, const _Alloc &) noexcept(<expr>)': cannot convert from argument 1 from 'void' to 'const unsigned int'
Being more familiar with gcc, I have little knowledge of how to understand this.
Second attempt
I made another two further attempts at this but they were quite similar.
One was to change my class CArrayForwardIt to inherit from iterator<std::forward_iterator_tag, std::ptrdiff_t, U, U*, U&>, and to remove the using... lines at the top of the class. This didn't seem to get me any closer to a solution.
In addition, I looked at the constructor definition for std::vector. See here.
I may be misunderstanding here, but it looks like std::vector requires a InputIt type argument.
Therefore I tried to change my class to be something like this:
#include <iostream>
#include <iterator>
#include <algorithm>
template <typename U>
class forward_iterator
{
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = U;
using pointer = U*;
using reference = U&;
public:
forward_iterator(pointer ptr)
: m_ptr(ptr)
{
}
// = default?
//forward_iterator(forward_iterator<U> other)
// : m_ptr(ptr)
// {
// }
reference operator*() const
{
return *m_ptr;
}
// what does this do, don't understand why operator-> is needed
// or why it returns a U* type
pointer operator->()
{
return m_ptr;
}
forward_iterator& operator++()
{
++ m_ptr;
return *this;
}
forward_iterator operator++(int)
{
forward_iterator tmp(*this);
++ (*this);
return tmp;
}
friend bool operator==(const forward_iterator& lhs, const forward_iterator& rhs)
{
return lhs.m_ptr == rhs.m_ptr;
}
friend bool operator!=(const forward_iterator& lhs, const forward_iterator& rhs)
{
return !(lhs == rhs);
}
private:
pointer m_ptr;
};
template<typename T, typename U>
auto begin(const CArray<T, U> &array)
{
return forward_iterator<U>(&array[0]);
}
template<typename T, typename U>
auto end(const CArray<T, U> &array)
{
return forward_iterator<U>((&array[array.GetCount() - 1]) + 1);
}
int main()
{
CArray<int, int> c;
// do something to c
std::vector<int> v(begin(c), end(c));
return 0;
}
This, perhaps unsurprisingly, did not compile either. At this point I became confused. std::vector appears to demand an InputIt type, which forward_iterator should work for, but it doesn't seem to make sense to redefine what forward_iterator is, even if I write this class outside of namespace std.
Question
I am fairly sure there should be a way to write an iterator class for the MFC CArray, which can be returned by begin and end functions. However, I am confused as to how to do this.
Further to the question of writing a working solution, I am beginning to wonder if there are any practical advantages to doing this? Does what I am trying to do even make sense? The raw pointer solution clearly works, so are there any advantages of investing the effort to write an iterator based solution? Can iterator solutions provide more sophisticated bounds checking, for example?
Since I managed to get this working I wanted to post a solution, hopefully I don't make too many errors transcribing it.
One thing that was not shown in the above code snippet is the fact that all these class and function definitions existed inside of namespace std. I posted another question about this earlier, and was informed that these things should not be inside namespace std. Correcting this seems to have resolved some problems and made the solution a step closer.
You can find that question here.
This is what I have so far: This is how to write an iterator for an external class which the programmer does not have access to. It also works for your own custom types or containers which you do have access to.
// How to write an STL iterator in C++
// this example is specific to the MFC CArray<T, U> type, but
// it can be modified to work for any type, note that the
// templates will need to be changed for other containers
#include <iterator>
#include <mfc stuff...>
template<typename T, typename U>
class CArrayForwardIt : public std::iterator<std::forward_iterator_tag, std::ptrdiff_t, U, U*, U&>
{
// the names used in this class are described in this list
// using iterator_category = std::forward_iterator_tag;
// using difference_type = std::ptrdiff_t;
// using value_type = U;
// using pointer = U*;
// using reference = U&;
public:
CArrayForwardIt(CArray<T, U> &array_ref, const std::size_t index)
: m_array_ref(array_ref)
, m_index(index)
{
}
// the only way I could get this to work was to make the return type
// an explicit U&, I don't know why this is required, as using
// reference operator*() const did not seem to work
U& operator*() const
{
if(m_index < m_array_ref.GetCount())
{
return m_array_ref[m_index];
}
else
{
throw std::out_of_range("Out of range Exception!");
}
}
CArrayForwardIt& operator++()
{
++ m_index;
return *this;
}
CArrayForwardIt operator++(int)
{
CForwardArrayIt tmp(*this);
++(*this);
}
friend bool operator==(const CArrayForwardIt& lhs, const CArrayForwardIt& rhs)
{
if(&(lhs.m_array_ref) == &(rhs.m_array_ref))
{
return lhs.m_index == rhs.m_index;
}
return false;
}
friend bool operator!=(const CArrayForwardIt& lhs, const CArrayForwardIt& rhs)
{
return !(lhs == rhs);
}
private:
std::size_t m_index;
CArray<T, U> &m_array_ref;
};
template<typename T, typename U>
auto begin(CArray<T, U> &array)
{
return CArrayForwardIt<T, U>(array, 0);
}
template<typename T, typename U>
auto end(CArray<T, U> &array)
{
return CArrayForwardIt<T, U>(array, array.GetCount());
}
int main()
{
CArray<int, int> array;
// do stuff to array
// construct vector from elements of array in one line
std::vector<int> vector(begin(array), end(array));
// also works with other STL algorithms
}
Note my comment about the U& operator* which produced some compiler error when written as reference operator* which might be a Visual Studio compiler bug. I'm not sure about this.
I would suggest that although this method is more difficult to implement (but not much when you know how to do it) it has the advantage of not using raw pointers which means that the iterator functions can provide proper exception throwing statements when illegal operations are attempted. For example, incrementing the iterator when it is already at the end.
Useful references:
https://lorenzotoso.wordpress.com/2016/01/13/defining-a-custom-iterator-in-c/
https://internalpointers.com/post/writing-custom-iterators-modern-cpp
For completeness, here is the simpler solution using raw pointers.
template<typename T, typename U>
auto begin(CArray<T, U> &array)
{
return &(array[0]);
}
template<typename T, typename U>
auto end(CArray<T, U> &array)
{
// get address of last element then increment
// pointer by 1 such that it points to a memory
// address beyond the last element. only works for
// storage containers where higher index elements
// are guaranteed to be at higher value memory
// addresses
if(array.GetCount() > 0)
{
return &(array[array.GetCount() - 1]) + 1;
}
else
{
return &(array[0]) + 1;
}
}
You can use these in the same way as demonstrated in the other answer, however there is also a way to use STL vector without the begin and end functions:
CArray<int, int> array; // ... do something to array
std::vector<int> vec(&array[0], &(array[array.GetCount() - 1]) + 1);
// note only works if elements guaranteed to be in continuous
// packed memory locations
but it also works with begin and end which is nicer
std::vector<int> vec(begin(array), end(array));

Having problem with implementing a chain class (chaining two containers)

I wrote a 'range-class' (get as input start and end index) and output all elements between them (int, char, double etc.)
I have a problem implementing the 'chain-class', chain takes two containers (a range class or a string) and chain it.The problem is that the begin and end class doesn't recognize the inner class type.
Range class implementation:
template<typename T>
struct RangeClass{
T start, last;
RangeClass(T first, T last): start(first), last(last) { };
struct iterator{ //inner class
T it;
T operator* (){ return it;
iterator& operator++() { ++it; return *this; }
bool operator== (const iterator& other){ return it==other.it; }
bool operator!= (const iterator& other){ return it!=other.it; }
}; // end Iterator
iterator end() const{ return iterator{last}
iterator begin() const{ return iterator{start};}
}; // end RangeClass
template<typename T>
RangeClass<T> range(T first, T last){
return RangeClass(first, last);
}
The ChainClass is:
template <typename T, typename V>
struct ChainRanges{
T t1;
V v1;
ChainRanges (const T& first, const V& second):
t1(first), v1(second)
{}
template<typename Y>
struct iterator{
Y var;
Y operator* (){ return var; }
iterator& operator++(){//right? can access outer class fields?
if(var==*it.end())
var=*v1.begin();
else
++var;
bool operator== (const iterator& other){ return var==other.var; }
bool operator!= (const oiterator& other){ return var!=other.var; }
}; // end Iterator
auto end() const {
//auto? or iterator<Y>
auto var = ( *(v1.end()) ) ;
return iterator{var};
}
auto begin() const{
auto var = ( *(t1.begin()) ) ;
return iterator{var};
}
}; // end struct chain
template<typename T, typename V>
ChainRanges<T,V >
chain(T first, V second)
{
return ChainRanges(first, second);
}
Main is :
for (int i: range(5,9))
cout << i; //prints 5678
for (char i: chain(range('a','e'), string("hello")))
cout << i;//want this to work = // abcdhello
No you cannot access outer class fields. Subclasses have friend-style access, but no state access, to their outer class. Of you want a pointer to their outer class you have to add one.
Values have fixed types in C++. You cannot have an.iterator value of two.possible types.
You can have a pointer to two possible types if they share a basr class. You can use type erasure to wrap multiple types into one variable. You can use std variant, std any, void pointers and careful manual vtables, concept and model templates wrapped by regular type abstractions, or a myriad of other ways to have "values.of more than one type"; they aren't really values of more than one type, just polymorphic behaviour values that include some type aware dispatching code.
Templates are not inheritly runtime polymorphic in C++, unlike Generics in Java/C#. Templates generate types and functions. The generated types and functions are unrelated at runtime, unless of course you make them related.
There are piles of ways to solve your problem, from boost any iterator to fun type manual type erasing the concept of iterable and printable, or print-iterable even. I know of none that you'd be wise to try at your level of C++ expertise.

Combining C++ standard algorithms by looping only once

I currently have this code up and running:
string word="test,";
string::iterator it = word.begin();
for (; it != word.end(); it++)
{
if (!isalpha(*it)) {
break;
}
else {
*it = toupper(*it);
}
}
word.erase(it, word.end());
// word should now be: TEST
I would like to make it more compact and readable it by:
Composing existing standard C++ algorithms (*)
Perform the loop only once
(*) I'm assuming that combining existing algorithms makes my code more readable...
An alternative solution
In addition to defining a custom transform_until algorithm, as suggested by jrok, it might be possible to define a custom iterator adaptor that would iterate using the underlying iterator but redefine operator*() by modifying the underlying reference before returning it.
Something like that:
template <typename Iterator, typename UnaryFunction = typename Iterator::value_type (*)(typename Iterator::value_type)>
class sidefx_iterator: public std::iterator<
typename std::forward_iterator_tag,
typename std::iterator_traits<Iterator>::value_type,
typename std::iterator_traits<Iterator>::difference_type,
typename std::iterator_traits<Iterator>::pointer,
typename std::iterator_traits<Iterator>::reference >
{
public:
explicit sidefx_iterator(Iterator x, UnaryFunction fx) : current_(x), fx_(fx) {}
typename Iterator::reference operator*() const { *current_ = fx_(*current_); return *current_; }
typename Iterator::pointer operator->() const { return current_.operator->(); }
Iterator& operator++() { return ++current_; }
Iterator& operator++(int) { return current_++; }
bool operator==(const sidefx_iterator<Iterator>& other) const { return current_ == other.current_; }
bool operator==(const Iterator& other) const { return current_ == other; }
bool operator!=(const sidefx_iterator<Iterator>& other) const { return current_ != other.current_; }
bool operator!=(const Iterator& other) const { return current_ != other; }
operator Iterator() const { return current_; }
private:
Iterator current_;
UnaryFunction fx_;
};
Of course this is still very raw, but it should give the idea.
With the above adaptor, I could then write the following:
word.erase(std::find_if(it, it_end, std::not1(std::ref(::isalpha))), word.end());
with the following defined in advance (which could be simplified by some template-magic):
using TransformIterator = sidefx_iterator<typename std::string::iterator>;
TransformIterator it(word.begin(), reinterpret_cast<typename std::string::value_type(*)(typename std::string::value_type)>(static_cast<int(*)(int)>(std::toupper)));
TransformIterator it_end(word.end(), nullptr);
If the standard would include such an adaptor I would use it, because it would mean that it was flawless, but since this is not the case I'll probably keep my loop as it is.
Such an adaptor would allow to reuse existing algorithms and mixing them in different ways not possible today, but it might have downsides as well, which I'm likely overlooking at the moment...
I don't think there's a clean way to do this with a single standard algorithm. None that I know of takes a predicate (you need one to decide when to break early) and allows to modify the elements of the source sequence.
You can write your own generic algorithm if you really want to do it "standard" way. Let's call it, hmm, transform_until:
#include <cctype>
#include <string>
#include <iostream>
template<typename InputIt, typename OutputIt,
typename UnaryPredicate, typename UnaryOperation>
OutputIt transform_until(InputIt first, InputIt last, OutputIt out,
UnaryPredicate p, UnaryOperation op)
{
while (first != last && !p(*first)) {
*out = op(*first);
++first;
++out;
}
return first;
}
int main()
{
std::string word = "test,";
auto it =
transform_until(word.begin(), word.end(), word.begin(),
[](char c) { return !::isalpha(static_cast<unsigned char>(c)); },
[](char c) { return ::toupper(static_cast<unsigned char>(c)); });
word.erase(it, word.end());
std::cout << word << '.';
}
It's debatable whether this is any better than what you have :) Sometimes a plain for loop is best.
After better understanding your question, I have got an idea that might work, but requires Boost.
You could use a transform_iterator which calls toupper on all characters and use that as the inputiterator to find_if or remove_if. I am not familiar enough with Boost to provide an example though.
As #jrok points out, the transform_iterator will only transform the value during iteration and not actually modify the original container. To get around this, instead of operating on the same sequence, you would want to copy to a new one, using something like remove_copy_if. This copies as long as the predicate is NOT true, so std::not1 would be needed. This would replace the remove_if case.
Use std::copy to copy until the iterator returned by std::find_if to get the other case to work.
Finally, if your output string is empty, it will need a std::inserter type of iterator for the output.

Chaining of ordering predicates (e.g. for std::sort)

You can pass a function pointer, function object (or boost lambda) to std::sort to define a strict weak ordering of the elements of the container you want sorted.
However, sometimes (enough that I've hit this several times), you want to be able to chain "primitive" comparisons.
A trivial example would be if you were sorting a collection of objects that represent contact data. Sometimes you will want to sort by last name, first name, area code. Other times first name, last name - yet other times age, first name, area code... etc
Now, you can certainly write an additional function object for each case, but that violates the DRY principle - especially if each comparison is less trivial.
It seems like you should be able to write a hierarchy of comparison functions - the low level ones do the single, primitive, comparisons (e.g. first name < first name), then higher level ones call the lower level ones in succession (probably chaining with && to make use of short circuit evaluation) to generate the composite functions.
The trouble with this approach is that std::sort takes a binary predicate - the predicate can only return a bool. So if you're composing them you can't tell if a "false" indicates equality or greater than. You can make your lower level predicates return an int, with three states - but then you would have to wrap those in higher level predicates before they could be used with std::sort on their own.
In all, these are not insurmountable problems. It just seems harder than it should be - and certainly invites a helper library implementation.
Therefore, does anyone know of any pre-existing library (esp. if it's a std or boost library) that can help here - of have any other thoughts on the matter?
[Update]
As mentioned in some of the comments - I've gone ahead and written my own implementation of a class to manage this. It's fairly minimal, and probably has some issues with it in general. but on that basis, for anyone interested, the class is here:
http://pastebin.com/f52a85e4f
And some helper functions (to avoid the need to specify template args) is here:
http://pastebin.com/fa03d66e
You could build a little chaining system like so:
struct Type {
string first, last;
int age;
};
struct CmpFirst {
bool operator () (const Type& lhs, const Type& rhs) { return lhs.first < rhs.first; }
};
struct CmpLast {
bool operator () (const Type& lhs, const Type& rhs) { return lhs.last < rhs.last; }
};
struct CmpAge {
bool operator () (const Type& lhs, const Type& rhs) { return lhs.age < rhs.age; }
};
template <typename First, typename Second>
struct Chain {
Chain(const First& f_, const Second& s_): f(f_), s(s_) {}
bool operator () (const Type& lhs, const Type& rhs) {
if(f(lhs, rhs))
return true;
if(f(rhs, lhs))
return false;
return s(lhs, rhs);
}
template <typename Next>
Chain <Chain, Next> chain(const Next& next) const {
return Chain <Chain, Next> (*this, next);
}
First f;
Second s;
};
struct False { bool operator() (const Type& lhs, const Type& rhs) { return false; } };
template <typename Op>
Chain <False, Op> make_chain(const Op& op) { return Chain <False, Op> (False(), op); }
Then to use it:
vector <Type> v; // fill this baby up
sort(v.begin(), v.end(), make_chain(CmpLast()).chain(CmpFirst()).chain(CmpAge()));
The last line is a little verbose, but I think it's clear what's intended.
One conventional way to handle this is to sort in multiple passes and use a stable sort. Notice that std::sort is generally not stable. However, there’s std::stable_sort.
That said, I would write a wrapper around functors that return a tristate (representing less, equals, greater).
You can try this:
Usage:
struct Citizen {
std::wstring iFirstName;
std::wstring iLastName;
};
ChainComparer<Citizen> cmp;
cmp.Chain<std::less>( boost::bind( &Citizen::iLastName, _1 ) );
cmp.Chain<std::less>( boost::bind( &Citizen::iFirstName, _1 ) );
std::vector<Citizen> vec;
std::sort( vec.begin(), vec.end(), cmp );
Implementation:
template <typename T>
class ChainComparer {
public:
typedef boost::function<bool(const T&, const T&)> TComparator;
typedef TComparator EqualComparator;
typedef TComparator CustomComparator;
template <template <typename> class TComparer, typename TValueGetter>
void Chain( const TValueGetter& getter ) {
iComparers.push_back( std::make_pair(
boost::bind( getter, _1 ) == boost::bind( getter, _2 ),
boost::bind( TComparer<TValueGetter::result_type>(), boost::bind( getter, _1 ), boost::bind( getter, _2 ) )
) );
}
bool operator()( const T& lhs, const T& rhs ) {
BOOST_FOREACH( const auto& comparer, iComparers ) {
if( !comparer.first( lhs, rhs ) ) {
return comparer.second( lhs, rhs );
}
}
return false;
}
private:
std::vector<std::pair<EqualComparator, CustomComparator>> iComparers;
};
std::sort is not guaranteed to be stable because stable sorts are usually slower than non-stable ones ... so using a stable sort multiple times looks like a recipe for performance trouble...
And yes it's really a shame that sort ask for a predicate:
I see no other way than create a functor accepting a vector of tristate functions ...
The chaining solution is verbose. You could also use boost::bind in conjunction with std::logical_and to build your sorting predicate. See the linked article for more information: How the boost bind library can improve your C++ programs
Variadic templates in C++ 11 give a shorter option:
#include <iostream>
using namespace std;
struct vec { int x,y,z; };
struct CmpX {
bool operator() (const vec& lhs, const vec& rhs) const
{ return lhs.x < rhs.x; }
};
struct CmpY {
bool operator() (const vec& lhs, const vec& rhs) const
{ return lhs.y < rhs.y; }
};
struct CmpZ {
bool operator() (const vec& lhs, const vec& rhs) const
{ return lhs.z < rhs.z; }
};
template <typename T>
bool chained(const T &, const T &) {
return false;
}
template <typename CMP, typename T, typename ...P>
bool chained(const T &t1, const T &t2, const CMP &c, P...p) {
if (c(t1,t2)) { return true; }
if (c(t2,t1)) { return false; }
else { return chained(t1, t2, p...); }
}
int main(int argc, char **argv) {
vec x = { 1,2,3 }, y = { 2,2,3 }, z = { 1,3,3 };
cout << chained(x,x,CmpX(),CmpY(),CmpZ()) << endl;
return 0;
}