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.