Page 1 of 1

templates, type traits and C++17 if constexpr

Posted: December 19th, 2017, 10:06 am
by albinopapa
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.

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

Posted: December 19th, 2017, 10:28 am
by albinopapa
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.

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

Posted: December 19th, 2017, 11:08 am
by albinopapa
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.

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

Posted: December 20th, 2017, 3:25 am
by chili
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.