<!DOCTYPE html>
<html lang="en">
	<head>
		<title>Safe integral comparisons</title>
	</head>
	<body>
		<address>
			Document number: P0586R0<br/>
			Date: 2017-09-12<br/>
			Project: Programming Language C++<br/>
			Reply-to: <a href="mailto:federico.kircheis@gmail.com">Federico Kircheis</a><br/>
			Audience: Library Evolution Working Group</br>
		</address>

		<h1>Safe integral comparisons</h1>

		<h2 id="Table">I. Table of Contents</h2>
		<ul style="font-family:monospace">
			<li><a href="#Table"          >I).......Table of Contents</a></li>
			<li><a href="#Motivation"     >II)......Motivation</a></li>
			<li><a href="#Proposal"       >III).....Proposal</a></li>
			<li><a href="#Examples"       >IV)......Examples</a></li>
			<li><a href="#Implementation" >V).......Possible implementation</a></li>
			<li><a href="#Effects"        >VI)......Effects on Existing Code</a></li>
			<li><a href="#Design"         >VII).....Design Decisions</a></li>
			<li><a href="#Related"        >VIII)....Related Works</a></li>
		</ul>


		<h2 id="Motivation">II. Motivation</h2>

		<p>
			Comparing integrals of different types may be a more complex task than expected. Most of the time we expect that a simple
		</p>
<pre><code>	if(a &lt; b){
		// ...
	} else {
		// ...
	}
</code></pre>
		<p>
			should work in all cases, but if <code>a</code> and <code>b</code> are of different types, things are more complicated.<br/>
			If <code>a</code> is a signed type, and <code>b</code> unsigned, then <code>a</code> is converted to the unsigned type.
			If <code>a</code> held a number less than zero, then the result may be unexpected, since the expression <code>a &lt; b</code> could evaluate to false, even if a strictly negative number is always lower than a positive one.
		</p>

		<p>
			Also converting integrals between different types can be challenging, for simplicity, most of the time we assume that values are in range, and write
		</p>
<pre><code>	a = static_cast&lt;decltype(a)&gt;(b);</code></pre>
		<p>
			If we want to write a safe conversion, we need to check if <code>b</code> has a value between <code>std::numeric_limits&lt;decltype(a)&gt;::min()</code> and <code>std::numeric_limits&lt;decltype(a)&gt;::max()</code>.
			We also need to pay attention that no implicit conversion (for example between unsigned and signed types) invalidates our comparison.
		</p>

		<p>
			Comparing and converting numbers, even of different numeric types, should be a trivial task.
			Unfortunately it is not, and because of implicit conversions we may write, without noticing it, unsafe code.
		</p>

		<h2 id="Proposal">III. Proposal</h2>

		<p>
			This paper proposes to add a set of <code>constexpr</code> and <code>noexcept</code> functions for converting and comparing integrals of different signeddes (except for <code>bool</code>):
		</p>

		<ul>
			<li>
				Two functions to compare if two variables represent the same value or not
<pre><code>	template &lt;typename T, typename U&gt;
	constexpr bool std::cmp_equal(T t, U u) noexcept;

	template &lt;typename T, typename U&gt;
	constexpr bool std::cmp_unequal(T t, U u) noexcept;
</code></pre>


			<li>
				A set of functions that can be used to determine the relative order of two values
<pre><code>	template &lt;typename T, typename U&gt;
	constexpr bool std::cmp_less(T t, U u) noexcept;

	template &lt;typename T, typename U&gt;
	constexpr bool std::cmp_greater(T t, U u) noexcept;

	template &lt;typename T, typename U&gt;
	constexpr bool std::cmp_less_or_equal(T t, U u) noexcept;

	template &lt;typename T, typename U&gt;
	constexpr bool std::cmp_greater_or_equal(T t, U u) noexcept;
</code></pre>

			<li>
				One function to determine if a specific value is inside the range of possible values of another type (i.e. if we can convert the value to the other type safely)
<pre><code>	template &lt;typename R, typename T&gt;
	constexpr bool in_range(T t) noexcept;
</code></pre>


		<h2 id="Examples">IV. Examples</h2>
			<h3>Examples without current proposal</h3>
				<p>Comparing an unsigned int with an int:</p>
<pre><code>	int a = ...
	unsigned int b = ...
	// added static_cast to avoid compiler warnings since we are doing a "safe" comparison
	if(a &lt; 0 || static_cast&lt;unsigned int&gt;(a) &lt; b){
		// do X
	} else {
		// do Y
	}
</code></pre>

				<p>Comparing an uint32_t with an int16_t:</p>
<pre><code>	int32_t a = ...
	uint16_t b = ...
	// added static_cast to avoid compiler warnings since we are doing a "safe" comparison
	if(a &lt; static_cast&lt;int32_t&gt;(b)){
		// do X
	} else {
		// do Y
	}
</code></pre>

				<p>Comparing an int with an intptr_t:</p>
<pre><code>	int a = ...
	intptr_t b = ...
	if(???){ // no idea how to do it in one readable line without some assumption about int and intptr_t
		// do X
	} else {
		// do Y
	}
</code></pre>


			<h3>Example with current proposal</h3>
				<p>
					Comparing one integral type <code>A</code> with another integral type <code>B</code> (both non <code>bool</code>):
				</p>
<pre><code>	A a = ...
	B b = ...
	// no need for any cast since std::cmp_less is taking care of everything
	if( std::cmp_less(a,b)){
		// do X
	} else {
		// do Y
	}
</code></pre>

		<h2 id="Implementation">V. Possible implementation</h2>
			<p>
				This section shows an example of how <code>cmp_equal</code>, <code>cmp_less</code> and <code>in_range</code> can be implemented with any standard conforming C++11 compiler.
				The only dependencies are the <code>std::numeric_limits</code> function from the <code>limits</code> header and some traits from the <code>type_traits</code> header.
				This implementation can also be found on <a href="https://raw.githubusercontent.com/fekir/safeintegral/master/safeintegral/safeintegralop_cmp.hpp">github</a>.
			</p>

<pre><code>
	#include &lt;limits&gt;
	#include &lt;type_traits&gt;

	namespace details{
	#if defined(ERR_MSG_xxx_NEEDS_INTEGRAL_NOT_BOOL) || defined(ASSERT_INTEGRAL_NOT_BOOL_TYPE)
	#error "ERR_MSG_xxx_NEEDS_INTEGRAL_NOT_BOOL or ASSERT_INTEGRAL_NOT_BOOL_TYPE already defined"
	#endif
	#define ERR_MSG_xxx_NEEDS_INTEGRAL_NOT_BOOL " needs to be an integral (not bool) value type"
	#define ASSERT_INTEGRAL_NOT_BOOL_TYPE(T) static_assert(is_integral_not_bool&lt;T&gt;(), #T ERR_MSG_xxx_NEEDS_INTEGRAL_NOT_BOOL);

	template &lt;typename T&gt;
	constexpr bool is_integral_not_bool(){
		using value_type = typename std::remove_cv&lt;T&gt;::type;
		return !std::is_same&lt;value_type,bool&gt;::value && std::is_integral&lt;T&gt;::value;
	}

	// could use the same implementation of in_range_signed_signed, but compiler may generate warning that t is always bigger than 0
	template &lt;typename R, typename T&gt;
	constexpr bool in_range_unsigned_unsigned(const T t) noexcept {
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(T);
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(R);
		return (std::numeric_limits&lt;T&gt;::digits  &gt; std::numeric_limits&lt;R&gt;::digits ) ?
		    (t &lt; static_cast&lt;T&gt;(std::numeric_limits&lt;R&gt;::max())) :
		    (static_cast&lt;R&gt;(t) &lt;std::numeric_limits&lt;R&gt;::max());
	}

	template &lt;typename R, typename T&gt;
	constexpr bool in_range_signed_signed(const T t) noexcept {
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(T);
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(R);
		return (std::numeric_limits&lt;T&gt;::digits  &gt; std::numeric_limits&lt;R&gt;::digits ) ?
		    (t &lt;= static_cast&lt;T&gt;(std::numeric_limits&lt;R&gt;::max()) && t &gt;= static_cast&lt;T&gt;(std::numeric_limits&lt;R&gt;::min())) :
		    (static_cast&lt;R&gt;(t) &lt;= std::numeric_limits&lt;R&gt;::max() && static_cast&lt;R&gt;(t) &gt;= std::numeric_limits&lt;R&gt;::max());
	}

	template &lt;typename R, typename T&gt;
	constexpr bool in_range_signed_unsigned(const T t) noexcept {
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(T);
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(R);
		return (t &lt; T{ 0 }) ? false :
		    (std::numeric_limits&lt;T&gt;::digits  / 2 &lt;= std::numeric_limits&lt;R&gt;::digits ) ? true :
		    (t &lt;= static_cast&lt;T&gt;(std::numeric_limits&lt;R&gt;::max()));
	}

	template &lt;typename R, typename T&gt;
	constexpr bool in_range_unsigned_signed(const T t) noexcept {
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(T);
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(R);
		return (std::numeric_limits&lt;T&gt;::digits  &gt;= std::numeric_limits&lt;R&gt;::digits  / 2) ? (t &lt;= static_cast&lt;T&gt;(std::numeric_limits&lt;R&gt;::max())) : true;
	}

	template &lt;typename R, typename T&gt;
	constexpr bool in_range_unsigned(const T t) noexcept {
		return std::is_unsigned&lt;R&gt;::value ? in_range_unsigned_unsigned&lt;R&gt;(t) : in_range_unsigned_signed&lt;R&gt;(t);
	}

	template &lt;typename R, typename T&gt;
	constexpr bool in_range_signed(const T t) noexcept {
		return std::is_signed&lt;R&gt;::value ? in_range_signed_signed&lt;R&gt;(t) : in_range_signed_unsigned&lt;R&gt;(t);
	}

	template &lt;typename T, typename U&gt;
	constexpr bool cmp_equal_same_sign(const T t, const U u) noexcept {
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(T);
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(U);
		return (std::numeric_limits&lt;T&gt;::digits &gt; std::numeric_limits&lt;U&gt;::digits ) ? (t == static_cast&lt;T&gt;(u)) : (static_cast&lt;U&gt;(t) == u);
	}

	template &lt;typename T, typename U&gt;
	constexpr bool cmp_equal_signed_unsigned(const T t, const U u) noexcept {
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(T);
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(U);
		return (t&lt;T{ 0 }) ? false : (std::numeric_limits&lt;T&gt;::digits  / 2&gt; std::numeric_limits&lt;U&gt;::digits  ) ? (t == static_cast&lt;T&gt;(u)) : (static_cast&lt;U&gt;(t) == u);
	}

	template &lt;typename T, typename U&gt;
	constexpr bool cmp_less_same_sign(const T t, const U u) noexcept {
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(T);
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(U);
		return (std::numeric_limits&lt;T&gt;::digits &gt;std::numeric_limits&lt;U&gt;::digits ) ? (t &lt; static_cast&lt;T&gt;(u)) : (static_cast&lt;U&gt;(t) &lt; u);
	}

	template &lt;typename T, typename U&gt;
	constexpr bool cmp_less_signed_unsigned(const T t, const U u) noexcept {
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(T);
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(U);
		return (t&lt;T{ 0 }) ? true : (std::numeric_limits&lt;T&gt;::digits  / 2&gt;std::numeric_limits&lt;U&gt;::digits ) ? (t &lt; static_cast&lt;T&gt;(u)) : (static_cast&lt;U&gt;(t) &lt; u);
	}

	template &lt;typename T, typename U&gt;
	constexpr bool cmp_less_unsigned_signed(const T t, const U u) noexcept {
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(T);
		ASSERT_INTEGRAL_NOT_BOOL_TYPE(U);
		return (u&lt;U{ 0 }) ? false : (std::numeric_limits&lt;U&gt;::digits  / 2&gt;std::numeric_limits&lt;T&gt;::digits ) ? (static_cast&lt;U&gt;(t) &lt; u) : (t &lt; static_cast&lt;T&gt;(u));
	}

	#undef ERR_MSG_xxx_NEEDS_INTEGRAL_NOT_BOOL
	#undef ASSERT_INTEGRAL_NOT_BOOL_TYPE
	} // end details

	/// Usage:
	/// size_t i = ...
	/// if(in_range&lt;DWORD&gt;(i)){
	///  // safe to use i as a DWORD value, parameter...
	/// } else {
	///  // not possible to rappresent i as a DWORD
	/// }
	template &lt;typename R, typename T&gt;
	constexpr bool in_range(const T t) noexcept {
		return std::is_unsigned&lt;T&gt;::value ? details::in_range_unsigned&lt;R&gt;(t) : details::in_range_signed&lt;R&gt;(t);
	}

	// equivalent of operator== for different types
	/// Usage:
	/// size_t i = ...
	/// DWORD j = ...
	/// if(cmp_equal(i,j)){
	///  // i and j rappresent the same quantity
	/// } else {
	///  // i and j rappresents different quantities
	/// }
	template &lt;typename T, typename U&gt;
	constexpr bool cmp_equal(const T t, const U u) noexcept {
		return
		    (std::is_signed&lt;T&gt;::value == std::is_signed&lt;U&gt;::value) ? details::cmp_equal_same_sign(t, u) :
		    (std::is_signed&lt;T&gt;::value) ? details::cmp_equal_signed_unsigned(t, u) : details::cmp_equal_signed_unsigned(u,t);
	}

	// equivalent of operator&lt; for different integral types
	/// Usage:
	/// size_t i = ...
	/// DWORD j = ...
	/// if(cmp_less(i,j)){
	///  // i &lt; j
	/// } else {
	///  // i &gt;= j
	/// }
	template &lt;typename T, typename U&gt;
	constexpr bool cmp_less(const T t, const U u) noexcept {
		return
		    (std::is_signed&lt;T&gt;::value == std::is_signed&lt;U&gt;::value) ? details::cmp_less_same_sign(t,u) :
		    (std::is_signed&lt;T&gt;::value) ? details::cmp_less_signed_unsigned(t, u) : details::cmp_less_unsigned_signed(t, u);
	}
</code></pre>

		<h2 id="Effects">VI. Effects on Existing Code</h2>
			<p>
				Since the proposed functions are not defined in any standard header, no currently existing code behavior will be changed.
			</p>

		<h2 id="Design">VII. Design Decisions</h2>
			<p>
				Since there is no reason to compare <code>true</code> and <code>false</code> with other integral types, there isn't one to provide an overload for the <code>bool</code> integral type either.<br/>
				The name of the functions (<code>cmp_equal</code>, <code>cmp_less</code> and others) are open to discussion, but the function names <code>std::less</code> and <code>std::greater</code> should not be used, since these do already exist, and have a different meaning.
			</p>

		<h2 id="Related">VIII. Related Works</h2>
			<p>
				In 2016, Robert Ramey did a much bigger proposal (see <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0228r0.pdf">p0228r0</a>) regarding safe integer types.
				He also used similar functions proposed in this paper for implementing his classes and operators, therefore an alternative implementation can be found on his <a href="https://github.com/robertramey/safe_numerics/blob/master/include/safe_compare.hpp">github repository</a>.
				This proposal addresses a smaller problem, namely comparing integral values, and is therefore much smaller.<br/>
				The functions provided can be also used for creating safe integer types.
			</p>

			<p>
				Another work, by Herb Sutter (see <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0515r0.pdf">p0515r0</a>), is about a new comparison operator (<code>&lt;=&gt;</code>).
				As far as I've understood the proposal the <code>operator&lt;=&gt;</code> should compare correctly different integral types, making part of this proposal obsolete if the operator is added to the language.
				While it would be a nice thing to have, having a new comparison operator that operates differently from the old operators may be counterintuitive and cause confusion, even if the new behaviour is more correct.
			</p>
	</body>
</html>
