else if question

Started by
12 comments, last by Khatharr 10 years, 11 months ago

First of all, I am reading a book on C++ - there was this one example in it:


#include <iostream>
using namespace std;

class Date
{
private:
	int day, month, year;
public:
	Date(int inputDay, int inputMonth, int inputYear)
		: day(inputDay), month(inputMonth), year(inputYear){}


	bool operator== (const Date& input)
	{
		if( (day == input.day) && (month == input.month) && (year == input.year) )
			return true;
		else
			return false;
	}

	bool operator< (const Date& input)
	{
		if(year < input.year)
			return true;
		else if(month < input.month)
			return true;
		else if(day < input.day)
			return true;
		else 
			return false;
	}

	bool operator<= (const Date& input)
	{
		if(this->operator== (input))
			return true;
		else 
			return this->operator< (input);
	}

};

int main()
{
	Date d1(19 , 4, 2013);
	Date d2(19, 5, 2012);
	cout << (d1<d2) << endl;

	cin.ignore();
	return 0;
}

Btw it's me that changed the main a little - just to show what a ridiculous output I get.

Is it me or is the inequality operator coded wrong?


	bool operator< (const Date& input)
	{
		if(year < input.year)
			return true;
		else if(month < input.month)
			return true;
		else if(day < input.day)
			return true;
		else 
			return false;
	}

If it were me I'd code it like this:


	bool operator< (const Date& input)
	{
		if(year < input.year)
			return true;
		else if(year > input.year)
			return false;
		else //when year == input.year
		{
			if(month < input.month)
				return true;
			else if(month > input.month)
				return false;
			else //when month == input.month
			{
				if(day < input.day)
					return true;
				else 
					return false;
			}
		}
	}

Is it the book's example that is wrong or is it me missing something crucial?

Advertisement
Nope, you're perfectly right. Whenever you compare sequences you only go deeper if the current elements are equal.

PS: Check if the book has an (online) errata and/or if you're using the latest edition.

What book is that? The "if (…) return true; else return false;" looks a bit, ehm, naive too...

I usually use something like the following pattern for less-than:


bool operator< (const Date& input) const
{
    int diff = year - input.year;
    if (diff == 0) diff = month - input.month;
    if (diff == 0) diff = day - input.day;
    return diff < 0;
}

openwar - the real-time tactical war-game platform

This book:

http://www.amazon.com/Sams-Teach-Yourself-One-Hour/dp/0672335670

I don't think it's the best one out there but I like it.

The "if (…) return true; else return false;" looks a bit, ehm, naive too...

Idk about that, you allocate an additional variable of type int on the stack, perform 3 subtractions and assignments and all in all the code doesn't look straight-forward(to me at least - especially if you consider what's the goal of the operator) - probably just a matter of taste.

Another option to improve readability is to skip the elses and curlies.


bool operator<(const Date& input) const
{
    if (year < input.year) return true;
    if (year > input.year) return false;
    if (month < input.month) return true;
    if (month > input.month) return false;
    if (day < input.day) return true;
    return false;
}

An advantage of the diff approach is that it easily can be used with strings and other data structures.


if (diff == 0) diff = strcmp(title, input.title); 

You can also write a strcmp-like function to use as a primitive for the operators.


int date_cmp(const Date& a, const Date& b) { … }

bool operator<(const Date& input) const { return date_cmp(*this, input) < 0; }
bool operator>(const Date& input) const { return date_cmp(*this, input) > 0; }
bool operator<=(const Date& input) const { return date_cmp(*this, input) <= 0; }
bool operator>=(const Date& input) const { return date_cmp(*this, input) >= 0; }
bool operator==(const Date& input) const { return date_cmp(*this, input) == 0; }
bool operator!=(const Date& input) const { return date_cmp(*this, input) != 0; }

openwar - the real-time tactical war-game platform

I get what you mean - that's why I said it's probably a matter of taste.

Concerning the elses and curlies - I'm not really sure which's better - using a return as a break or having a few additional curlies and elses.

I consider cutting off a function with just a return (or doing the same in a loop with break) "bad", I think that a few elses and braces promote readability over a return or a break.

This example here is trivial:


bool operator<(const Date& input) const
{
    if (year < input.year) return true;
    if (year > input.year) return false;
    if (month < input.month) return true; // the programmer has to take into account that year and input.year were evaluated
    // if you move month before year you'd get a problem
    if (month > input.month) return false;
    if (day < input.day) return true;
    return false;
}

Still I can't perceive this as good practice - of course it works - however (for me at least) this is sure to be more confusing than a code with a few additional elses and braces.

As I said it's a matter of taste - building the logical flow in a function around returns may cause me trouble in a bigger and more complex project(I'd have a big issue if I have to find a bug and I've based the logical flow inside my functions around return type breaks).

About your 2nd point - I guess it's good, in the end it all depends on what goals you have for your function/class though.

I get what you mean - that's why I said it's probably a matter of taste.

Concerning the elses and curlies - I'm not really sure which's better - using a return as a break or having a few additional curlies and elses.

I consider cutting off a function with just a return (or doing the same in a loop with break) "bad", I think that a few elses and braces promote readability over a return or a break.

This example here is trivial:


bool operator<(const Date& input) const
{
    if (year < input.year) return true;
    if (year > input.year) return false;
    if (month < input.month) return true; // the programmer has to take into account that year and input.year were evaluated
    // if you move month before year you'd get a problem
    if (month > input.month) return false;
    if (day < input.day) return true;
    return false;
}

Still I can't perceive this as good practice - of course it works - however (for me at least) this is sure to be more confusing than a code with a few additional elses and braces.

As I said it's a matter of taste - building the logical flow in a function around returns may cause me trouble in a bigger and more complex project(I'd have a big issue if I have to find a bug and I've based the logical flow inside my functions around return type breaks).

About your 2nd point - I guess it's good, in the end it all depends on what goals you have for your function/class though.

If it were up to me I would've written it like this:


bool operator<(const Date& input) const
{
  if (year != input.year) return (year < input.year);
  if (month != input.month) return (month < input.month);
  if (day != input.day) return (day < input.day);
  return false; // same date
 }

The control flow is clear and concise - check each date field in order of significance, return with the correct comparison if they differ. If the code makes it to the last line, the two dates are equal and hence we return false (as we're overloading the strictly less operator). This might look better in a general purpose date comparison function, though.

Early return is a pattern I enjoy very much as it simplifies assertions I need to keep track of throughout my code. Trivial edge cases can be dispatched quickly instead of endlessly dragging them along the entire function block and writing redundant conditions everywhere. Of course arcane code is bad, but when it's easy to read, is a well-understood idiom, or is in general intuitive, there is no problem.

The main issue I have with your "return else" concept is that while it is syntactically consistent, it doesn't add any value to the code except an extra keyword, scope and indentation level. Unless the code inside that "if" condition is freakishly complicated (which it shouldn't and isn't here) it doesn't take a genius to see that anything entering that block will return, hence the "else" is redundant and is just there to eat up horizontal space. And as a real life analogy, would you tell someone this?


Please go buy food, then, when you have bought the food, make dinner. Or, if there was no food at the store, don't make dinner.

No, that is a horribly redundant sentence. What you would say is:


Please go buy food and then make dinner.

There's no need to specify that you need to actually buy the food first before making dinner. Nor is there a need to explicitly tell whoever you're talking to that he shouldn't make dinner if he couldn't buy any food, since he can't. It's self-evident. Just as it is in the comparison code. There is no need to keep on comparing the date if the years differ. You're done. There isn't even a need to check what the rest of the function is like. Remember an important programming adage: DRY. Don't Repeat Yourself. That applies to code as well as documentation.

That said this stuff comes with experience in writing code and you will eventually develop your own style that you find elegant, effective and productive. But just because it doesn't look intuitive to you now doesn't mean it's bad. However, it's good that you're asking questions and discussing why things are the way they are, it shows that you reflect on the code you write. I think we were all puzzled by some code at some point in time (and everyone is always in a perpetual state of learning when it comes to programming anyway, even if they won't admit it)

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

Good questions.

The problem with multiple returns and breaks is usually not that there are multiple exit points, but that the code is so complex that those exit points are hard to see. If the code is simple enough you'll easily see all the dependencies in it. Also, evaluation order usually matters (more often than the other way around).

But you're right that much of readability is a matter of taste. It's easier to read what you are used to read.

Btw, wouldn't the single-return version be something like this: (Not sure I prefer this one though.)


bool operator<(const Date& input) const
{
     return year < input.year || (year == input.year && (month < input.month || (month == input.month && day < input.day)));
}

openwar - the real-time tactical war-game platform

Thanks guys!

I won't avoid so much organizing my program flow around return/breaks now.

I still think that this is valid:

building the logical flow in a function around returns may cause me trouble in a bigger and more complex project(I'd have a big issue if I have to find a bug and I've based the logical flow inside my functions around return type breaks).

But I guess it really depends on what is "complex code" - this of course was a trivial example so maybe it really wouldn't hurt organizing things with return.

As you said:

this stuff comes with experience in writing code

I just don't want to end up with some bad habits/style when programming - because as I've come to see - different books/tutorials promote different style of coding and I guess it's all about getting the best out of each book/tutorial, while ignoring the "bad practices". That's why a forum as this one is especially helpful in such situations.

So thanks once again guys - I really appreciate it! smile.png

My own style is very similar to Bacterius's:

bool operator<(const Date& input) const {
  if (year != input.year)
    return year < input.year;

  if (month != input.month)
    return month < input.month;

  return day < input.day;
}

This topic is closed to new replies.

Advertisement