Robin Caloudis 2fe59d5259
[libc++][math] Fix acceptance of convertible types in std::isnan() and std::isinf() (#98952)
Following up on https://github.com/llvm/llvm-project/pull/98841.

Changes:
- Properly test convertible types for `std::isnan()` and `std::inf()`
- Tighten conditional in `cmath.pass.cpp` (Find insights on `_LIBCPP_PREFERRED_OVERLOAD` below)
- Tighten preprocessor guard in `traits.h`

Insights into why `_LIBCPP_PREFERRED_OVERLOAD` is needed:

(i) When libc++ is layered on top of glibc on Linux, glibc's `math.h` is
    included. When compiling with `-std=c++03`, this header brings the
    function declaration of `isinf(double)` [1] and `isnan(double)` [2]
    into scope. This differs from the C99 Standard as only the macros
    `#define isnan(arg)` and `#define isinf(arg)` are expected.

    Therefore, libc++ needs to respect the presense of the `double` overload
    and cannot redefine it as it will conflict with the declaration already
    in scope. For `-std=c++11` and beyond this issue is fixed, as glibc
    guards both the `isinf` and `isnan` by preprocessor macros.

(ii) When libc++ is layered on top of Bionic's libc, `math.h` exposes a
     function prototype for `isinf(double)` with return type `int`. This
     function prototype in Bionic's libc is not guarded by any preprocessor
     macros [3].

`_LIBCPP_PREFERRED_OVERLOAD` specifies that a given overload is a better match
than an otherwise equally good function declaration. This is implemented in
modern versions of Clang via `__attribute__((__enable_if__))`, and not elsewhere.
See [4] for details. We use `_LIBCPP_PREFERRED_OVERLOAD` to define overloads in
the global namespace that displace the overloads provided by the C
libraries mentioned above.

[1]: fe94080875/math/bits/mathcalls.h (L185-L194)
[2]: fe94080875/math/bits/mathcalls.h (L222-L231)
[3]: https://cs.android.com/android/platform/superproject/+/master:bionic/libc/include/math.h;l=322-323;drc=master?hl=fr-BE%22https:%2F%2Fsupport.google.com%2Fmerchants%2Fanswer%2F188494%5C%22%22https:%2F%2Fsupport.google.com%2Fmerchants%2Fanswer%2F188494%5C%22
[4]: 5fd17ab1b0
2024-08-16 11:18:49 -04:00

83 lines
2.2 KiB
C++

//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// bool isfinite(floating-point-type x); // constexpr since C++23
// We don't control the implementation on windows
// UNSUPPORTED: windows
#include <cassert>
#include <cmath>
#include <limits>
#include "test_macros.h"
#include "type_algorithms.h"
struct TestFloat {
template <class T>
static TEST_CONSTEXPR_CXX23 bool test() {
assert(!std::isnan(std::numeric_limits<T>::max()));
assert(!std::isnan(std::numeric_limits<T>::infinity()));
assert(!std::isnan(std::numeric_limits<T>::min()));
assert(!std::isnan(std::numeric_limits<T>::denorm_min()));
assert(!std::isnan(std::numeric_limits<T>::lowest()));
assert(!std::isnan(-std::numeric_limits<T>::infinity()));
assert(!std::isnan(T(0)));
assert(std::isnan(std::numeric_limits<T>::quiet_NaN()));
assert(std::isnan(std::numeric_limits<T>::signaling_NaN()));
return true;
}
template <class T>
TEST_CONSTEXPR_CXX23 void operator()() {
test<T>();
#if TEST_STD_VER >= 23
static_assert(test<T>());
#endif
}
};
struct TestInt {
template <class T>
static TEST_CONSTEXPR_CXX23 bool test() {
assert(!std::isnan(std::numeric_limits<T>::max()));
assert(!std::isnan(std::numeric_limits<T>::lowest()));
assert(!std::isnan(T(0)));
return true;
}
template <class T>
TEST_CONSTEXPR_CXX23 void operator()() {
test<T>();
#if TEST_STD_VER >= 23
static_assert(test<T>());
#endif
}
};
template <typename T>
struct ConvertibleTo {
operator T() const { return T(); }
};
int main(int, char**) {
types::for_each(types::floating_point_types(), TestFloat());
types::for_each(types::integral_types(), TestInt());
// Make sure we can call `std::isnan` with convertible types
{
assert(!std::isnan(ConvertibleTo<float>()));
assert(!std::isnan(ConvertibleTo<double>()));
assert(!std::isnan(ConvertibleTo<long double>()));
}
return 0;
}