diff --git a/include/iris/type_traits.hpp b/include/iris/type_traits.hpp index c225856..1b768e2 100644 --- a/include/iris/type_traits.hpp +++ b/include/iris/type_traits.hpp @@ -223,39 +223,116 @@ template constexpr bool is_trivially_swappable_v = is_trivially_swappable::value; -// P0870R7: is_convertible_without_narrowing -// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p0870r7.html namespace detail { +// Workaround for MSVC 2026's Intellisense +template +inline constexpr bool reference_converts_from_temporary_workaround_v = +#if defined(__cpp_lib_reference_from_temporary) + std::reference_converts_from_temporary_v; +#elif defined(__has_builtin) && __has_builtin(__reference_converts_from_temporary) + __reference_converts_from_temporary(To, From); +#else + true; +#endif + +// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p0870r8.html + template -struct is_convertible_without_narrowing_impl +struct is_convertible_without_narrowing_array_check : std::false_type {}; -// void to void "conversion" is valid since `is_convertible` is defined -// as "returning `From` is valid for function whose return type is `To`?" -template<> -struct is_convertible_without_narrowing_impl +template + requires requires (From&& x) { + { std::type_identity_t{std::forward(x)} } -> std::same_as; + } +struct is_convertible_without_narrowing_array_check + : std::true_type +{}; + +// ---------------------------------------------- + +// Array of reference cannot be formed, handle special case. +template +struct is_convertible_without_narrowing_dispatch + : is_convertible_without_narrowing_array_check +{ + static_assert(!std::is_reference_v); +}; + +template +using is_never_narrowing_family = std::disjunction< + std::is_same, std::remove_cvref_t>, + std::is_base_of, std::remove_cvref_t>, + std::is_function>, + std::is_array> +>; +template + requires + std::is_reference_v && + is_never_narrowing_family::value +struct is_convertible_without_narrowing_dispatch : std::true_type {}; template requires - requires (From&& x) { - { std::type_identity_t{std::forward(x)} } -> std::same_as; - } + std::is_reference_v && + (!is_never_narrowing_family::value) && + (!reference_converts_from_temporary_workaround_v) +struct is_convertible_without_narrowing_dispatch + : std::true_type +{}; + +template + requires + std::is_reference_v && + (!is_never_narrowing_family::value) && + reference_converts_from_temporary_workaround_v +struct is_convertible_without_narrowing_dispatch + : is_convertible_without_narrowing_array_check< + From, + std::remove_reference_t // temporary type is copy-list-initialized + > +{}; + +// ---------------------------------------------- + +template +struct is_convertible_without_narrowing_impl + : is_convertible_without_narrowing_dispatch +{}; + +// Corner case mentioned on the paper: void +// cv variants are already handled via `std::is_convertible`. +template + requires std::is_void_v struct is_convertible_without_narrowing_impl : std::true_type {}; +// DR11: Converting from T* to bool should be considered narrowing +// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1957r2.html +// +// This is already applied to all major vendors, but some implementations +// disagree with `std::nullptr_t`. Note that `std::nullptr_t` is NOT a +// pointer type, so it cannot be checked with `std::is_pointer`. +template + requires std::is_null_pointer_v && std::same_as, bool> +struct is_convertible_without_narrowing_impl : std::false_type +{}; + } // namespace detail template -struct is_convertible_without_narrowing - : std::conjunction< - std::is_convertible, - detail::is_convertible_without_narrowing_impl - > +struct is_convertible_without_narrowing : std::false_type +{}; + +template + requires std::is_convertible_v +struct is_convertible_without_narrowing + : detail::is_convertible_without_narrowing_impl {}; template diff --git a/test/type_traits.cpp b/test/type_traits.cpp index 755e492..a489ea5 100644 --- a/test/type_traits.cpp +++ b/test/type_traits.cpp @@ -19,20 +19,6 @@ struct n_tuple; template struct n_list; -struct convertible_from_int -{ - convertible_from_int(int); -}; - -struct not_convertible_from_int {}; - -struct explicit_from_int -{ - explicit explicit_from_int() = default; - explicit explicit_from_int(int); - explicit_from_int& operator=(int) { return *this; } -}; - enum class scoped_enum {}; enum unscoped_enum {}; enum class scoped_enum_uint8 : unsigned char {}; @@ -51,25 +37,8 @@ struct explicit_conversion_op explicit operator int() const; }; -struct abstract_class -{ - virtual void f() = 0; -}; - -struct multi_arg_implicit -{ - multi_arg_implicit(int, double); -}; - -struct has_initializer_list_ctor -{ - has_initializer_list_ctor(std::initializer_list); -}; - struct member_ptr_test { - int member; - void func(); }; } // anonymous @@ -266,8 +235,26 @@ TEST_CASE("is_convertible_without_narrowing: pointer types") STATIC_CHECK(!iris::is_convertible_without_narrowing_v); } -TEST_CASE("is_convertible_without_narrowing: class types with implicit ctor") +TEST_CASE("is_convertible_without_narrowing: constexpr caveat") { + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p0870r8.html#ch5.9 + // The paper wants true in this case. + // TODO: GCC/Clang and MSVC(2026) disagree; unfixable + //STATIC_CHECK(iris::is_convertible_without_narrowing_v, float>); +} + +TEST_CASE("is_convertible_without_narrowing: class types with conversion") +{ + struct convertible_from_int + { + convertible_from_int(int); + }; + + struct not_convertible_from_int {}; + // Implicit converting constructor STATIC_CHECK(iris::is_convertible_without_narrowing_v); STATIC_CHECK(!iris::is_convertible_without_narrowing_v); @@ -276,15 +263,31 @@ TEST_CASE("is_convertible_without_narrowing: class types with implicit ctor") STATIC_CHECK(!iris::is_convertible_without_narrowing_v); STATIC_CHECK(!iris::is_convertible_without_narrowing_v); STATIC_CHECK(!iris::is_convertible_without_narrowing_v); -} -TEST_CASE("is_convertible_without_narrowing: class types with explicit ctor") -{ + // ------------------------------------------ + + struct explicit_from_int + { + explicit explicit_from_int() = default; + explicit explicit_from_int(int) {} + explicit_from_int& operator=(int) { return *this; } + }; + // Explicit ctor: is_convertible is false, so trait is false STATIC_CHECK(!iris::is_convertible_without_narrowing_v); STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // ------------------------------------------ + + struct convertible_to_double + { + operator double(); + }; + STATIC_CHECK(!iris::is_convertible_without_narrowing::value); + STATIC_CHECK(!iris::is_convertible_without_narrowing::value); } + TEST_CASE("is_convertible_without_narrowing: class types with conversion operator") { // Implicit conversion operator @@ -304,7 +307,7 @@ TEST_CASE("is_convertible_without_narrowing: class types inheritance") // Derived& to base: implicitly convertible STATIC_CHECK(iris::is_convertible_without_narrowing_v); - STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); } TEST_CASE("is_convertible_without_narrowing: aggregate types") @@ -350,6 +353,9 @@ TEST_CASE("is_convertible_without_narrowing: void") { // void to void: is_convertible_v is true per the standard. STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); // void to/from anything else: not convertible STATIC_CHECK(!iris::is_convertible_without_narrowing_v); @@ -360,12 +366,12 @@ TEST_CASE("is_convertible_without_narrowing: void") TEST_CASE("is_convertible_without_narrowing: cv-qualified types") { // const arithmetic: same narrowing rules - STATIC_CHECK(iris::is_convertible_without_narrowing_v); - STATIC_CHECK(iris::is_convertible_without_narrowing_v); - STATIC_CHECK(iris::is_convertible_without_narrowing_v); - STATIC_CHECK(!iris::is_convertible_without_narrowing_v); - STATIC_CHECK(iris::is_convertible_without_narrowing_v); - STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); } TEST_CASE("is_convertible_without_narrowing: reference types as From") @@ -382,6 +388,34 @@ TEST_CASE("is_convertible_without_narrowing: reference types as From") STATIC_CHECK(!iris::is_convertible_without_narrowing_v); } +TEST_CASE("is_convertible_without_narrowing: reference types as To") +{ + STATIC_CHECK(std::is_convertible_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + + STATIC_CHECK(std::is_convertible_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + + STATIC_CHECK(!std::is_convertible_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + STATIC_CHECK(!std::is_convertible_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + STATIC_CHECK(std::is_convertible_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + + // -------------------------------------------------- + + using F = void(); + + STATIC_CHECK(std::is_convertible_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + + STATIC_CHECK(std::is_convertible_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); +} + TEST_CASE("is_convertible_without_narrowing: function pointers") { using fp = void(*)();