#include <limits>
#include <type_traits>
#include <algorithm>

using std::min;
using std::max;

template <typename T>
constexpr auto digits = std::numeric_limits<T>::digits;

template <typename T, typename U, std::enable_if_t<std::is_unsigned<T>{} && std::is_unsigned<U>{} && !std::is_same<T, U>{}, int> = 0> // unsigned, unsigned
constexpr auto min(T t, U u)
{
    using result_t = std::conditional_t<digits<U> < digits<T>, U, T>;                                                                 // kleinerer Typ
    return u < t ? static_cast<result_t>(u) : static_cast<result_t>(t);
}
template <typename T, typename U, std::enable_if_t<std::is_signed<T>{} && std::is_signed<U>{} && !std::is_same<T, U>{}, int> = 0>     // signed, signed
constexpr auto min(T t, U u)
{
    using result_t = std::conditional_t<digits<T> < digits<U>, U, T>;                                                                 // größerer Typ
    return u < t ? static_cast<result_t>(u) : static_cast<result_t>(t);
}
template <typename T, typename U, std::enable_if_t<std::is_unsigned<T>{} && std::is_signed<U>{}, int> = 0>                            // unsigned, signed
constexpr auto min(T t, U u)
{
    using result_t = U;
    return u < 0 || static_cast<std::make_unsigned_t<U>>(u) < t ? static_cast<result_t>(u) : static_cast<result_t>(t);
}
template <typename T, typename U, std::enable_if_t<std::is_signed<T>{} && std::is_unsigned<U>{}, int> = 0>                            // signed, unsigned
constexpr auto min(T t, U u)
{
    using result_t = T;
    return t > 0 && u < static_cast<std::make_unsigned_t<T>>(t) ? static_cast<result_t>(u) : static_cast<result_t>(t);
}

template <typename T, typename U, std::enable_if_t<std::is_unsigned<T>{} == std::is_unsigned<U>{} && !std::is_same<T, U>{}, int> = 0> // unsigned, unsigned oder signed, signed
constexpr auto max(T t, U u)
{
    using result_t = std::conditional_t<digits<U> < digits<T>, U, T>;                                                                 // größerer Typ
    return u < t ? static_cast<result_t>(u) : static_cast<result_t>(t);
}
template <typename T, typename U, std::enable_if_t<std::is_unsigned<T>{} && std::is_signed<U>{}, int> = 0>                            // unsigned, signed
constexpr auto max(T t, U u)
{
    using result_t = std::conditional_t<digits<T> < digits<std::make_unsigned_t<U>>, std::make_unsigned_t<U>, T>;
    return u >= 0 && t < static_cast<std::make_unsigned_t<U>>(u) ? static_cast<result_t>(u) : static_cast<result_t>(t);
}
template <typename T, typename U, std::enable_if_t<std::is_signed<T>{} && std::is_unsigned<U>{}, int> = 0>                            // signed, unsigned
constexpr auto max(T t, U u)
{
    using result_t = std::conditional_t<digits<U> < digits<std::make_unsigned_t<T>>, std::make_unsigned_t<T>, U>;
    return t < 0 || static_cast<std::make_unsigned_t<T>>(t) < u ? static_cast<result_t>(u) : static_cast<result_t>(t);
}

int main()
{
	constexpr char c = 1;
	constexpr unsigned short us = 2;
	constexpr int i = 3;
	constexpr long long ll = 4;
	constexpr unsigned long long ull = 5;
	static_assert( min( c, us ) == 1, "" );
	static_assert( std::is_same<decltype(min( c, us )), char>{}, "" );
	static_assert( max( c, us ) == 2, "" );
	static_assert( std::is_same<decltype(max( c, us )), unsigned short>{}, "" );
	static_assert( min( ll, ull ) == 4, "" );
	static_assert( std::is_same<decltype(min( ll, ull )), long long>{}, "" );
	static_assert( max( ll, ull ) == 5, "" );
	static_assert( std::is_same<decltype(max( ll, ull )), unsigned long long>{}, "" );
}