Skip to content

Commit 13e2943

Browse files
committed
[clang] Implement CWG2851: floating-point conversions in converted constant expressions
1 parent 487967a commit 13e2943

File tree

4 files changed

+106
-3
lines changed

4 files changed

+106
-3
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ Resolutions to C++ Defect Reports
164164
- Clang now diagnoses declarative nested-name-specifiers with pack-index-specifiers.
165165
(`CWG2858: Declarative nested-name-specifiers and pack-index-specifiers <https://cplusplus.github.io/CWG/issues/2858.html>`_).
166166

167+
- Allow floating-point promotions and conversions in converted constant expressions.
168+
(`CWG2851 Allow floating-point conversions in converted constant expressions <https://cplusplus.github.io/CWG/issues/2851.html>`_).
169+
167170
C Language Changes
168171
------------------
169172

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ def err_expr_not_cce : Error<
8585
"%select{case value|enumerator value|non-type template argument|"
8686
"array size|explicit specifier argument|noexcept specifier argument|"
8787
"call to 'size()'|call to 'data()'}0 is not a constant expression">;
88+
def err_float_conv_cant_represent : Error<
89+
"non-type template argument evaluates to %0 which cannot be "
90+
"exactly represented in type %1"
91+
>;
8892
def ext_cce_narrowing : ExtWarn<
8993
"%select{case value|enumerator value|non-type template argument|"
9094
"array size|explicit specifier argument|noexcept specifier argument|"

clang/lib/Sema/SemaOverload.cpp

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6072,6 +6072,10 @@ static bool CheckConvertedConstantConversions(Sema &S,
60726072
case ICK_Integral_Promotion:
60736073
case ICK_Integral_Conversion: // Narrowing conversions are checked elsewhere.
60746074
case ICK_Zero_Queue_Conversion:
6075+
// Per CWG2851, floating-point promotions and conversions are allowed.
6076+
// The value of a conversion is checked afterwards.
6077+
case ICK_Floating_Promotion:
6078+
case ICK_Floating_Conversion:
60756079
return true;
60766080

60776081
case ICK_Boolean_Conversion:
@@ -6091,9 +6095,7 @@ static bool CheckConvertedConstantConversions(Sema &S,
60916095
// only permitted if the source type is std::nullptr_t.
60926096
return SCS.getFromType()->isNullPtrType();
60936097

6094-
case ICK_Floating_Promotion:
60956098
case ICK_Complex_Promotion:
6096-
case ICK_Floating_Conversion:
60976099
case ICK_Complex_Conversion:
60986100
case ICK_Floating_Integral:
60996101
case ICK_Compatible_Conversion:
@@ -6229,7 +6231,37 @@ static ExprResult BuildConvertedConstantExpression(Sema &S, Expr *From,
62296231
if (Result.isInvalid())
62306232
return Result;
62316233

6232-
// Check for a narrowing implicit conversion.
6234+
if (SCS->Second == ICK_Floating_Conversion) {
6235+
// Unlike with narrowing conversions, the value must fit
6236+
// exactly even if it is in range
6237+
assert(CCE == Sema::CCEKind::CCEK_TemplateArg &&
6238+
"Only non-type template args should use floating-point conversions");
6239+
6240+
// Initializer is From, except it is a full-expression
6241+
const Expr *Initializer =
6242+
IgnoreNarrowingConversion(S.Context, Result.get());
6243+
6244+
// If it's value-dependent, we can't tell whether it will fit
6245+
if (Initializer->isValueDependent())
6246+
return Result;
6247+
6248+
// Not-constant diagnosed afterwards
6249+
if (!Initializer->isCXX11ConstantExpr(S.Context, &PreNarrowingValue))
6250+
return Result;
6251+
6252+
llvm::APFloat PostNarrowingValue = PreNarrowingValue.getFloat();
6253+
bool LosesInfo = true;
6254+
PostNarrowingValue.convert(S.Context.getFloatTypeSemantics(T),
6255+
llvm::APFloat::rmNearestTiesToEven, &LosesInfo);
6256+
6257+
if (LosesInfo)
6258+
S.Diag(From->getBeginLoc(), diag::err_float_conv_cant_represent)
6259+
<< PreNarrowingValue.getAsString(S.Context, From->getType()) << T;
6260+
6261+
return Result;
6262+
}
6263+
6264+
// Check for a narrowing integer conversion.
62336265
bool ReturnPreNarrowingValue = false;
62346266
QualType PreNarrowingType;
62356267
switch (SCS->getNarrowingKind(S.Context, Result.get(), PreNarrowingValue,

clang/test/CXX/drs/dr28xx.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// RUN: %clang_cc1 -std=c++14 -verify=expected %s
44
// RUN: %clang_cc1 -std=c++17 -verify=expected %s
55
// RUN: %clang_cc1 -std=c++20 -verify=expected,since-cxx20 %s
6+
// RUN: %clang_cc1 -std=c++20 -mlong-double-64 -verify=expected,since-cxx20 %s
67
// RUN: %clang_cc1 -std=c++23 -verify=expected,since-cxx20,since-cxx23 %s
78
// RUN: %clang_cc1 -std=c++2c -verify=expected,since-cxx20,since-cxx23,since-cxx26 %s
89

@@ -59,6 +60,69 @@ void B<int>::g() requires true;
5960

6061
} // namespace cwg2847
6162

63+
namespace cwg2851 { // cwg2851: 19
64+
65+
#if __cplusplus >= 202002L
66+
template<typename T, T v> struct Val { static constexpr T value = v; };
67+
68+
69+
// Floating-point promotions
70+
71+
static_assert(Val<long double, 0.0>::value == 0.0L);
72+
static_assert(Val<long double, 0.0f>::value == 0.0L);
73+
static_assert(Val<double, 0.0f>::value == 0.0);
74+
static_assert(Val<long double, -0.0>::value == -0.0L);
75+
76+
static_assert(!__is_same(Val<long double, -0.0>, Val<long double, 0.0L>));
77+
static_assert(__is_same(Val<long double, 0.5>, Val<long double, 0.5L>));
78+
79+
static_assert(__is_same(Val<long double, __builtin_inff()>, Val<long double, __builtin_infl()>));
80+
81+
static_assert(__is_same(Val<long double, __builtin_nanf("")>, Val<long double, static_cast<long double>(__builtin_nanf(""))>));
82+
static_assert(__is_same(Val<long double, __builtin_nansf("")>, Val<long double, static_cast<long double>(__builtin_nansf(""))>));
83+
static_assert(__is_same(Val<long double, __builtin_nanf("0x1")>, Val<long double, static_cast<long double>(__builtin_nanf("0x1"))>));
84+
static_assert(__is_same(Val<long double, __builtin_nansf("0x1")>, Val<long double, static_cast<long double>(__builtin_nansf("0x1"))>));
85+
86+
87+
// Floating-point conversions where the source value can be represented exactly in the destination type
88+
89+
static_assert(Val<float, 0.0L>::value == 0.0L);
90+
static_assert(__is_same(Val<float, 0.0>, Val<float, 0.0L>));
91+
static_assert(__is_same(Val<float, 0.0>, Val<float, 0.0f>));
92+
static_assert(!__is_same(Val<float, -0.0L>, Val<float, 0.0f>));
93+
static_assert(__is_same(Val<float, 0.5L>, Val<float, 0.5f>));
94+
static_assert(__is_same(Val<float, 0.5L>, Val<float, 0.5f>));
95+
96+
static_assert(__is_same(Val<float, double{__FLT_DENORM_MIN__}>, Val<float, __FLT_DENORM_MIN__>));
97+
Val<float, double{__FLT_DENORM_MIN__} / 2.0> _1;
98+
// since-cxx20-error-re@-1 {{non-type template argument evaluates to {{.+}} which cannot be exactly represented in type 'float'}}
99+
Val<float, static_cast<long double>(__FLT_DENORM_MIN__) / 2.0L> _2;
100+
// since-cxx20-error-re@-1 {{non-type template argument evaluates to {{.+}} which cannot be exactly represented in type 'float'}}
101+
Val<float, __DBL_MAX__> _3;
102+
// since-cxx20-error-re@-1 {{non-type template argument evaluates to {{.+}} which cannot be exactly represented in type 'float'}}
103+
104+
static_assert(__is_same(Val<float, __builtin_infl()>, Val<float, __builtin_inff()>));
105+
106+
static_assert(__is_same(Val<float, __builtin_nanl("")>, Val<float, static_cast<float>(__builtin_nanl(""))>));
107+
static_assert(__is_same(Val<float, __builtin_nansl("")>, Val<float, static_cast<float>(__builtin_nansl(""))>));
108+
#if __SIZEOF_LONG_DOUBLE__ > 8
109+
// since-cxx20-error@-2 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
110+
#endif
111+
// Payload is shifted right so these payloads will be preserved
112+
static_assert(__is_same(Val<float, __builtin_nan("0xFF00000000")>, Val<float, static_cast<float>(__builtin_nan("0xFF00000000"))>));
113+
static_assert(__is_same(Val<float, __builtin_nans("0xFF00000000")>, Val<float, static_cast<float>(__builtin_nans("0xFF00000000"))>));
114+
static_assert(__is_same(Val<float, __builtin_nanl("0x1")>, Val<float, static_cast<float>(__builtin_nanl("0x1"))>));
115+
// since-cxx20-error@-1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
116+
static_assert(__is_same(Val<float, __builtin_nansl("0x1")>, Val<float, static_cast<float>(__builtin_nansl("0x1"))>));
117+
// since-cxx20-error@-1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
118+
static_assert(__is_same(Val<float, __builtin_nanl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")>, Val<float, static_cast<float>(__builtin_nanl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))>));
119+
// since-cxx20-error@-1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
120+
static_assert(__is_same(Val<float, __builtin_nansl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")>, Val<float, static_cast<float>(__builtin_nansl("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"))>));
121+
// since-cxx20-error@-1 {{non-type template argument evaluates to nan which cannot be exactly represented in type 'float'}}
122+
#endif
123+
124+
}
125+
62126
namespace cwg2858 { // cwg2858: 19
63127

64128
#if __cplusplus > 202302L

0 commit comments

Comments
 (0)