Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  1. Composing existing standard C++ algorithms (*)
  2. 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...

like image 500
José Mari Avatar asked Dec 27 '12 12:12

José Mari


1 Answers

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.

like image 88
jrok Avatar answered Nov 07 '22 00:11

jrok