user3449209
user3449209

Reputation:

restrict cin. operator to a specific range

what if i wanted to limit the range of cin operator? something like the code below, where i take the input from the user, now the input must be less than 100 i.e. if the user enters marks > 100 it should report an error message.

using namespace std;

void GetInput(const std::string &prompt, int &i, int max)
{
    bool valid = false;
 do
  {
    cout << prompt;
    if (cin >> i && i < max)
    {
        valid = true;
    }
    else
    {
        cout << "Error: Input must be less than " << max << "." << endl;
    }
} while (!valid);

}

class Marks
{
private:
int elec, prog, math, perc = NULL; 

public:

void input()
{

        cout << "enter the marks obtained in Mathematics = ";
        cin >> math;
        cout << "enter the marks obtained in Programming = ";
        cin >> prog;
        cout << "enter the marks obtained in Electronics = ";
        cin >>elec;


} 

void avg()
{
    double avg = NULL;
    avg = (math + prog + elec) / 3;
    cout << "Average = " << avg << endl;
}


void Perc()
{

    perc = ((math + elec + prog)*100) / 300;
    cout << "The Percentage = " << perc <<" %"<< endl;

}


 void grade()
{

     if (perc >= 79)
     {
         cout << "A" << endl;
     }
     else if ((perc > 66) && (perc < 80))
         cout << "B" << endl;

    else if (perc < 66)
        cout << "C" << endl;
}

};

int main(void)
{
    Marks m;
    int maths, prog, elec;
    GetInput("enter the marks obtained in Mathematics = ", maths, 100);
    GetInput("enter the marks obtained in Programming = ", prog, 100);
    GetInput("enter the marks obtained in Electronics = ", elec, 100);

    m.Perc();
    m.avg();
    m.grade();
    system("PAUSE");
    return 0;

}

Upvotes: 1

Views: 1691

Answers (4)

David G
David G

Reputation: 96810

In addition to my other answer, here's another way to set a range but incorporate the semantics into std::num_get<facet> installed in the stream's locale. It's a bit more complicated, but it overrides the stream's default behavior for extracting integers and uses the custom range checking code:

#include <iostream>
#include <utility>

int input_range() { static int idx = std::ios_base::xalloc(); return idx; }

void erase_range(std::ios_base::event evt, std::ios_base& str, int index)
{
    if (evt == std::ios_base::erase_event)
    {
        delete static_cast<std::pair<int, int>*>(str.pword(index));
    }
}

class num_get : public std::num_get<char>
{
public:
    iter_type do_get( iter_type in, iter_type end, std::ios_base& str, 
                      std::ios_base::iostate& err, long& v ) const
    {
        long temp;
        in = std::num_get<char>::do_get(in, end, str, err, temp);

        if (in != end)
            err |= std::ios_base::failbit;

        void*& p = str.pword(input_range());

        if (p)
        {
            auto values = *static_cast<std::pair<int, int>*>(p);
            if (!(values.first <= temp && temp <= values.second))
            {
                err |= std::ios_base::failbit;
            } else
                v = temp;
        }
        return in;
    }
};

template<int I, int J>
std::istream& set_range(std::istream& is)
{
    void*& p = is.pword(input_range());
    erase_range(std::ios_base::erase_event, is, input_range());

    if (is)
    {
        p = new std::pair<int, int>(I, J);

        if (!dynamic_cast<const num_get*>(
                &std::use_facet<std::num_get<char>>(is.getloc())))
        {
            is.imbue(std::locale(is.getloc(), new num_get));
            is.register_callback(&erase_range, input_range());
        }
    }
    return is;
}

You can use it like this:

void input()
{
    std::cin >> set_range<0, 100>;

    std::cout << "enter the marks obtained in Mathematics = ";
    if (!(std::cin >> maths))
        std::cout << "Error!";
    std::cout << "enter the marks obtained in Programming = ";
    // so on...
    std::cout << "enter the marks obtained in Electronics = ";
    // and so forth...
}

Upvotes: 0

David G
David G

Reputation: 96810

You can create a manipulator that does this for you:

#include <iostream>

template<int, int>
struct input_range_impl
{
    input_range_impl(int& val) : val_(val)
    {
    }

    template<int I, int U>
    friend std::istream& operator>>(std::istream&, const input_range_impl<I, U>&);
private:
    int& val_;
};

template<int I, int U>
std::istream& operator>>(std::istream& is, const input_range_impl<I, U>& manip)
{
    if (!(is >> manip.val_) || !(I <= manip.val_ && manip.val_ >= U))
        is.setstate(std::ios_base::failbit);
    return is;
}

template<int I, int U>
input_range_impl<I, U> input_range(int& val)
{
    return { val };
}

void input()
{
    std::cout << "enter the marks obtained in Mathematics = ";
    std::cin >> input_range<0, 100>(maths);
    std::cout << "enter the marks obtained in Programming = ";
    std::cin >> input_range<0, 100>(prog);
    std::cout << "enter the marks obtained in Electronics = ";
    std::cin >> input_range<0, 100>(elec);
}

Upvotes: 0

Ben Voigt
Ben Voigt

Reputation: 283713

In this case, a bit of operator overloading can hide the complexity in a reusable helper class:

template<typename TMin, typename TMax>
struct unbound_limit_range
{
    const TMin lower_limit;
    const TMax upper_limit;
};

template<typename TMin, typename TMax>
class bound_limit_range
{
    std::istream& the_stream;
    const unbound_limit_range<TMin, TMax> the_limits;
public:
    bound_limit_range(istream& p_stream, const unbound_limit_range<TMin, TMax>& p_limits)
         : the_stream(p_stream), the_limits(p_limits)
    {}

    template<typename TVar>
    istream& operator>>(T& dest)
    {
        while (true) {
            if (!(the_stream >> dest)) return the_stream;

            if (dest < the_limits.lower_limit) {
                std::cout << "Value too low\n";
            }
            else if (dest > the_limits.upper_limit) {
                std::cout << "Value too high\n";
            }
            else {
                return the_stream;
            }

            std::cout << "Try again: " << std::flush;
        }
    }
};

template<typename TMin, typename TMax>
bound_limit_range<TMin, TMax> operator>>(std::istream& stream, const limit_range<TMin, TMax>& limits) { return { stream, limits }; }

template<typename TMin, typename TMax>
unbound_limit_range<TMin, TMax> limit_range(TMin lower, TMax upper) { return { lower, upper }; }

Quite a mess, but then you can simply write:

cout << "enter the marks obtained in Mathematics = ";
cin >> limit_range(0, 100) >> maths;
cout << "enter the marks obtained in Programming = ";
cin >> limit_range(0, 100) >> prog;
cout << "enter the marks obtained in Electronics = ";
cin >> limit_range(0, 100) >> elec;

if (cin) { /* all succeeded */ }

Upvotes: 0

David S.
David S.

Reputation: 538

You could try something like this:

int maths;
bool valid = false;
do
{
    cout << "Enter the marks obtained in Mathematics = ";
    if (cin >> maths && maths < 100)
    {
        valid = true;
    }
    else
    {
        cout << "Error: Input must be less than 100." << endl;
    }
} while (!valid);

This will ensure that the input is both a valid integer value and less than 100, only terminating the loop once valid input has been entered.

To do it generically:

void GetInput(const std::string &prompt, int &i, int max)
{
  bool valid = false;
  do
  {
      cout << prompt;
      if (cin >> i && i < max)
      {
          valid = true;
      }
      else
      {
          cout << "Error: Input must be less than " << max << "." << endl;
      }
  } while (!valid);
}

And to call it in your code:

int maths, prog, elec;
GetInput("enter the marks obtained in Mathematics = ", maths, 100);
GetInput("enter the marks obtained in Programming = ", prog, 100);
GetInput("enter the marks obtained in Electronics = ", elec, 100);

Upvotes: 1

Related Questions