C++ beginner exercises: grading program I finished the grading exercise from this list of beginner c++ exercises. I have a (probably unfounded) dislike of if elses to dispatch a grade to a note so contrarily to this similar question I implemented a vector of tuples to which I compare the received note. After completion, I realized that it probably amounts to mostly the same thing, if not worse (I am still comparing the value with an if).

I looking for any feedback, but possibly something about:


Is the vector of tuple an unnecessary overhead over the simple if/else?
Is it actually slower, or does compilation optimizes execution?
In this case it probably doesn't matter because the grade set is small, but is this approach considered a good/correct practice if we were to have a bigger set?




#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;tuple&gt;

using namespace std;

char dispatchGrade(int grade) {
    vector&lt;tuple&lt;int, char&gt;&gt; fork {
            tuple&lt;int, char&gt; {90, 'A'},
            tuple&lt;int, char&gt; {80, 'B'},
            tuple&lt;int, char&gt; {70, 'C'},
            tuple&lt;int, char&gt; {60, 'D'},
            tuple&lt;int, char&gt; {0, 'E'},
    };
    for (vector&lt;tuple&lt;int, char&gt;&gt;::size_type i = 0; i != fork.size(); i++) {
        if (grade &gt;= get&lt;0&gt;(fork[i])) {
            return get&lt;1&gt;(fork[i]);
        }
    }
}

int main() {
    int grade;
    char note;
    cout &lt;&lt; "Hello, World! enter your grade:" &lt;&lt; endl;
    cin &gt;&gt; grade;
    if (!cin.fail() &amp;&amp; grade &gt; -1 &amp;&amp; grade &lt; 101) {
        cout &lt;&lt; "That is a valid grade. ";
        note = dispatchGrade(grade);
        cout &lt;&lt; "That grade is good for a " &lt;&lt; note &lt;&lt; endl;
    } else {
        cout &lt;&lt; "That is not a valid grade." &lt;&lt; endl;
    }
}




cmake_minimum_required(VERSION 3.8)
project(grades)

set(CMAKE_CXX_STANDARD 17)

set(SOURCE_FILES main.cpp)
add_executable(grades ${SOURCE_FILES})
Code Review:
grade &gt; -1 &amp;&amp; grade &lt; 101

That's fine for integers. But you have to consider that somebody can come along behind you and modify your types sometime in the future. If I changed the type of grade to float (not an unreasonable change). Then a grade of -.05 becomes valid. I would change this to be:
grade &gt;= 0 &amp;&amp; grade &lt;= 100
!cin.fail()

Checking the state of the file manually is basically never correct (its an anti-pattern). Also this case is also wrong as fail() does not check for end of file, so you could have hit end of file not read anything into grade.
A better check is:
cin.good();

But the more usual check is
cin  // If used in a boolean context (like an if) it will be converted
     // to a boolean based on the call to good.


if (cin &amp;&amp; grade &gt;= 0 &amp;&amp; grade &lt;= 100)

But even this is not usual. Usually you check the result of the read operation by placing the read into the if statement.
if (cin &gt;&gt; grade &amp;&amp; grade &gt;= 0 &amp;&amp; grade &lt;= 100)

Note: the operator&gt;&gt; returns a a reference to the stream, so this is functionally the same as the line above. But it imparts more information.
What you are saying here is: if the read works and the grade is in the range [0..100] we succeeded.
Looking at your dispatch. You have a structure that is the same every time you call.
vector&lt;tuple&lt;int, char&gt;&gt; fork {
        tuple&lt;int, char&gt; {90, 'A'},
        tuple&lt;int, char&gt; {80, 'B'},
        tuple&lt;int, char&gt; {70, 'C'},
        tuple&lt;int, char&gt; {60, 'D'},
        tuple&lt;int, char&gt; {0, 'E'},
};

Since this is never modified it should be marked const. Also since you really only want to build the structure once just mark it static that way you only build the structure on the first call. Also there is a convenience function called make_tuple
static const vector&lt;tuple&lt;int, char&gt;&gt; fork
{
        make_tuple(90, 'A'),
        make_tuple(80, 'B'),
        make_tuple(70, 'C'),
        make_tuple(60, 'D'),
        make_tuple(0,  'E')
}; 

This loop is a bit C-Like (though it works).
for (vector&lt;tuple&lt;int, char&gt;&gt;::size_type i = 0; i != fork.size(); i++)

Though I would have used some typedefs to make it more readable:
typedef std::tuple&lt;int, char&gt;  MarkRange;
typedef std::vector&lt;MarkRange&gt; MarkSet;
for (MarkSet::size_type i = 0; i != fork.size(); ++i)  // prefer pre-increment

If you were using C++03 then I would be using iterators:
for (MarkSet::const_iterator i = fork.begin(); i != fork.end(); ++i)

But since C++11 we have had range based for
for (auto const&amp; mark : fork)

Note:
// Since C++11 prefer `using` over typedef
using MarkRange = std::tuple&lt;int, char&gt;;
using MarkSet   = std::vector&lt;MarkRange&gt;;

Using std::endl is not a good idea. Prefer &quot;\n&quot;. The difference is that endl flushes the stream.  Flushing is usually always wrong. The streams will flush themselves at appropriate times let the code do its work.
Note: The most common complaint about C++ stream is that they are slower than C stream. This is usually down to inappropriate calls to flush that significantly degrades performance when done incorrectly (and manually is usually always incorrect).
Questions:

Is the vector of tuple an unnecessary overhead over the simple if/else?

I like it. Its called Data Driven programing (where the code uses data to define its structure). This is usually considered best practice.

Is it actually slower, or does compilation optimizes execution?

I doubt you could measure the difference.
But if it is important then you should measure it.
People will always have opinions on this; they are not relevant. The only thing that matters is data. BUT it only matters if this is important. Normally I would consider more expressive readable code more important. This code is readable and easy to update (I could even put the data into a config file that is modified seprately).

In this case it probably doesn't matter because the grade set is small, but is this approach considered a good/correct practice if we were to have a bigger set?

Even in bigger list this seems like a better idea.