I think I'd leave the responsibility with the loop, but instead of a loop with the logic in its body, I'd write an algorithm that made the intent a little more explicit:
template <class InIter, class Cond, class Action>
void for_each_if(InIter b, InIter e, Action action, Cond cond) {
while (b != e) {
if (cond(*b))
action(*b);
++b;
}
}
For a testing, I did a mock implementation of Displayable
:
struct Displayable {
bool showing;
std::string label;
Displayable(bool showing, std::string label) : showing(showing), label(label) {}
bool isShowing() const { return showing; }
void draw() const { std::cout << label; }
};
To exercise it, I wrote a simple main like:
int main(void){
std::vector<Displayable> d{ { true, "Showing" }, { false, "not showing" } };
for_each_if(d.begin(), d.end(),
[](Displayable const &d) { d.draw(); },
[](Displayable const &d){return d.isShowing(); });
}
Although it's even more severe overkill, and doesn't fit the scenario quite as portrayed above, there's another possibility that might be worth considering under the right circumstances. First, I'll assume that in reality, you specify some surface
when you do drawing. This would be a class that represents a device context on Windows, a graphics context on iOS, etc.
For the moment, I'll define a really trivial "surface" that just acts as a wrapper for an ostream:
struct surface {
std::ostream &os;
public:
surface(std::ostream &os) : os(os) {}
void draw(std::string const &s) { os << s; }
};
Using that, we can define an ostream-like interface to a surface:
struct Displayable {
bool showing;
std::string label;
Displayable(bool showing, std::string label) : showing(showing), label(label) {}
bool isShowing() const { return showing; }
friend surface &operator<<(surface &s, Displayable const &d) {
s.draw(d.label);
return s;
}
};
Then we get to a somewhat ugly part: defining an iterator to write Displayable
s to a surface
. This is quite a bit longer than we'd like, but most of it is pure boiler-plate. The only part that really matters at all is the assignment operator, which writes a Displayable
to a surface
:
template <class T, class charT = char, class traits = std::char_traits<charT>>
struct draw_iterator : public std::iterator<std::output_iterator_tag, void, void, void, void> {
surface *os;
public:
draw_iterator(surface& s)
: os(&s)
{}
draw_iterator<T, charT, traits>& operator=(T const &item)
{
*os << item;
return *this;
}
draw_iterator<T, charT, traits> &operator*() {
return *this;
}
draw_iterator<T, charT, traits> &operator++() {
return *this;
}
draw_iterator<T, charT, traits> &operator++(int) {
return *this;
}
};
With those in place, we can then use a standard algorithm to write the chosen Displayable
objects to the surface
:
int main(void){
std::vector<Displayable> d{ { true, "Showing" }, { false, "not showing" } };
surface s(std::cout);
std::copy_if(d.begin(), d.end(),
draw_iterator<Displayable>(s),
[](Displayable const &d){return d.isShowing(); });
}
Of course, the "surface" I've defined is extremely limited (only knows how to display strings right now), but it's intended purely as a mock-up. As I already said, in a real implementation you probably already have something that corresponds to it, so you wouldn't typically be implementing that part at all, just setting up the stream-like interface, and the iterator for it.
The iterator, in particular is more extra work than you'd typically like. The good point, however, is that when you're done it acts like any other iterator--it'll support all the standard algorithms, not just this particular one.
language-agnostic
tag \$\endgroup\$return
early statement is better to even be in the method call in question. The other option is to check the condition before even invoking the function. \$\endgroup\$