A
Alf P. Steinbach
Note: this article is cross-posted to [comp.lang.c++] and
[comp.programming].
The subject of how to best express a logical "loop-and-a-half" has
popped up in a number of recent [clc++] threads.
The language, C++, is a bit important because in C++ all code must be
prepared for exceptions at any point, so that in this language the
possibility of missed cleanup due to early exit is not an issue.
Assume helpers
void throwX( char const s[] ) { throw std::runtime_error( s ); }
string commandStringFromUser()
{
using namespace std;
string line;
cout << "Command? ";
if( !getline( cin, line ) ) { throwX( "i/o failure" ); }
return line;
}
bool isValidCommandString( string const& s )
{
return (s.length() == 1 && isValidCommandChar( s[1] ));
}
Then, an example of "loop-and-a-half" expressed with exit in the middle:
// Variant 1, exit in middle.
char validUserCommand()
{
for( ;; )
{
std::string const line = commandStringFromUser();
if( isValidCommandString( line ) )
{
return line[1];
}
giveShortHelpAboutValidCommands();
}
}
Example of expressing the loop with redundant duplication of code:
// Variant 2, unrolled.
char validUserCommand()
{
std::string line;
line = commandStringFromUser();
while( !isValidCommandString( line ) )
{
giveShortHelpAboutValidCommands();
line = commandStringFromUser();
}
return line[1];
}
Example of expressing the loop with side-effect in the condition checking:
// Variant 3, side-effect in continuation condition checking.
char validUserCommand()
{
std::string line;
while( !isValidCommandString( line = commandStringFromUser() ) )
{
giveShortHelpAboutValidCommands();
}
return line[1];
}
It has been argued that the last two forms have a loop invariant whereas
the exit-in-middle lacks one, or more specifically, that its loop
invariant isn't established until halfway through the first iteration.
I think that's a bogus argument, and prefer variant 1, exit-in-middle.
Cheers (looking forward to comments! )
- Alf
[comp.programming].
The subject of how to best express a logical "loop-and-a-half" has
popped up in a number of recent [clc++] threads.
The language, C++, is a bit important because in C++ all code must be
prepared for exceptions at any point, so that in this language the
possibility of missed cleanup due to early exit is not an issue.
Assume helpers
void throwX( char const s[] ) { throw std::runtime_error( s ); }
string commandStringFromUser()
{
using namespace std;
string line;
cout << "Command? ";
if( !getline( cin, line ) ) { throwX( "i/o failure" ); }
return line;
}
bool isValidCommandString( string const& s )
{
return (s.length() == 1 && isValidCommandChar( s[1] ));
}
Then, an example of "loop-and-a-half" expressed with exit in the middle:
// Variant 1, exit in middle.
char validUserCommand()
{
for( ;; )
{
std::string const line = commandStringFromUser();
if( isValidCommandString( line ) )
{
return line[1];
}
giveShortHelpAboutValidCommands();
}
}
Example of expressing the loop with redundant duplication of code:
// Variant 2, unrolled.
char validUserCommand()
{
std::string line;
line = commandStringFromUser();
while( !isValidCommandString( line ) )
{
giveShortHelpAboutValidCommands();
line = commandStringFromUser();
}
return line[1];
}
Example of expressing the loop with side-effect in the condition checking:
// Variant 3, side-effect in continuation condition checking.
char validUserCommand()
{
std::string line;
while( !isValidCommandString( line = commandStringFromUser() ) )
{
giveShortHelpAboutValidCommands();
}
return line[1];
}
It has been argued that the last two forms have a loop invariant whereas
the exit-in-middle lacks one, or more specifically, that its loop
invariant isn't established until halfway through the first iteration.
I think that's a bogus argument, and prefer variant 1, exit-in-middle.
Cheers (looking forward to comments! )
- Alf