#include <iostream>
#include <map>

using namespace std;

//--------------------------------------------------------------------
// A class whose only purpose is to let us experiment with operator
// and conversion overloading.
//--------------------------------------------------------------------
class MyClass {
 public:
  // This constructor allows C++ to perform implicit conversions of integers to 
  // MyClass objects.
  MyClass(int i) {
    cout << "MyClass(" << i << ")" << endl;
    _val = i;
  }

  // This constructor allows implicit conversion from a string to MyClass.
  // This is a very artificial idea; for the most part, there is no sensible conversion.
  MyClass(const char * s) {
    cout << "MyClass(" << s << ")" << endl;
    auto it = _string_map.find(s);
    if ( it != _string_map.end() ) _val = it->second;
    else _val = 0;
  }
  
  // This addition operator requires a MyClass object on the left-hand-side (lhs)
  // and another on the right hand side (rhs).  C++ will do an implicit conversion
  // of an int to a MyClass for the rhs argument, though, because of the 
  // constructor we provided, and similarly for a string.
  // The argument must be 'const' is required for these implicit conversions to take
  // place.
  MyClass operator+(const MyClass &other) const {
    cout << "MyClass(" << _val << ") + MyClass(" << other._val << ")" << endl;
    return MyClass(_val + other._val);
  }

  // We can't use a class method a non-MyClass is on the lhs,
  // so we use a regular old method.  But, we want that method to have
  // access to the private state of  the MyClass object.  Thus, 'friend'.
  
  // Example use: 4 + myClassObj
  friend MyClass operator+(int lhs, const MyClass& rhs);
  // Example use: cout << ... << myClassObj << ...
  friend ostream& operator<<(ostream& os, const MyClass& t);

  // You can overload conversion operators in C++.  Usually you
  // do this to enable implicit conversions between primitive types and objects
  // of your class.  In this not-too-unusual case, simply enabling an implicit
  // conversion to int causes an ambiguity: when confronted with cout << myClassObj,
  // the compiler doens't know whether to use the overloaded << operator
  // above, or to implicitly convert myClassObj to an int and then apply
  // the << operator.   To fix that, we modify this method with the 'explicit'
  // keyword, disabling implicit conversion using it, but still allowing
  // explicit conversion.
  explicit operator int() const { return _val; }
  
 private:
  int _val;
  static const map<string,int> _string_map;
};
// definition / initialization of class static variable
const map<string,int> MyClass::_string_map = {{"one", 1}, {"two", 2}, {"three", 3}};

//--------------------------------------------------------------------------------------
// Here's the regular old method defining the operation with signature int + myClassObj.
//--------------------------------------------------------------------------------------
MyClass operator+(int lhs, const MyClass& rhs) {
  cout << "int(" << lhs << ") + MyClass(" << rhs._val << ")" << endl;
  return MyClass(lhs + rhs._val);
}

//--------------------------------------------------------------------------------------
// The regular old method for ostream output
//--------------------------------------------------------------------------------------
ostream& operator<<(ostream& os, const MyClass& t) {
  os << "(operator <<) " << t._val;
  return os;
}

//----------------------------------
// Main line starts here
//----------------------------------
int main() {
  cout << "----- 1 ------------------------------------" << endl;
  MyClass a(0);
  cout << "----- 2 ------------------------------------" << endl;
  MyClass b(1);

  cout << "----- 3 ------------------------------------" << endl;
  a = a + 1;

  cout << "----- 4 ------------------------------------" << endl;
  b = b + "one";

  cout << "----- 5 ------------------------------------" << endl;
  // This version will invoke our << operator
  cout << "1 + a + b + 4 = " << (1 + a + b + 4) << endl;

  cout << "----- 6 ------------------------------------" << endl;
  // This version explicitly converts the result to an int, which is then output
  cout << "1 + a + b + 4 = " << (int)(1 + a + b + 4) << endl;

  cout << "----- 7 ------------------------------------" << endl;
  // Here we do integer addition, then construct a MyClass object,
  // then move assign it to b.
  b = 20 + int(a);
  cout << "b = 20 + int(a) ==> b = " << int(b) << endl;

  // Will this compile?  run?
  // cout << "----- 8 ------------------------------------" << endl;
  // a  = 2 * (a + b);
  // cout << "a  = 2 * (a + b) ==> a = " << int(a) << endl;
}