https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113007

--- Comment #5 from Jonathan Wakely <redi at gcc dot gnu.org> ---
(In reply to Pavel Novikov from comment #4)
> Indeed the standard says all over that narrowing conversion in
> initialization is prohibited, though this code compiles:
> 
>     int i = 42;
>     bool b[] = {i}; // narrowing
>     int64_t i64[] = {i};
>     double d[] = {i}; // narrowing*

They compile *with a diagnostic* (a warning, specifically). That is what the
standard requires for ill-formed code, and those narrowing conversions are
ill-formed. If you want them to be errors not warnings, you can use
-Werror=narrowing. In a SFINAE context, they result in substitution failure,
because "warn and continue" is not an option for substitution failures.

> (*putting aside that any `int` can be _exactly_ represented by a double
> precision floating point value)

Assuming 32-bit int and 64-bit double, yes. That isn't guaranteed by the
standard though: int could have 64 bits, for example. To avoid unportable code,
it's considered to be narrowing "except where the source is a constant
expression and the actual value after conversion will fit into the target type
and will produce the original value when converted back to the original type".

So double d{42} is not a narrowing conversion, but double d{i} is.

> As an approximation for `T x[] = {t}` requirement I came up with this code:
> 
>     template<typename T, typename U>
>     std::enable_if_t<sizeof(decltype(T{std::declval<U>()}))>
>     test() {}
> 
> and indeed it fails to compile when narrwoing conversion is required, i.e.
> 
>     test<bool, int>(); // error: narrowing
>     test<int64_t, int>();
>     test<double, int>(); // error: narrowing
> 
> So turning
> 
> > an imaginary function FUN(Ti) for each alternative type Ti for which Ti x[] 
> > = {std​::​forward<T>(t)}; is well-formed for some invented variable x.
> 
> into code using the approximation above we have the following code
> 
>     template<typename T, typename U>
>     std::enable_if_t<sizeof(decltype(T{std::declval<U>()}))>
>     F(U) {}
> 
>     F<bool>(42); // error: deduction/substitution failed due to narrowing
>     F<int64_t>(42);
>     F<double>(42); // error: deduction/substitution failed due to narrowing
> 
> And if we consider `F<T_j>()` functions as an overload set, then indeed only
> `F<int64_t>()` is well formed considering (non)narrowing requirement and is
> ultimately selected.
> (See https://godbolt.org/z/sxof45Eeh)
> 
> Did I get it right?

Yes. Libstdc++ uses something similar to your enable_if, but using void_t
instead:

  template<typename _Ti> struct _Arr { _Ti _M_x[1]; };

  template<size_t _Ind, typename _Tp, typename _Ti>
    struct _Build_FUN<_Ind, _Tp, _Ti,
                      void_t<decltype(_Arr<_Ti>{{std::declval<_Tp>()}})>>

The _Arr struct is needed to ensure that we're trying to convert to T{1} not
just T, which is what the standard wants.

Reply via email to