43

My teacher has assigned a program to use both if-else statements and switch statements, so we understand how to implement both. The program asked us to prompt the user to input their weight and height in pounds and meters respectively. This is my attempt:

Without the switch

#include "stdafx.h"
#include <iostream>

using namespace std;


int main()
{
    double height, weight, BMI, heightMeters, weightKilo;
    const double KILOGRAMS_PER_POUND = 0.45359237;
    const double METERS_PER_INCH = 0.0245;

    cout << "Please enter your height (inches) and weight (pounds)" << endl;
    cin >> height >> weight;

    weightKilo = weight*KILOGRAMS_PER_POUND;
    heightMeters = height*METERS_PER_INCH;
    BMI = weightKilo / (heightMeters*heightMeters);

    if (BMI < 18.5) {
        cout << "You are underweight " << endl;
    }
    else if (BMI >= 18.5 && BMI < 25.0) {
        cout << "You are normal" << endl;
    }
    else if (BMI >= 25.0 && BMI < 30.0) {
        cout << "You are overweight" << endl;
    }
    else if (BMI >= 30.0 && BMI < 35) {
        cout << "You are obese" << endl;
    }
    else {
        cout << "You are gravely overweight" << endl;
    }
}

With the switch

#include "stdafx.h"
#include <iostream>

using namespace std;


int main()
{
    double height, weight, heightMeters, weightKilo;
    int BMI, q;
    const double KILOGRAMS_PER_POUND = 0.45359237;
    const double METERS_PER_INCH = 0.0245;

    cout << "Please enter your height (inches) and weight (pounds)" << endl;
    cin >> height >> weight;

    weightKilo = weight*KILOGRAMS_PER_POUND;
    heightMeters = height*METERS_PER_INCH;
    BMI = weightKilo / (heightMeters*heightMeters);

    if (BMI < 18.5) {
        q = 1;
    }
    else if (BMI >= 18.5 && BMI < 25.0) {
        q = 2;
    }
    else if (BMI >= 25.0 && BMI < 30.0) {
        q = 3;
    }
    else if (BMI >= 30.0 && BMI < 35) {
        q = 4;
    }
    else {
        q = 5;
    }

    switch (q) {
        case 1: cout << "You are underweight" << endl; break;
        case 2: cout << "You are a normal weight " << endl; break;
        case 3: cout << "You are overweight" << endl; break;
        case 4: cout << "You are obese" << endl; break;
        case 5: cout << "You are gravely overweight" << endl; break;
    }
}

This was the way I thought of, to include a switch statement. Is there some way to implement the first code block to just a switch statement?

I was almost certain that it could not be done to use ranges nor to use doubles (18.5). I emailed my teacher and they gave me an answer along the lines of

It may not make sense to you, but sometimes you are going to have to write a program that does not make sense. I am not saying that you don't have legitimate questions, but if anyone can figure it out you can. But then, maybe it can't be figured out. That's the challenge".

So, I'm asking: Is there some method to just use a switch statement for the first code block, or is what I did the best way to use a switch statement in the code, even if it is in no way necessary?

13
  • 3
    You can't use switches with doubles.
    – robert
    Oct 9, 2015 at 12:12
  • 2
    Unrelated: the conversion from inches to meters is 0.0254 m/in. Also it is very nice to see named conversion factors in your code. I can't tell you how many "magic numbers" I come across in legacy code where I can't figure out what the heck the number means.
    – Carlton
    Oct 9, 2015 at 12:16
  • 6
    "It may not make sense to you but sometimes you are going to have to write a program that does not make sense." - That a strange thing to tell to a student. Oct 9, 2015 at 12:28
  • 4
    @ChristianHackl Yes it is. I believe it because she doesn't know what she is talking about.
    – TEEBQNE
    Oct 9, 2015 at 12:32
  • 1
    @nocomprende, I strongly disagree. When studying a problem set, the solution may not seem to translate very well to a language like C++ because you have to consider that the C++ language models a very low level machine: pointers and bit types and whatnot. Solutions written in a functional language will closely resemble mathematical models of your problem set, though.
    – sleblanc
    Oct 9, 2015 at 17:37

7 Answers 7

110

As always in C++, favour standard library algorithms. In this case you want to do a range lookup. This is easy with an ordered sequence of boundaries:

double const boundaries[] = { 18.5, 25, 30, 35 };

switch (upper_bound(begin(boundaries), end(boundaries), BMI) - boundaries) {
    case 0: cout << "You are underweight "       << endl; break;
    case 1: cout << "You are normal"             << endl; break;
    case 2: cout << "You are overweight"         << endl; break;
    case 3: cout << "You are obese"              << endl; break;
    case 4: cout << "You are gravely overweight" << endl; break;
};

Actually, I suggest you

See a live demo on Coliru

#include <iostream>
#include <algorithm>

const char* bmi_classification(double bmi) {
    static double const boundaries[] = { 18.5, 25, 30, 35 };

    double const* lookup = std::upper_bound(std::begin(boundaries), std::end(boundaries), bmi);
    switch (lookup - std::begin(boundaries)) {
        case 0: return "underweight";
        case 1: return "normal";
        case 2: return "overweight";
        case 3: return "obese";
        case 4: return "gravely overweight";
    }
    throw std::logic_error("bmi_classification");
}

int main() {
    for (double BMI : { 0.0, 18.4999, 18.5, 24.0, 25.0, 29.0, 30.0, 34.0, 35.0, 999999.0 }) {
        std::cout << "BMI: " << BMI << " You are " << bmi_classification(BMI) << "\n";
    }
}

Prints

BMI: 0 You are underweight
BMI: 18.4999 You are underweight
BMI: 18.5 You are normal
BMI: 24 You are normal
BMI: 25 You are overweight
BMI: 29 You are overweight
BMI: 30 You are obese
BMI: 34 You are obese
BMI: 35 You are gravely overweight
BMI: 999999 You are gravely overweight

BONUS

You can be more elegant without the requirement to use switch:

Live On Coliru

const char* bmi_classification(double bmi) {
    constexpr int N = 5;
    static constexpr std::array<char const*, N> classifications {
        { "underweight", "normal", "overweight", "obese", "gravely overweight" }};
    static constexpr std::array<double, N-1> ubounds {
        { 18.5, 25, 30, 35 }};

    auto lookup = std::upper_bound(std::begin(ubounds), std::end(ubounds), bmi);
    return classifications.at(lookup - std::begin(ubounds));
}
12
  • 30
    A beautiful implementation. The OP will benefit by studying every part, as did I.
    – Bathsheba
    Oct 9, 2015 at 12:35
  • Thanks so much. I guess what i'm confused about is how to get a user input for it. When I remove the for loop, it prints out the incorrect bmi type. Is there another function I can use other then upper_bound, since this is just picking the BMI type one over, since it prints the value over the val. (Ex: if it is normal it prints overweight, underweight it prints normal, etc).
    – TEEBQNE
    Oct 9, 2015 at 13:03
  • Is there a bug in "0 You are normal"? Shouldn't that be underweight? Oct 9, 2015 at 13:04
  • 1
    @P45Imminent Ooops. Yes, removed the extraneous 0 boundary element. Thanks!
    – sehe
    Oct 9, 2015 at 13:07
  • 2
    In your switch statement, why you didn't write : upper_bound(...) - begin(boundaries) It looks to me that it's more generic that way.
    – Pumkko
    Oct 9, 2015 at 13:11
21

Unless you have an absolutely ghastly compiler extension, you can't switch on a range in C++.

But you could use a switch elegantly if you create a std::vector of the BMI ranges:

std::vector<double> v = {18.5, 25.0 /*etc*/}

Then use std::lower_bound along with std::distance to get the position of a given BMI in the above ranges. This is the quantity that you switch on.

You could then go one stage further and define a std::vector<std::string> of the output messages. Then you need neither a switch nor an if block! All the selection logic is delegated to std::lower_bound.

I deliberately haven't given you the full code: I trust these hints are sufficient.

1
  • 1
    Although I don't use gcc case ranges, I feel calling such extensions ghastly is a bit over the top commentary. Oct 15, 2015 at 19:09
8

We need to fit in the input, so, instead of this code:

if (BMI < 18.5) {
        q = 1;
    }
    else if (BMI >= 18.5 && BMI < 25.0) {
        q = 2;
    }
    else if (BMI >= 25.0 && BMI < 30.0) {
        q = 3;
    }
    else if (BMI >= 30.0 && BMI < 35) {
        q = 4;
    }
    else {
        q = 5;
    }

    switch (q) {
    case 1: cout << "You are underweight" << endl; break;
    case 2: cout << "You are a normal weight " << endl; break;
    case 3: cout << "You are overweight" << endl; break;
    case 4: cout << "You are obese" << endl; break;
    case 5: cout << "You are gravely overweight" << endl; break;

    }

You need something like

switch (1 + (BMI >= 18.5) + (BMI >= 25) + (BMI >= 30) + (BMI >= 35)) {
    case 1: cout << "You are underweight" << endl; break;
    case 2: cout << "You are a normal weight " << endl; break;
    case 3: cout << "You are overweight" << endl; break;
    case 4: cout << "You are obese" << endl; break;
    case 5: cout << "You are gravely overweight" << endl; break;
}

The logic is to convert the if-elses into a mathematical formula, returning an int.

7
  • 1
    Difficult to maintain: you have to remember to update the 5 whenever you add a new case.
    – user1804599
    Oct 9, 2015 at 12:41
  • That is true. Generally, there are pros and cons whenever you have a solution. I believe the idea here is valid, but whether it is the best, it is up to debate. Oct 9, 2015 at 12:44
  • it's a nice idea to mathematical calculate an integer from the BMI. In this special problem there is probably no case to add in the future. BMI calculation-formula wont change.
    – Fanax
    Oct 9, 2015 at 14:44
  • 1
    What about switch ((BMI >= 18.5)+(BMI >= 25.0)+(BMI >= 30)+...)? It looks simpler.
    – chi
    Oct 9, 2015 at 15:38
  • 1
    Say no to 1 based indexes! Oct 9, 2015 at 19:43
5

You can not use a double inside a switch. The documentation says:

switch ( expression )
   case constant-expression : statement
   [default   : statement]

The expression must be of an integral type or of a class type for which there is an unambiguous conversion to integral type. Integral promotion is performed as described in Integral Promotions.

On a side note:

There are some compilers (like Clang 3.5.1) which are allowing the case x ... y as an extension to the C++ language. But that too is for an integral datatype. Something like

switch(x){
       case 0:
            cout << "Test1";
            break;
       case 0 ... 9:
            cout << "Test2";
            break;
3

A switch in C++ only allows you to check for the values of integers and chars.

The BMI is a double type, so it's not possible to check its value in a switch.

In your solution with the switch you also should declare the variable BMI as double. If you declare it as integer all decimal results will be casted to an integer, and you will lose the decimal places.

2

You could calculate your case labels dynamically from an array/vector instead of hardcoding an if/else expression:

//#include "stdafx.h"
#include <iostream>

using namespace std;


inline int seg(double d){ //calculate segment for a BMI of d
  constexpr double segs[] = { 18.5, 25, 30, 35 };
  constexpr int n = sizeof(segs)/sizeof(double);
  int r; for(r=0; r<n; r++)
    if(d<segs[r]) return r;
  return r;
}

int main()
{
  double height, weight, heightMeters, weightKilo;
  int BMI, q;
  const double KILOGRAMS_PER_POUND = 0.45359237;
  const double METERS_PER_INCH = 0.0245;

  cout << "Please enter your height (inches) and weight (pounds)" << endl;
  cin >> height >> weight;

  weightKilo = weight*KILOGRAMS_PER_POUND;
  heightMeters = height*METERS_PER_INCH;
  BMI = weightKilo / (heightMeters*heightMeters);



  switch (seg(BMI)) {
    case 0: cout << "You are underweight" << endl; break;
    case 1: cout << "You are a normal weight " << endl; break;
    case 2: cout << "You are overweight" << endl; break;
    case 3: cout << "You are obese" << endl; break;
    case 4: cout << "You are gravely overweight" << endl; break;
  }

}

(You could even make the seg functions constexpr if your really wanted to).

1
  • 2
    +1 for solving problem. -1 for poor code formatting. -1 for re-implementing std::upper_bound in a harder to read way. Oct 9, 2015 at 19:45
1

You can do something like:

switch ((round)BMI)
{
    case 1: case 2: case 3: .... case 15: case 16: case 17: cout<< "You are underweight " << endl; break;
    case 18: ... case 24: cout << "You are normal" << endl; break;
    case 25: ... case 29: cout << "You are overweight" << endl; break;
    case 30: ... case 34: cout << "You are obese" << endl; break;
    default: cout << "You are gravely overweight" << endl;
}

Also I couldn't help but notice this that since you are using if-else you can avoid the first condition in else-if statements like:

if (BMI < 18.5)
{
    cout << "You are underweight " << endl;
}
else if (BMI < 25.0)
{
    cout << "You are normal" << endl;
}
else if (BMI < 30.0)
{
    cout << "You are overweight" << endl;
}
else if(BMI < 35)
{
    cout << "You are obese" << endl;
}
else
{
    cout << "You are gravely overweight" << endl;
}

Apart from this, both of your implementations look good.

2
  • Typing case 34 times for 4 lines of code seems insane to me.
    – Teepeemm
    Oct 9, 2015 at 19:02
  • @Teepeemm Easy! Use macros! Oct 9, 2015 at 19:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.