#include <iostream>
#include <string>
using namespace std;
class Class
{
public:
	Class();    // default constructor
	Class(string);  // name parameterized constructor
	Class(string, int);  // name and data parameterized constructor
	Class(const Class&);  // copy constructor
	~Class();  // destructor
	Class& operator=(const Class&);  // assignment operator
	void setData(int s);
	int getData() const;
	string getName() const;
	
        // the ultimate const correctness nightmare
	const Class* const pass_by_reference_using_const_reference_return_pointer(const Class& o) const;
private:
	string name;
	int data;
};
Class::Class()
{
	name = "default_constructed";
	data = 0;
	cout << "default constructor name: " << name << "    data: " << data << endl;
}
Class::Class(const Class& other)
{
	name = "copy_constructed";
	data = other.data;
	cout << "call to copy constructor" << endl;
	cout << "data copied but name will stay \"copy_constructed\"" << endl;
	cout << "at end of copy constructor name: " << name << "    data: " << data << " copied from name: " << other.getName() << "    data: " << other.getData() << endl;
}
Class::~Class()
{
	cout << "destructor name: " << name << "    data: " << data << endl;
}
Class& Class::operator=(const Class& rValue)
{
	cout << "call to operator=" << endl;
	cout << "this is object name: " << name << "    data: " << data << endl;
	data = rValue.data;
	cout << "data copied but name will stay the same" << endl;
	cout << "at end of operator= name: " << name << "    data: " << data  << " assigned from name: " << rValue.getName() << "    data: " << rValue.getData() << endl;
	return *this;
}
Class::Class(string n)
{
	name = n;
	data = 0;
	cout << "name only parameterized constructor name: " << name << "    data: " << data << endl;
}
Class::Class(string n, int d)
{
	name = n;
	data = d;
	cout << "name and data parameterized constructor name: " << name << "    data: " << data  << endl;
}
void Class::setData(int d)
{
	data = d;
}
int Class::getData() const
{
	return data;
}
string Class::getName() const
{
	return name;
}
const Class* const Class::pass_by_reference_using_const_reference_return_pointer(const Class& o) const
{
	cout << "this is object name: " << name << "    data: " << data << endl;
	cout << "the ultimate const correctness nightmare" << endl;
	cout << "member function of Class: pass by reference using const reference, parameter is name: " << o.getName() << "    data: " << o.getData() << endl;
	cout << "preparing to return by const pointer const" << endl;
	return &o;
}
void pass_by_value(Class o)
{
	cout << "pass_by_value:" << endl;
	cout << "parameter is name: " << o.getName() << "    data: " << o.getData() << endl;
	cout << "returns void" << endl;
}
void pass_by_reference_using_pointers(Class* o)
{
	cout << "pass_by_reference_using_pointers:" << endl;
	cout << "parameter is name: " << o->getName() << "    data: " << o->getData() << endl;
	cout << "returns void" << endl;
}
void pass_by_reference_using_non_const_reference(Class& o)
{
	cout << "pass_by_reference_using_non_const_reference:" << endl;
	cout << "parameter is name: " << o.getName() << "    data: " << o.getData() << endl;
	cout << "returns void" << endl;
}
void pass_by_reference_using_const_reference(const Class& o)
{
	cout << "pass_by_reference_using_const_reference:" << endl;
	cout << "parameter is name: " << o.getName() << "    data: " << o.getData() << endl;
	cout << "returns void" << endl;
}
Class pass_by_reference_using_const_reference_return_by_value(const Class& o)
{
	cout << "pass_by_reference_using_const_reference:" << endl;
	cout << "parameter is name: " << o.getName() << "    data: " << o.getData() << endl;
	cout << "preparing to return by value" << endl;
	return o;
}
Class pass_by_reference_using_non_const_reference_return_by_value(Class& o)
{
	cout << "pass_by_reference_using_non_const_reference:" << endl;
	cout << "parameter is name: " << o.getName() << "    data: " << o.getData() << endl;
	cout << "preparing to return by value" << endl;
	return o;
}
const Class* pass_by_reference_using_const_reference_return_pointer(const Class& o)
{
	cout << "pass_by_reference_using_const_reference:" << endl;
	cout << "parameter is name: " << o.getName() << "    data: " << o.getData() << endl;
	cout << "preparing to return by pointer to class const" << endl;
	return &o;
}
Class* pass_by_reference_using_const_reference_return_pointer(Class& o)
{
	cout << "pass_by_reference_using_non_const_reference:" << endl;
	cout << "parameter is name: " << o.getName() << "    data: " << o.getData() << endl;
	cout << "preparing to return by pointer to non const Class" << endl;
	return &o;
}
Class& pass_by_reference_using_const_reference_return_non_const_reference(Class& o)
{
	cout << "pass_by_reference_using_non_const_reference:" << endl;
	cout << "parameter is name: " << o.getName() << "    data: " << o.getData() << endl;
	cout << "preparing to return by non const reference" << endl;
	return o;
}
const Class& pass_by_reference_using_const_reference_return_const_reference(const Class& o) 
{
	cout << "pass_by_reference_using_const_reference:" << endl;
	cout << "parameter is name: " << o.getName() << "    data: " << o.getData() << endl;
	cout << "preparing to return by const reference" << endl;
	return o;
}

int main()
{
	Class default_constructed;
	Class name_and_data_constructed("name_and_data_constructed", 2);
	Class name_constructed("name_constructed");
	default_constructed.setData(1);
	name_constructed.setData(3);
	cout << "Check of data in the three objects after data has been set, in order: ";
	cout << default_constructed.getData() << "  " << name_and_data_constructed.getData() << "  " << name_constructed.getData() << endl;
	cout << endl;
    pass_by_value(default_constructed);
	cout << endl;
    pass_by_reference_using_pointers(&default_constructed);
	cout << endl;
    pass_by_reference_using_non_const_reference(default_constructed);
	cout << endl;
    pass_by_reference_using_const_reference(default_constructed);
	cout << endl;
	Class returned_by_value("returned_by_value");
	returned_by_value = pass_by_reference_using_const_reference_return_by_value(default_constructed);
    cout << "returned by value is name: " << returned_by_value.getName() << "    data: " << returned_by_value.getData()  << endl;
	cout << endl;
	Class* returned_by_pointer = pass_by_reference_using_const_reference_return_pointer(default_constructed);
    cout << "returned by pointer is name: " << returned_by_pointer->getName() << "    data: " << returned_by_pointer->getData() << endl;
	cout << endl;
	Class& returned_by_non_const_reference = pass_by_reference_using_const_reference_return_non_const_reference(default_constructed);
    cout << "returned by non const reference is name: " << returned_by_non_const_reference.getName() 
	     << "    data: " << returned_by_non_const_reference.getData() << endl;
	cout << endl;
	const Class& returned_by_const_reference = pass_by_reference_using_const_reference_return_const_reference(default_constructed);
    cout << "returned by const reference is name: " << returned_by_const_reference.getName() << "    data: " << returned_by_const_reference.getData() << endl;
	cout << endl;
	cout << "before simple assignment statement of:  name_and_data_constructed = default_constructed" << endl;
    name_and_data_constructed = default_constructed;
	cout << "after simple assignment statement. name_and_data_constructed now has data " << name_and_data_constructed.getData() << endl;
	cout << endl;
	cout << "before complex assignment statement: name_constructed = pass_by_reference_using_non_const_reference_return_by_value(default_constructed)" << endl;
    name_constructed = pass_by_reference_using_non_const_reference_return_by_value(default_constructed);
	cout << "after complex assignment statement. name_constructed now has data " << name_constructed.getData() << endl;
	cout << endl;
	cout << "before most complex assignment statement: name_constructed = name_and_data_constructed.pass_by_reference_using_const_reference_return_by_value(default_constructed)" << endl;
    name_constructed = pass_by_reference_using_const_reference_return_by_value(default_constructed);
	cout << "after most complex assignment statement. name_constructed now has data " << name_constructed.getData() << endl;
	cout << endl;
	cout << "Check of data in the three objects after function calls, in order: ";
	cout << default_constructed.getData() << "  " << name_and_data_constructed.getData() << "  " << name_constructed.getData() << endl;
	cout << endl;
 
	return 0;
}