An explicit cast for narrowing numeric conversions
The first thing I noticed: you only support basic arithmetic types (integral and floating point).  This isn't apparent from the name narrow_cast.  Either enforce this with a static assert, or provide a mechanism to provide extensions for user defined types.

Here's what you write:


template &lt;typename T, typename U&gt;
constexpr bool can_fully_represent =
    std::is_same&lt;T, U&gt;::value ||
    ( std::is_integral&lt;T&gt;::value &amp;&amp; std::is_integral&lt;U&gt;::value &amp;&amp;
        ( ( std::is_signed&lt;T&gt;::value &amp;&amp; sizeof(T) &gt;  sizeof(U) ) ||
          ( is_same_signedness&lt;T, U&gt; &amp;&amp; sizeof(T) &gt;= sizeof(U) ) ) ) ||
    ( std::is_floating_point&lt;T&gt;::value &amp;&amp; std::is_floating_point&lt;U&gt;::value &amp;&amp; sizeof(T) &gt;= sizeof(U) );



Here's what I think should be sufficient:

template &lt;typename T, typename U&gt;
constexpr bool can_fully_represent =
       std::numeric_limits&lt;T&gt;::min() &lt;= std::numeric_limits&lt;U&gt;::min()
    &amp;&amp; std::numeric_limits&lt;T&gt;::max() &gt;= std::numeric_limits&lt;U&gt;::max();


OK, maybe I overlooked some edge cases, but that's the idea.

The unchecked version uses a forwarding reference, whereas the check version does not.  Try to keep the interface consistent.

std::is_same&lt;T, U&gt;::value can be simplified to std::is_same_v&lt;T, U&gt;.