diff --git a/include/iris/rvariant/rvariant.hpp b/include/iris/rvariant/rvariant.hpp index b30d5ba..9d05b3b 100644 --- a/include/iris/rvariant/rvariant.hpp +++ b/include/iris/rvariant/rvariant.hpp @@ -620,10 +620,10 @@ class rvariant : private detail::rvariant_base_t (!std::is_same_v, rvariant>) && (!is_ttp_specialization_of_v, std::in_place_type_t>) && (!is_ctp_specialization_of_v, std::in_place_index_t>) && - std::is_constructible_v::type, T> + std::is_constructible_v::type, T> constexpr /* not explicit */ rvariant(T&& t) - noexcept(std::is_nothrow_constructible_v::type, T>) - : base_type(std::in_place_index::index>, std::forward(t)) + noexcept(std::is_nothrow_constructible_v::type, T>) + : base_type(std::in_place_index::index>, std::forward(t)) {} IRIS_RVARIANT_ALWAYS_THROWING_UNREACHABLE_BEGIN @@ -632,12 +632,12 @@ IRIS_RVARIANT_ALWAYS_THROWING_UNREACHABLE_BEGIN template requires (!std::is_same_v, rvariant>) && - detail::variant_assignable::type, T>::value + detail::variant_assignable::type, T>::value constexpr rvariant& operator=(T&& t) - noexcept(detail::variant_nothrow_assignable::type, T>::value) + noexcept(detail::variant_nothrow_assignable::type, T>::value) { - using Tj = aggregate_initialize_resolution::type; // either plain type or wrapped with recursive_wrapper - constexpr std::size_t j = aggregate_initialize_resolution::index; + using Tj = no_narrowing_resolution::type; // either plain type or wrapped with recursive_wrapper + constexpr std::size_t j = no_narrowing_resolution::index; static_assert(j != std::variant_npos); this->raw_visit([this, &t](std::in_place_index_t, [[maybe_unused]] Ti& ti) diff --git a/include/iris/type_traits.hpp b/include/iris/type_traits.hpp index 7044a2c..c225856 100644 --- a/include/iris/type_traits.hpp +++ b/include/iris/type_traits.hpp @@ -10,6 +10,7 @@ #include +#include #include #include @@ -222,55 +223,87 @@ 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 { + +template +struct is_convertible_without_narrowing_impl + : 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 + : std::true_type +{}; + +template + requires + requires (From&& x) { + { std::type_identity_t{std::forward(x)} } -> std::same_as; + } +struct is_convertible_without_narrowing_impl + : std::true_type +{}; + +} // namespace detail + +template +struct is_convertible_without_narrowing + : std::conjunction< + std::is_convertible, + detail::is_convertible_without_narrowing_impl + > +{}; + +template +inline constexpr bool is_convertible_without_narrowing_v = is_convertible_without_narrowing::value; + + namespace detail { template -struct aggregate_initialize_tag +struct no_narrowing_tag { static constexpr std::size_t index = I; using type = Ti; }; -// This version works better than MSVC's, does not break IntelliSense or ReSharper template -struct aggregate_initialize_overload +struct no_narrowing_overload { - using TiA = Ti[]; - - // https://eel.is/c++draft/dcl.init.general#14 - // https://eel.is/c++draft/dcl.init.list#3.4 - // https://eel.is/c++draft/dcl.init.aggr#3 - template - auto operator()(Ti, T&&) -> aggregate_initialize_tag - requires requires(T&& t) { { TiA{std::forward(t)} }; } // emulate `Ti x[] = {std::forward(t)};` + auto operator()(Ti, T&&) -> no_narrowing_tag + requires is_convertible_without_narrowing_v { return {}; // silence MSVC warning } }; template -struct aggregate_initialize_fun; +struct no_narrowing_fun; // Imaginary function FUN of https://eel.is/c++draft/variant#ctor-14 template -struct aggregate_initialize_fun, Ts...> - : aggregate_initialize_overload... +struct no_narrowing_fun, Ts...> + : no_narrowing_overload... { - using aggregate_initialize_overload::operator()...; + using no_narrowing_overload::operator()...; }; template -using aggregate_initialize_fun_for = aggregate_initialize_fun, Ts...>; +using no_narrowing_fun_for = no_narrowing_fun, Ts...>; template -struct aggregate_initialize_resolution {}; +struct no_narrowing_resolution {}; template -struct aggregate_initialize_resolution< - std::void_t{}(std::declval(), std::declval()))>, T, Ts... +struct no_narrowing_resolution< + std::void_t{}(std::declval(), std::declval()))>, T, Ts... > { - using tag = decltype(aggregate_initialize_fun_for{}(std::declval(), std::declval())); + using tag = decltype(no_narrowing_fun_for{}(std::declval(), std::declval())); using type = tag::type; static constexpr std::size_t index = tag::index; }; @@ -281,7 +314,7 @@ struct aggregate_initialize_resolution< // because they would lead to unnecessarily nested instantiation for // legitimate infinite recursion errors on recursive types. template -struct aggregate_initialize_resolution : detail::aggregate_initialize_resolution {}; +struct no_narrowing_resolution : detail::no_narrowing_resolution {}; } // iris diff --git a/test/type_traits.cpp b/test/type_traits.cpp index 59eae66..755e492 100644 --- a/test/type_traits.cpp +++ b/test/type_traits.cpp @@ -19,9 +19,406 @@ 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 {}; +enum unscoped_enum_short : short {}; + +struct base {}; +struct derived : base {}; + +struct implicit_conversion_op +{ + operator int() const; +}; + +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 +TEST_CASE("is_convertible_without_narrowing: same type identity") +{ + 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); + 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: integer widening") +{ + 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: integer narrowing") +{ + 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: signed/unsigned mismatch") +{ + 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: floating-point widening") +{ + 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: floating-point narrowing") +{ + 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: integer/floating-point cross") +{ + // Integer to floating-point: narrowing + 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); + + // Floating-point to integer: narrowing + 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: bool") +{ + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + + // bool to integer: widening (bool is an integer type with rank < int) + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + + // Integer/float to bool: narrowing + 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: char types") +{ + // char8_t, char16_t, char32_t are distinct types with specific widths + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // wchar_t cross: platform-dependent (wchar_t width varies) + // On platforms where sizeof(wchar_t) == sizeof(int), these may be non-narrowing. + + // char to int: widening + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + + // int to char: narrowing + 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: enum types") +{ + // Scoped enums: not implicitly convertible to/from anything + 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); + + // Unscoped enums: implicitly convertible to integer types + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // Unscoped enum with fixed underlying type (short) + 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: pointer types") +{ + // Same pointer type + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + + // Derived-to-base pointer conversion + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // T* to void* + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // Adding const via pointer + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // nullptr_t + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // pointer to bool: narrowing (all pointers/nullptr to bool is narrowing) + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // Pointer to/from arithmetic: not convertible + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // Pointer-to-member + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // Member function pointer + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); +} + +TEST_CASE("is_convertible_without_narrowing: class types with implicit ctor") +{ + // Implicit converting constructor + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // Not convertible at all + 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") +{ + // 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); +} + +TEST_CASE("is_convertible_without_narrowing: class types with conversion operator") +{ + // Implicit conversion operator + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // Explicit conversion operator: is_convertible is false + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); +} + +TEST_CASE("is_convertible_without_narrowing: class types inheritance") +{ + // Derived to base (by value): implicitly convertible + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + // Base to derived: not implicitly convertible + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // Derived& to base: implicitly convertible + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); +} + +TEST_CASE("is_convertible_without_narrowing: aggregate types") +{ + // Aggregates with brace-init but no implicit conversion + { + struct S { + union { + int x; + float y; + } u; + }; + [[maybe_unused]] S s{42}; + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + } + { + struct S { + int x[1]; + }; + [[maybe_unused]] S s{42}; + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + } + { + struct S { + struct { + int x; + } inner; + }; + [[maybe_unused]] S s{42}; + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + } + { + struct S { + int x; + double y; + }; + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + } +} + +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); + + // void to/from anything else: not 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: 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); +} + +TEST_CASE("is_convertible_without_narrowing: reference types as From") +{ + // Lvalue reference + 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); + + // Rvalue reference + 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: function pointers") +{ + using fp = void(*)(); + using fp2 = int(*)(double); + + 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); + + // Function pointer to bool: narrowing + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // Function pointer to void*: not allowe per standard, but MSVC accepts this conversion + // STATIC_CHECK(!iris::is_convertible_without_narrowing_v); +} + +TEST_CASE("is_convertible_without_narrowing: array and function types") +{ + // Array types as From decay to pointers + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // Element types to array types + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // char[] to string + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + + // Function type decays to function pointer + using fn = void(); + using fnp = void(*)(); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); +} + + TEST_CASE("specialization_of") { STATIC_CHECK(iris::is_ttp_specialization_of_v, tuple>);