0

After reading a great answer about the comma operator in C/C++ (What does the comma operator do - and I use the same example code), I wanted to know which is the most readable, maintainable, preferred way to implement a while loop. Specifically a while loop whose condition depends on an operation or calculation, and the condition might be false the first time (if the loop were to always pass at least once then the do-while would work fine).

Is the comma version the most preferred? (how about an answer for each, and the rest can vote by upvoting accordingly?)

Simple Implementation

This code has duplicate statements, that (most likely) must always be the same.

string s;
read_string(s);     // first call to set up the condition
while(s.len() > 5)  // might be false the first pass
{
   //do something
   read_string(s);  // subsequent identical code to update the condition
}

Implementation using break

string s;
while(1)                  // this looks like trouble
{
   read_string(s);
   if(s.len() > 5) break; // hmmm, where else might this loop exit
   //do something
}

Implementation using comma

string s;
while( read_string(s), s.len() > 5 ) 
{
   //do something
}
Community
  • 1
  • 1
Michael
  • 2,118
  • 1
  • 19
  • 25
  • 2
    Use the do-while pattern, executing `read_string` at the *beginning*, testing the condition at the end. – Cody Gray - on strike Mar 11 '16 at 16:27
  • 1
    Both of you forget the "do something", which I guess should be executed after `read_string` and only if the condition passes. – leemes Mar 11 '16 at 16:29
  • @Cody Gray: was I not clear? This does not handle the case if the code in condition was false the first time (and the loop should not enter). – Michael Mar 11 '16 at 16:30
  • 2
    I am not usually a fan of little "tricks" with the comma operator, but in this case, and given the way `read_string` is set up, I would vote for #3. The `while( reading a line succeeds ) { do something with the line }` pattern is a very powerful, readable, natural, and common one, and it's worth sticking with it. However, it would be even more worth rewriting the `read_string` function so that its return value can be used directly. – Steve Summit Mar 11 '16 at 16:30
  • 2
    Voting to reopen. While this does involve opinion to some degree, there's enough factual basis for questions about how to make code like this readable that it's a worthwhile question that can generate meaningful answers. – Jerry Coffin Mar 11 '16 at 16:34
  • Using `for`: `for (read_string(s); s.len() > 5; read_string(s))`. – Jarod42 Mar 11 '16 at 16:36
  • I tend to prefer option 2. I sometimes write a `for()` loop instead, with the `read_string()` analog both in the initialization clause and in the increment clause. I don't recall ever using option 3, but it's not horrible. – John Bollinger Mar 11 '16 at 16:37

2 Answers2

3

I would say none of the above. I see a couple of options. The choice between them depends on your real constraints.

One possibility is that you have a string that should always have some minimum length. If that's the case, you can define a class that embodies that requirement:

template <size_t min>
class MinString{
    std::string data;
public:
    friend std::istream &operator>>(std::istream &is, MinString &m) {
        std::string s;
        read_string(is, s); // rewrite read_string to take an istream & as a parameter
        if (s.length() >= min)
            m.data = s;
        else
            is.setstate(std::ios::failbit);
        return is;
    }

    operator std::string() { return data; }

    // depending on needs, maybe more here such as assignment operator
    // and/or ctor that enforce the same minimum length requirement

};

This leads to code something like this:

Minstring<5> s;
while (infile >> s)
    process(s);

Another possibility is that you have normal strings, but under some circumstances you need to do a read that must be at least 5 characters. In this case the enforcement should be in a function rather than the type.

bool read_string_min(std::string &s, size_t min_len) { 
    read_string(s);
    return s.length() >= min_len;
}

Again, with this the loop can be simple and clean:

while (read_string_min(s, 5))
    process(s);

It's also possible to just write a function that returns the length that was read, and leave enforcement of the minimum length in the while loop:

while (read_string(s) > 5)
    process(s);

Some people like this on the idea that it fits the single responsibilty principle better. IMO, "read a string of at least 5 characters" qualifies perfectly well as a single responsibility, so it strikes me as a weak argument at best though (but even this design still makes it easy to write the code cleanly).

Summary: anything that does input should either implicitly or explicitly provide some way of validating that it read the input correctly. Something that just attempts to read some input but provides no indication of success/failure is simply a poor design (and it's that apparent failure in the design of your read_string that's leading to the problem you've encountered).

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
0

There is a fourth option that seems better to me:

string s;
while( read_string(s) && s.len() > 5 ) 
{
   //do something
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • 1
    @Michael `read_string` should not be a void function. If you are doing a read operation you should return a bool so you can tell if the read succeeded. – NathanOliver Mar 11 '16 at 16:34
  • if `read_string` returns `void`, then `while( read_string(s), s.len() > 5 )` is better. – R Sahu Mar 11 '16 at 16:36
  • read_string() is an example, what I mean is whatever operation that needs to be done to prepare the condition. It might return true, false, 0, 1 or void... – Michael Mar 11 '16 at 16:37
  • 1
    `read_string` should return bytes read, and you're done with `while(read_string(s) > 5)`. Which in my opinion the best design: the entire question exists, because `read_string` is poorly designed IMO. – Bart Friederichs Mar 11 '16 at 16:40
  • @Michael, it's difficult to come up with a generic solution for a hypothetical use case. I thinks it's a better design if the function that does the prep work to returns a status. – R Sahu Mar 11 '16 at 16:41
  • But then the encapsulating function reads something like `ReadStringAndCheckLength()`. It's doing 2 things at once. Is this desired? – Michael Mar 11 '16 at 16:47
  • @Michael, it doesn't have to. It doesn't check the length. It just returns the number of characters read. That's not too much to expect. – R Sahu Mar 11 '16 at 16:50
  • @Michael: with `s = read_string()` (which is a correct interface IMO), your problem is relevant. – Jarod42 Mar 11 '16 at 16:51