templates, type traits and C++17 if constexpr

The Partridge Family were neither partridges nor a family. Discuss.
Post Reply
albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

templates, type traits and C++17 if constexpr

Post by albinopapa » December 19th, 2017, 10:06 am

It sure is nice to have all these great features allowing a programmer to be so lazy. So, normally, I put stuff like DotProduct, Normalize, Length and LengthSq in the vector classes that they need to work on, but lately, I've been messing around with templates and some C++17 features as Visual Studio allows. One of them is if constexpr. What this does is allows you to, in a function, determine which code path to COMPILE not just run as long as the condition is a constexpr value.

Here is my DotProduct example

Code: Select all

// Only dealing with float base types; _Vec2<float>, _Vec3<float>

// Just to make code look nicer in function
template<class VecType>
constexpr void VecType_Error()
{
	static_assert( !(is_vec2 && is_vec3), "Requires VecType to be a _Vec2<float or _Vec3<float>." );
}

template<class VecType>
float operator*( const VecType& lhs, const VecType& rhs )
{
	return DotProduct( lhs, rhs );
}

template<class VecType>
float DotProduct( const VecType& lhs, const VecType& rhs )
{
	// absurd value indicating you passed in something you shouldn't have
	float result = std::limits<float>::max();

	constexpr auto is_vec2 = std::is_same_v<VecType, Vec2>;
	constexpr auto is_vec3 = std::is_same_v<VecType, Vec3>;

	// Test for Vec2
	if constexpr( is_vec2 )
	{
		result = lhs.x * rhs.x + lhs.y * rhs.y;
	}
	else if constexpr( is_vec3 )
	{
		result = lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z;
	}
	else
	{
		// Will show error message in Output window if path is compiled
		VecType_Error<VecType>();
	}
	
	return result;
}
Now I can write two function instead of 6. Granted, this could be done without the if constexpr, but you would pay a runtime branching penalty. This way, it's decided at compile time which branch to take.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: templates, type traits and C++17 if constexpr

Post by albinopapa » December 19th, 2017, 10:28 am

Not sure about CrossProduct though. The Vec2 type returns a float, and the Vec3 type returns a Vec3.

Code: Select all

template<class VecType>
auto operator%( const VecType& lhs, const VecType& rhs )->decltype( CrossProduct( lhs, rhs )
{
	VecType_Error<VecType>();
	return CrossProduct( lhs, rhs );
}

float CrossProduct( const Vec2& lhs, const Vec2& rhs )
{
	return (lhs.y * rhs.x) - (lhs.x * rhs.y);
}
Vec3 CrossProduct( const Vec3& lhs, const Vec3& rhs )
{
	return {
	(lhs.y * rhs.z) - (lhs.z * rhs.y),
	(lhs.z * rhs.x) - (lhs.x * rhs.z),
	(lhs.x * rhs.y) - (lhs.y * rhs.x)
	};
}
I wouldn't know how to get the CrossProduct functions to auto deduce their return type since not only are the return types different, but also they way they are calculated. If not possible, then I only saved one function.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: templates, type traits and C++17 if constexpr

Post by albinopapa » December 19th, 2017, 11:08 am

Ok, I think I have a way. I just put a public type alias inside those vector classes and use that as the return type.

Code: Select all

template<class T>
class _Vec2
{
public:
	using CrossReturnType = T;
};

template<class T>
class _Vec3
{
public:
	using CrossReturnType = _Vec3<T>;
};

template<class VecType>
auto operator*( const VecType& lhs, const VecType& rhs )->typename VecType::CrossReturnType
{
	return CrossProduct( lhs, rhs );
}

template<class VecType>
auto CrossProduct( const VecType& lhs, const VecType& rhs )->typename VecType::CrossReturnType
{
	constexpr auto is_vec2 = std::is_same_v<VecType, _Vec2<float>>;
	constexpr auto is_vec3 = std::is_same_v<VecType, _Vec3<float>>;
	static_assert( is_vec2 || is_vec3, "Not a valid vector or not implemented." );

	if constexpr( is_vec2 )
	{
		// return Vec2 cross
	}
	else
	{
		// return Vec3 cross
	}
}
Haven't tried this yet, still working on some things.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
chili
Site Admin
Posts: 3948
Joined: December 31st, 2011, 4:53 pm
Location: Japan
Contact:

Re: templates, type traits and C++17 if constexpr

Post by chili » December 20th, 2017, 3:25 am

consexpr if can do some cool shit.

It is not true that without constexpr if you will pay a runtime branching penalty. The compiler will remove the branch for a if( true ) or if( false). Also, for branches where the same path is taken every time, but overhead is negligible since the branch predictor will never fail (or will only fail the first time). There are some exceptions to this rule (which can also often be dealt with with PGO), but in general that is the gist.

The real place that constexpr if shines is when you need to write templated code that will not be guaranteed to be well-formed depending on the template param. In that case, with a normal if it will be compiled, and the compilation will fail (or at least, the template overload will be removed from the candidate list via SFINAE). But with constexpr if, you can have code in the function that is ill formed, and it will not be touched by the compiler if the predicate evaluates to false.
Chili

Post Reply