Page 1 of 1

Defining functions in terms of others.

Posted: March 7th, 2018, 9:15 pm
by albinopapa
I can mostly only describe math operator overloads and perhaps the comparison operator overloads, but it is standard practice to define one function in terms of previously defined function and for this topic, specifically, the math operators and the assignment overloads.

Let me give the example:

Code: Select all

struct Vec2f
{
	Vec2f& operator+=(const Vec2f& other)
	{
		x += other.x;
		y += other.y;
		return *this;
	}
	Vec2 operator+(const Vec2f& other)const
	{
		return Vec2f(*this) += other;
	}
	float x,y;
};
This is what I mean by defining one function in terms of another. The operator+ is defined in terms of the operator+=.

I believe the reason for this is to reduce the amount of duplicated code, but why isn't it the other way round, the operator+= defined in terms of operator+. Well, I believe it's because standard practice is to define non-assignment operators as free standing functions and friending those functions.

Code: Select all

struct Vec2f
{
	Vec2f& operator+=(const Vec2f& other)
	{
		x += other.x;
		y += other.y;
		return *this;
	}

	friend Vec2 operator+(const Vec2f& leftSize, const Vec2f& rightSide);

	float x,y;
};
Vec2 operator+(const Vec2f& leftSize, const Vec2f& rightSide)
{
	return Vec2f(*leftSide) += rightSide;
}
This keeps the assignment overloads within the class itself and the regular operator overloads separate...sort of, if you follow this practice you still need to forward declare a the friend functions inside the class itself. In this example, you wouldn't have to because it's a struct and all it's members are public, you'd only have to follow this practice for classes where the members are private.

Now, what if we wanted to implement constexpr which would allow us to have a compile time vector.

Code: Select all

struct Vec2f
{
	constexpr Vec2f()=default;
	// Can't declare constexpr for this function, it changes the state of the object
	Vec2f& operator+=(const Vec2f& other)
	{
		x += other.x;
		y += other.y;
		return *this;
	}
	// This is illegal because constexpr functions can only call other constexpr functions
	constexpr Vec2 operator+(const Vec2f& other)const
	{
		return Vec2f(*this) += other;
	}
	float x = 0.f,y = 0.f;
};
Because we have defined the operator+ in terms of operator+= ( and the same holds true in the free standing function case ) we cannot declare the operator+ function as constexpr. You can call constexpr functions from non-constexpr functions so I believe writing the operator+= in terms of operator+ would be a better fit.

Code: Select all

struct Vec2f
{
	constexpr Vec2f()=default;
	// Still can't declare constexpr, but won't need to if you are wanting to change the state
	// of this object during runtime.
	Vec2f& operator+=(const Vec2f& other)
	{
		*this = *this + other;
		return *this;
	}
	// This is legal because we aren't calling a non-constexpr function and it's even legal C++11
	// code because it doesn't have any newly declared variables or multiple return or code paths.
	constexpr Vec2 operator+(const Vec2f& other)const
	{
		return { x + other.x, y + other.y };
	}
	float x = 0.f,y = 0.f;
};
It should even work for the free standing version:

Code: Select all

struct Vec2f
{
	Vec2f& operator+=(const Vec2f& other)
	{
		*this = *this + other;
		return *this;
	}

	friend constexpr Vec2 operator+(const Vec2f& leftSize, const Vec2f& rightSide);

	float x,y;
};
constexpr Vec2 operator+(const Vec2f& leftSide, const Vec2f& rightSide)
{
	return {leftSide.x + rightSide.x, leftSide.y + rightSide.y};
}
I have found with the free standing function version, you either declare as friend inside of the class, or forward declare it before the class so that the operator+= can actually use it.