How Do You Overload Unary Operators?

Unary operator overloading lets you define the behavior of single-operand operators like negation (-), logical NOT (!), and unary plus (+) for your own class types. Since they operate only on the object they are applied to, they are typically implemented as member functions.

Unary operators are operators that work with a single operand. The most common unary operators you'll encounter are the negation operator (-), the logical NOT operator (!), and the unary plus operator (+). Since these operators operate only on the object they're applied to, unary operator overloads are typically implemented as member functions.

Overloading the negation operator

Let's start with a practical example using a Temperature class that stores temperature values:

#include <iostream>

class Temperature
{
private:
    double m_celsius{};

public:
    Temperature(double celsius) : m_celsius{celsius} {}

    Temperature operator-() const;

    double getCelsius() const { return m_celsius; }
};

Temperature Temperature::operator-() const
{
    return Temperature{-m_celsius};
}

int main()
{
    Temperature freezing{0.0};
    Temperature belowFreezing{-freezing};

    std::cout << "Temperature: " << belowFreezing.getCelsius() << " degrees Celsius\n";

    return 0;
}

This example demonstrates how we can negate a Temperature object. The overloaded negation operator returns a new Temperature object with the opposite value. Note that we make the operator const because it doesn't modify the object it operates on.

Overloading the logical NOT operator

The logical NOT operator (!) is particularly useful when you want to test if an object is in some default or "empty" state. Let's create a BankAccount class:

#include <iostream>

class BankAccount
{
private:
    double m_balance{};

public:
    BankAccount(double balance) : m_balance{balance} {}

    bool operator!() const;

    double getBalance() const { return m_balance; }
};

bool BankAccount::operator!() const
{
    return m_balance == 0.0;
}

int main()
{
    BankAccount account1{0.0};
    BankAccount account2{150.50};

    if (!account1)
        std::cout << "Account 1 is empty\n";
    else
        std::cout << "Account 1 has funds\n";

    if (!account2)
        std::cout << "Account 2 is empty\n";
    else
        std::cout << "Account 2 has funds\n";

    return 0;
}

This program outputs:

Account 1 is empty
Account 2 has funds

The overloaded operator! returns true if the balance is zero (empty account), and false otherwise. This provides an intuitive way to check if an account is empty.

A more complex example

Here's a Vector3D class that demonstrates both negation and logical NOT:

#include <iostream>
#include <cmath>

class Vector3D
{
private:
    double m_x{};
    double m_y{};
    double m_z{};

public:
    Vector3D(double x = 0.0, double y = 0.0, double z = 0.0)
        : m_x{x}, m_y{y}, m_z{z}
    {
    }

    Vector3D operator-() const;
    bool operator!() const;

    double getX() const { return m_x; }
    double getY() const { return m_y; }
    double getZ() const { return m_z; }
};

Vector3D Vector3D::operator-() const
{
    return Vector3D{-m_x, -m_y, -m_z};
}

bool Vector3D::operator!() const
{
    return (m_x == 0.0 && m_y == 0.0 && m_z == 0.0);
}

int main()
{
    Vector3D forward{0.0, 1.0, 0.0};
    Vector3D backward{-forward};

    std::cout << "Forward vector: (" << forward.getX() << ", "
              << forward.getY() << ", " << forward.getZ() << ")\n";
    std::cout << "Backward vector: (" << backward.getX() << ", "
              << backward.getY() << ", " << backward.getZ() << ")\n";

    Vector3D zero{};
    if (!zero)
        std::cout << "Vector is at origin\n";

    return 0;
}

This outputs:

Forward vector: (0, 1, 0)
Backward vector: (0, -1, 0)
Vector is at origin

Unary plus operator

The unary plus operator (+) typically returns its operand unchanged. While less commonly overloaded, here's how you might implement it:

Vector3D Vector3D::operator+() const
{
    return *this;
}

Since we're simply returning a copy of the object, this is the most straightforward implementation.

Summary

Unary operators: Operators that work with a single operand, typically overloaded as member functions since they operate on the object they're called on.

Overloading unary minus (-): Returns a new object with negated values. Implemented as const member function since it doesn't modify the original object.

Overloading logical NOT (!): Returns bool indicating whether the object is in a default or empty state. Useful for testing object validity.

Overloading unary plus (+): Less commonly overloaded, typically just returns a copy of the object unchanged.

Implementation pattern: Unary operators are typically implemented as const member functions that take no parameters (aside from the implicit *this) and return either a modified copy or a boolean value depending on the operator's purpose.

Unary operators make custom classes behave naturally with standard unary operations, allowing code like -myObject or !myObject to work intuitively.