Skip to content

[clang] Implement CWG2851: floating-point conversions in converted constant expressions #90387

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ C++2c Feature Support
Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

- Allow floating-point promotions and conversions in converted constant expressions.
(`CWG2851 Allow floating-point conversions in converted constant expressions <https://cplusplus.github.io/CWG/issues/2851.html>`_).

C Language Changes
------------------

Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ def err_expr_not_cce : Error<
"%select{case value|enumerator value|non-type template argument|"
"array size|explicit specifier argument|noexcept specifier argument|"
"call to 'size()'|call to 'data()'}0 is not a constant expression">;
def err_float_conv_cant_represent : Error<
"non-type template argument evaluates to %0 which cannot be "
"exactly represented in type %1"
>;
def ext_cce_narrowing : ExtWarn<
"%select{case value|enumerator value|non-type template argument|"
"array size|explicit specifier argument|noexcept specifier argument|"
Expand Down
38 changes: 35 additions & 3 deletions clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5955,6 +5955,10 @@ static bool CheckConvertedConstantConversions(Sema &S,
case ICK_Integral_Promotion:
case ICK_Integral_Conversion: // Narrowing conversions are checked elsewhere.
case ICK_Zero_Queue_Conversion:
// Per CWG2851, floating-point promotions and conversions are allowed.
// The value of a conversion is checked afterwards.
case ICK_Floating_Promotion:
case ICK_Floating_Conversion:
return true;

case ICK_Boolean_Conversion:
Expand All @@ -5974,9 +5978,7 @@ static bool CheckConvertedConstantConversions(Sema &S,
// only permitted if the source type is std::nullptr_t.
return SCS.getFromType()->isNullPtrType();

case ICK_Floating_Promotion:
case ICK_Complex_Promotion:
case ICK_Floating_Conversion:
case ICK_Complex_Conversion:
case ICK_Floating_Integral:
case ICK_Compatible_Conversion:
Expand Down Expand Up @@ -6113,7 +6115,37 @@ static ExprResult BuildConvertedConstantExpression(Sema &S, Expr *From,
if (Result.isInvalid())
return Result;

// Check for a narrowing implicit conversion.
if (SCS->Second == ICK_Floating_Conversion) {
// Unlike with narrowing conversions, the value must fit
// exactly even if it is in range
assert(CCE == Sema::CCEKind::CCEK_TemplateArg &&
"Only non-type template args should use floating-point conversions");

// Initializer is From, except it is a full-expression
const Expr *Initializer =
IgnoreNarrowingConversion(S.Context, Result.get());

// If it's value-dependent, we can't tell whether it will fit
if (Initializer->isValueDependent())
return Result;

// Not-constant diagnosed afterwards
if (!Initializer->isCXX11ConstantExpr(S.Context, &PreNarrowingValue))
return Result;

llvm::APFloat PostNarrowingValue = PreNarrowingValue.getFloat();
bool LosesInfo = true;
PostNarrowingValue.convert(S.Context.getFloatTypeSemantics(T),
llvm::APFloat::rmNearestTiesToEven, &LosesInfo);

if (LosesInfo)
S.Diag(From->getBeginLoc(), diag::err_float_conv_cant_represent)
<< PreNarrowingValue.getAsString(S.Context, From->getType()) << T;

return Result;
}

// Check for a narrowing integer conversion.
bool ReturnPreNarrowingValue = false;
QualType PreNarrowingType;
switch (SCS->getNarrowingKind(S.Context, Result.get(), PreNarrowingValue,
Expand Down
62 changes: 62 additions & 0 deletions clang/test/CXX/drs/cwg28xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// RUN: %clang_cc1 -std=c++14 -pedantic-errors -verify=expected %s
// RUN: %clang_cc1 -std=c++17 -pedantic-errors -verify=expected %s
// RUN: %clang_cc1 -std=c++20 -pedantic-errors -verify=expected,since-cxx20 %s
// RUN: %clang_cc1 -std=c++20 -pedantic-errors -mlong-double-64 -verify=expected,since-cxx20 %s
// RUN: %clang_cc1 -std=c++23 -pedantic-errors -verify=expected,since-cxx20,since-cxx23 %s
// RUN: %clang_cc1 -std=c++2c -pedantic-errors -verify=expected,since-cxx20,since-cxx23,since-cxx26 %s

Expand Down Expand Up @@ -87,6 +88,67 @@ void B<int>::g() requires true;

} // namespace cwg2847

namespace cwg2851 { // cwg2851: 20
#if __cplusplus >= 202002L
template<typename T, T v> struct Val { static constexpr T value = v; };


// Floating-point promotions

static_assert(Val<long double, 0.0>::value == 0.0L);
static_assert(Val<long double, 0.0f>::value == 0.0L);
static_assert(Val<double, 0.0f>::value == 0.0);
static_assert(Val<long double, -0.0>::value == -0.0L);

static_assert(!__is_same(Val<long double, -0.0>, Val<long double, 0.0L>));
static_assert(__is_same(Val<long double, 0.5>, Val<long double, 0.5L>));

static_assert(__is_same(Val<long double, __builtin_inff()>, Val<long double, __builtin_infl()>));

static_assert(__is_same(Val<long double, __builtin_nanf("")>, Val<long double, static_cast<long double>(__builtin_nanf(""))>));
static_assert(__is_same(Val<long double, __builtin_nansf("")>, Val<long double, static_cast<long double>(__builtin_nansf(""))>));
static_assert(__is_same(Val<long double, __builtin_nanf("0x1")>, Val<long double, static_cast<long double>(__builtin_nanf("0x1"))>));
static_assert(__is_same(Val<long double, __builtin_nansf("0x1")>, Val<long double, static_cast<long double>(__builtin_nansf("0x1"))>));


// Floating-point conversions where the source value can be represented exactly in the destination type

static_assert(Val<float, 0.0L>::value == 0.0L);
static_assert(__is_same(Val<float, 0.0>, Val<float, 0.0L>));
static_assert(__is_same(Val<float, 0.0>, Val<float, 0.0f>));
static_assert(!__is_same(Val<float, -0.0L>, Val<float, 0.0f>));
static_assert(__is_same(Val<float, 0.5L>, Val<float, 0.5f>));
static_assert(__is_same(Val<float, 0.5L>, Val<float, 0.5f>));

static_assert(__is_same(Val<float, double{__FLT_DENORM_MIN__}>, Val<float, __FLT_DENORM_MIN__>));
Val<float, double{__FLT_DENORM_MIN__} / 2.0> _1;
// since-cxx20-error-re@-1 {{non-type template argument evaluates to {{.+}} which cannot be exactly represented in type 'float'}}
Val<float, static_cast<long double>(__FLT_DENORM_MIN__) / 2.0L> _2;
// since-cxx20-error-re@-1 {{non-type template argument evaluates to {{.+}} which cannot be exactly represented in type 'float'}}
Val<float, __DBL_MAX__> _3;
// since-cxx20-error-re@-1 {{non-type template argument evaluates to {{.+}} which cannot be exactly represented in type 'float'}}

static_assert(__is_same(Val<float, __builtin_infl()>, Val<float, __builtin_inff()>));

static_assert(__is_same(Val<float, __builtin_nanl("")>, Val<float, static_cast<float>(__builtin_nanl(""))>));
static_assert(__is_same(Val<float, __builtin_nansl("")>, Val<float, static_cast<float>(__builtin_nansl(""))>));
#if __SIZEOF_LONG_DOUBLE__ > 8
// since-cxx20-error@-2 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
#endif
// Payload is shifted right so these payloads will be preserved
static_assert(__is_same(Val<float, __builtin_nan("0xFF00000000")>, Val<float, static_cast<float>(__builtin_nan("0xFF00000000"))>));
static_assert(__is_same(Val<float, __builtin_nans("0xFF00000000")>, Val<float, static_cast<float>(__builtin_nans("0xFF00000000"))>));
static_assert(__is_same(Val<float, __builtin_nanl("0x1")>, Val<float, static_cast<float>(__builtin_nanl("0x1"))>));
// since-cxx20-error@-1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
static_assert(__is_same(Val<float, __builtin_nansl("0x1")>, Val<float, static_cast<float>(__builtin_nansl("0x1"))>));
// since-cxx20-error@-1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
static_assert(__is_same(Val<float, __builtin_nanl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")>, Val<float, static_cast<float>(__builtin_nanl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))>));
// since-cxx20-error@-1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
static_assert(__is_same(Val<float, __builtin_nansl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")>, Val<float, static_cast<float>(__builtin_nansl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))>));
// since-cxx20-error@-1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
#endif
} // namespace cwg2851

namespace cwg2857 { // cwg2857: no
struct A {};
template <typename>
Expand Down
2 changes: 1 addition & 1 deletion clang/www/cxx_dr_status.html
Original file line number Diff line number Diff line change
Expand Up @@ -16922,7 +16922,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2851.html">2851</a></td>
<td>DR</td>
<td>Allow floating-point conversions in converted constant expressions</td>
<td class="unknown" align="center">Unknown</td>
<td class="unreleased" align="center">Clang 20</td>
</tr>
<tr class="open" id="2852">
<td><a href="https://cplusplus.github.io/CWG/issues/2852.html">2852</a></td>
Expand Down
Loading