This project has been created as part of the 42 curriculum by baelgadi.

CPP Module 02

Ad-hoc polymorphism, operator overloading and the Orthodox Canonical class form


No powers. No shortcuts.
Every criminal in Gotham knows what happens when they face someone with a plan.

A class with no defined copy, no defined assignment and no defined destructor is merely a criminal waiting to happen.

C++ gives us operators and CPP02 teaches us to own them.


CPP Module 02:

4 exercises. C++98 standard only.
Compiler: c++ · Flags: -Wall -Wextra -Werror · Must compile with -std=c++98

Exercise 00: My First Class in Orthodox Canonical Form · ex00/ · OCF + raw accessors
Exercise 01: Towards a more useful fixed-point number class · ex01/ · Conversions + operator<<
Exercise 02: Now we’re talking · ex02/ · Full operator suite
Exercise 03: BSP · ex03/ · Point-in-triangle with Fixed arithmetic

Forbidden: using namespace (-42) · friend (-42) · printf · *alloc · free · STL containers · STL algorithms
Allowed: roundf from <cmath> (ex01–03)

Previously…


Orthodox Canonical Form

I have a contingency plan for every scenario. The OCF is yours.
From module 02 onwards, every class you write must define these 4 members.

OCF Member What it does
Default constructor Creates an object with a known state
Copy constructor Creates a new object from an existing one
Copy assignment operator Overwrites an existing object
Destructor Cleans up when the object is destroyed

Without them, the compiler auto-generates shallow.
Safe here but lethal later, when a class owns heap memory.

class Fixed {
public:
    Fixed(void);
    Fixed(Fixed const &other);
    Fixed &operator=(Fixed const &other);
    ~Fixed(void);
};

[!WARNING] The copy constructor is called on Fixed b(a).
The copy assignment operator is called on b = a, only when b already exists.
They are not the same.


Fixed-Point Numbers

Floating-point is a guess while fixed-point is a contract.
We store numbers as integers scaled by 256 (2^8). (8 fractional bits)

raw value 256  →  256 / 256 = 1.0
raw value 384  →  384 / 256 = 1.5
raw value   1  →    1 / 256 = 0.00390625   (the epsilon — the smallest representable step)

Conversions:

// int → fixed-point
_value = n << 8;                // multiply by 256

// float → fixed-point
_value = roundf(f * 256.0f);    // multiply and round (not truncate)

// fixed-point → float
return (float)_value / 256.0f;

// fixed-point → int
return _value >> 8;             // divide by 256, truncates toward zero

Arithmetic:

// Addition / subtraction: same scale but just operate on raw values
result._value = _value + other._value;

// Multiplication: (a * 256) * (b * 256) = (a * b) * 256^2 —(shift back by 8)
result._value = ((long long)_value * other._value) >> 8;

// Division: (a * 256) / (b * 256) = a / b (scale up first or lose the fraction)
result._value = ((long long)_value << 8) / other._value;

[!WARNING] Cast to long long before multiplying.
2 int values near 2^24 produce a 2^48 product that silently overflows 32 bits.


Operator Overloading

In C, a + b only works for built-in types. You’d call add(a, b) for anything else.
In C++ however, we define what + means for our type.

// This:
Fixed c = a + b;

// is this:
Fixed c = a.operator+(b);

Member vs non member:

Member Non member
Left operand this First parameter
Use when Operator belongs to your type Left operand isn’t your type
Example bool operator>(const Fixed &rhs) const std::ostream &operator<<(...)

operator<< must be non member: the left operand is std::ostream, which we don’t own.

std::ostream &operator<<(std::ostream &out, Fixed const &f)
{
    out << f.toFloat(); // uses public toFloat(), not _value directly
    return out;
}

[!NOTE] Using friend to give operator<< access to _value is not allowed.


Pre vs Post Increment

Both operators share the same symbol. The (int) dummy parameter is how C++ tells them apart.

// Pre increment: modify and return the modified object
Fixed &Fixed::operator++(void)
{
    ++_value;
    return *this;   // reference — no copy made
}

// Post increment: save, modify and return the old state
Fixed Fixed::operator++(int)
{
    Fixed tmp(*this);   // snapshot before the change
    ++_value;
    return tmp; // by value — tmp is local, never return its reference
}
Pre (++a) Post (a++)
Returns Reference to *this Copy of old state
Return type Fixed& Fixed
Signature operator++(void) operator++(int)

The increment unit is 1 raw bit = 1/256 ≈ 0.00390625 (the smallest step the system can represent).


BSP (Point in Triangle)

Given triangle ABC and a point P. Is P strictly inside?
Cross products tell us which side of an edge a point is on.

cross(AB, AP) = (B.x - A.x) * (P.y - A.y) - (B.y - A.y) * (P.x - A.x)

Positive → P is to the LEFT of edge A→B
Zero     → P is exactly ON the line through A and B
Negative → P is to the RIGHT

Compute for all three edges. If all three are the same sign and none is zero — P is inside.

bool bsp(Point const a, Point const b, Point const c, Point const point)
{
    Fixed d1 = cross(a, b, point);
    Fixed d2 = cross(b, c, point);
    Fixed d3 = cross(c, a, point);

    if (d1 == Fixed(0) || d2 == Fixed(0) || d3 == Fixed(0))
        return false;   // on edge or vertex (subject requires false)

    bool allPos = (d1 > Fixed(0)) && (d2 > Fixed(0)) && (d3 > Fixed(0));
    bool allNeg = (d1 < Fixed(0)) && (d2 < Fixed(0)) && (d3 < Fixed(0));
    return allPos || allNeg;
}

[!NOTE] Point has Fixed const _x, _y. The copy assignment operator must exist for OCF, but its body does nothing.
You cannot reassign const members.

Point &Point::operator=(const Point &other) { (void)other; return *this; }

Traps

operator<< printing _value instead of toFloat()
_value = 256 represents 1.0. The output must show 1, not 256.

Missing const overloads for min/max
The subject requires 4 overloads: 2 non-const + 2 const.
Without the const versions, Fixed::min with const arguments fails to compile.

Post increment returning a reference

// WRONG (dangling reference to a local)
Fixed &Fixed::operator++(int) { Fixed tmp(*this); ++_value; return tmp; }

// RIGHT (return by value)
Fixed Fixed::operator++(int) { Fixed tmp(*this); ++_value; return tmp; }

Missing long long in multiplication
int * int overflows at 2^31. Cast before multiplying, not after.

result._value = ((long long)_value * other._value) >> 8;

BSP returning true on edges
Zero cross product = point on edge = return false.

Point assignment casting away const

// WRONG (UB)
*(Fixed *)&_x = other._x;

// RIGHT (body does nothing)
Point &Point::operator=(const Point &other) { (void)other; return *this; }

Instructions

# ──── ex00 ────
cd ex00 && make
./fixed

# ──── ex01 ────
cd ex01 && make
./fixed

# ──── ex02 ────
cd ex02 && make
./fixed | grep -v called

# ──── ex03 ────
cd ex03 && make
./bsp

Evaluator demo for ex02:

Fixed a(5), b(3);
std::cout << (a > b) << " " << (a < b) << " " << (a == b) << std::endl; // 1 0 0
std::cout << a + b << " " << a - b << " " << a * b << " " << a / b << std::endl; // 8 2 15 1.66406

const Fixed x(1), y(2);
std::cout << Fixed::min(x, y) << std::endl; // 1 const overload
std::cout << Fixed::max(x, y) << std::endl; // 2 const overload

Resources


Do you bleed? Override it.

You either die a fixed-point, or you live long enough to see yourself become a float.