Search code examples
c++oopoperator-overloadingconstructor-overloading

How do I create a helper function that can access a private member and operatore += sign?


My question is at the bottom.

I am creating a module for a Mark to encapsulate between 0 and 100.

I have a few operator and conversion overload functions so far:

class Mark {   
      int mark;

   public:

      Mark(); //constructor
      ~Mark(); //deconstructor
      Mark(int value); // mark is created using int value that sets the value of mark

      
      void setEmpty(); // mark = 0
      bool isValid()const; //checks if mark is valid

      //type conversion
      operator int() const; // mark casted to an int. result would be value of the mark or zero if mark is invalid
      operator double() const; //mark casted to a double for gpa..equivalent of int value
      operator char() const; // mark casted to char type...result would be grade latter value of mark

      //binary member operator
      Mark& operator += (int w); // int is added to the value of mark
      Mark& operator = (int i);  // mark is set to an integer
   };

This is the main program:

image

This is the output:

image

HERE IS MY QUESTION

I am trying to add mark to an integer and return that integer and any invalid marks would not add any value to the integer. In the main program, this is the code, its the very last 2 lines.

Mark n(80), k(120);
cout << (val += n) << endl;
cout << (val += k) << endl;

and the output would be

140
140

I am not able to create this without their being errors saying that my += operator from above is ambiguous. So I am thinking that this needs to be a helper function?

Any help on how to do this?


Solution

  • Your implicit casting is weakening your type. It is so weak, that it has now become ambiguous. Since you allow a Mark to be automatically converted into an int, you may as well just use an int and create stand-alone functions that manipulate ints instead of Marks.

    If you insist to manipulate Marks, you should create a type that has a smaller interface that enforces your invariants. I don't know what those are, but let's say that a Mark must be an int in [0, 100]. The following type (class) will ensure that you cannot create a Mark with values outside that range. Any accessory functionality is added as a stand-alone function that takes a Mark and manipulates its value with the confidence that such value will never be outside [0,100], because it is impossible for it to be outside the range.

    #include <iostream>
    #include <stdexcept>
    
    class Mark {
     public:
      Mark(int value = 0) {
        validate(value);
        m_value = value;
      }
    
      int value() const { return m_value; }
    
      void clear() { m_value = 0; }
    
      // I don't know why you need in-place modifications, but here they are and
      // they are exception safe.
    
      Mark& operator+=(const Mark& other) {
        int new_value = value() + other.value();
        validate(new_value);
        m_value = new_value;
        return *this;
      }
    
      Mark& operator-=(const Mark& other) {
        int new_value = value() - other.value();
        validate(new_value);
        m_value = new_value;
        return *this;
      }
    
     private:
      void validate(int value) const {
        // place your own logic here -- the purpose is to ensure that a Mark cannot
        // exist unless it is in a valid state.
        if (value < 0 || value > 100) {
          throw std::runtime_error("value must be in [0, 100]");
        }
      }
    
      int m_value = 0;
    };
    
    double to_double(const Mark& mark) {
      // replace with your own logic
      return mark.value() / 100.0;
    }
    
    char to_char(const Mark& mark) {
      // replace with your own logic
      if (mark.value() > 0 && mark.value() < 50) {
        return 'D';
      } else if (mark.value() >= 50 && mark.value() <= 100) {
        return 'A';
      } else {
        return 'X';
      }
    }
    
    std::ostream& operator<<(std::ostream& os, const Mark& m) {
      // replace with your own logic
      return os << "Mark(" << m.value() << ") / " << to_double(m) << " / "
                << to_char(m);
    }
    
    int main() {
      Mark m;
      Mark n(25);
      Mark k(100);
      // Mark p(-10); // uncommented will throw exception
      std::cout << m << "\n"
                << n << "\n"
                << k << "\n"
          // << p << "\n" // uncommented will throw exception
          ;
    }
    

    Sample output:

    $ clang++ example.cpp -std=c++2a
    $ ./a.out                             
    Mark(0) / 0 / X
    Mark(25) / 0.25 / D
    Mark(100) / 1 / A