From cb354f2126fb1eae9254414cbe9fc77256ab384d Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Mon, 28 Sep 2026 22:41:48 +0900 Subject: [PATCH] Add `is_*_without_any_narrowing` family that checks user-defined conversion --- include/iris/type_traits.hpp | 286 ++++++++++++++++++----- test/CMakeLists.txt | 1 + test/narrowing.cpp | 427 +++++++++++++++++++++++++++++++++++ test/type_traits.cpp | 426 ---------------------------------- 4 files changed, 662 insertions(+), 478 deletions(-) create mode 100644 test/narrowing.cpp diff --git a/include/iris/type_traits.hpp b/include/iris/type_traits.hpp index a27fb64..4a9ece7 100644 --- a/include/iris/type_traits.hpp +++ b/include/iris/type_traits.hpp @@ -9,6 +9,7 @@ #include // IWYU pragma: export #include +#include #include // IWYU pragma: export #include @@ -193,6 +194,24 @@ template concept weakly_assignable_from = std::is_assignable_v; +template +// ReSharper disable once CppFunctionDoesntReturnValue +[[nodiscard]] T declval_exact() noexcept +{ + // ReSharper disable once CppStaticAssertFailure + static_assert(false, "declval_exact() must not be odr-used"); +} + +template +// ReSharper disable once CppFunctionDoesntReturnValue +[[nodiscard]] T copy_initialize(T) noexcept +{ + // ReSharper disable once CppStaticAssertFailure + static_assert(false, "copy_initialize() must not be odr-used"); +} + +// ---------------------------------------------- + namespace detail { // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p0870r8.html @@ -212,14 +231,6 @@ struct is_convertible_without_narrowing_array_check // ---------------------------------------------- -// 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>, @@ -227,47 +238,52 @@ using is_never_narrowing_family = std::disjunction< 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 +template +using reference_binds_to_temporary = std::conditional_t< + Direct, + std::reference_constructs_from_temporary, + std::reference_converts_from_temporary +>; + +// `Check` judges the initialization of a non-reference `To`. +// Array of reference cannot be formed, so a reference is judged by the temporary it binds to. +template class Check, bool Direct, class From, class To> +struct narrowing_check_dispatch + : Check +{ + static_assert(!std::is_reference_v); +}; + +template class Check, bool Direct, class From, class To> requires std::is_reference_v && - (!is_never_narrowing_family::value) && - (!std::reference_converts_from_temporary_v) -struct is_convertible_without_narrowing_dispatch + (is_never_narrowing_family::value || !reference_binds_to_temporary::value) +struct narrowing_check_dispatch : std::true_type {}; -template +template class Check, bool Direct, class From, class To> requires std::is_reference_v && (!is_never_narrowing_family::value) && - std::reference_converts_from_temporary_v -struct is_convertible_without_narrowing_dispatch - : is_convertible_without_narrowing_array_check< - From, - std::remove_reference_t // temporary type is copy-list-initialized - > + reference_binds_to_temporary::value +struct narrowing_check_dispatch + : Check> // temporary is copy-initialized {}; // ---------------------------------------------- -template -struct is_convertible_without_narrowing_impl - : is_convertible_without_narrowing_dispatch +template class Check, class From, class To> +struct convertible_narrowing_check + : narrowing_check_dispatch {}; // Corner case mentioned on the paper: void // cv variants are already handled via `std::is_convertible`. -template +template class Check, class From, class To> requires std::is_void_v -struct is_convertible_without_narrowing_impl +struct convertible_narrowing_check : std::true_type {}; @@ -277,9 +293,10 @@ struct is_convertible_without_narrowing_impl // 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 +template class Check, class From, class To> requires std::is_null_pointer_v && std::same_as, bool> -struct is_convertible_without_narrowing_impl : std::false_type +struct convertible_narrowing_check + : std::false_type {}; } // namespace detail @@ -291,12 +308,195 @@ 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 + : detail::convertible_narrowing_check {}; template inline constexpr bool is_convertible_without_narrowing_v = is_convertible_without_narrowing::value; +// ---------------------------------------------- + +namespace detail { + +template +concept class_or_union = std::is_class_v || std::is_union_v; + +// Unlike `std::is_convertible_v`, false for an incomplete or abstract `T` +template +concept copy_initializable = requires { iris::copy_initialize(std::declval()); }; + +template +concept copy_list_initializable = requires { iris::copy_initialize>({std::declval()}); }; + +template +concept direct_list_initializable = requires { std::type_identity_t>{std::declval()...}; }; + +template +concept list_assignable = requires { std::declval() = {std::declval()}; }; + +// ---------------------------------------------- + +struct opaque_checker +{ + opaque_checker() = default; + opaque_checker(opaque_checker const&) = delete; +}; + +template +struct initializer_list_checker : opaque_checker +{ + template + requires (copy_initializable && ...) + operator std::initializer_list() const; +}; + +// `T{args...}` selects an initializer-list constructor regardless of narrowing +template +concept selects_initializer_list = + !std::is_aggregate_v && + ( + (!std::is_constructible_v && + std::is_constructible_v const&>) || + (direct_list_initializable && !std::is_constructible_v) + ); + +template +concept selects_initializer_list_assignment = + selects_initializer_list, U> || + std::is_assignable_v const&>; + +// ---------------------------------------------- + +template +concept copy_initializable_without_any_narrowing = + !selects_initializer_list && + ( + (class_or_union && class_or_union>) || + copy_list_initializable + ); + +template +struct copy_initialization_narrowing_check + : std::bool_constant> +{}; + +// ---------------------------------------------- + +template +struct class_checker : opaque_checker +{ + template + requires std::is_scalar_v

&& std::is_convertible_v + operator P() const = delete; + + template + requires + class_or_union && copy_initializable && + (!Strict || copy_initializable_without_any_narrowing) + operator Q() const; +}; + +template +struct value_checker : opaque_checker +{ + operator std::add_rvalue_reference_t() const; +}; + +template +struct direct_initialization_narrowing_check + : std::false_type +{}; + +template + requires + (!selects_initializer_list) && + direct_list_initializable && + std::is_constructible_v< + T, + std::conditional_t>, Args, value_checker>... + > +struct direct_initialization_narrowing_check + : std::true_type +{}; + +template +struct direct_initialization_narrowing_check + : std::true_type +{}; + +template +concept direct_initializable_without_any_narrowing = + !selects_initializer_list && + direct_list_initializable && + ( + // `T{v}` may not check narrowing in the converting constructor of a class-type parameter + !std::is_constructible_v> || + std::is_constructible_v> + ); + +template +struct direct_initialization_narrowing_check + : std::bool_constant> +{}; + +template + requires std::is_reference_v +struct direct_initialization_narrowing_check + : narrowing_check_dispatch +{}; + +// See the note on P1957R2 above +template + requires std::is_null_pointer_v> && std::same_as, bool> +struct direct_initialization_narrowing_check + : std::false_type +{}; + +} // detail + +// is_convertible_without_narrowing + also rejects user-defined conversion in constructor +template +struct is_convertible_without_any_narrowing : std::false_type +{}; + +template + requires std::is_convertible_v +struct is_convertible_without_any_narrowing + : detail::convertible_narrowing_check +{}; + +template +inline constexpr bool is_convertible_without_any_narrowing_v = is_convertible_without_any_narrowing::value; + +template +struct is_constructible_without_any_narrowing : std::false_type +{}; + +template + requires std::is_constructible_v +struct is_constructible_without_any_narrowing + : detail::direct_initialization_narrowing_check +{}; + +template +inline constexpr bool is_constructible_without_any_narrowing_v = is_constructible_without_any_narrowing::value; + +template +struct is_assignable_without_any_narrowing : std::false_type +{}; + +template + requires + std::is_assignable_v && + (!detail::selects_initializer_list_assignment) && + detail::list_assignable +struct is_assignable_without_any_narrowing + : std::true_type +{}; + +template +inline constexpr bool is_assignable_without_any_narrowing_v = is_assignable_without_any_narrowing::value; + namespace detail { @@ -354,24 +554,6 @@ struct no_narrowing_resolution : detail::no_narrowing_resolution // ---------------------------------------------- -template -// ReSharper disable once CppFunctionDoesntReturnValue -[[nodiscard]] T declval_exact() noexcept -{ - // ReSharper disable once CppStaticAssertFailure - static_assert(false, "declval_exact() must not be odr-used"); -} - -template -// ReSharper disable once CppFunctionDoesntReturnValue -[[nodiscard]] T copy_initialize(T) noexcept -{ - // ReSharper disable once CppStaticAssertFailure - static_assert(false, "copy_initialize() must not be odr-used"); -} - -// ---------------------------------------------- - namespace detail { // The conversion part of INVOKE: `To` is cv void, or VAL can be diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a03ca6b..f4163f4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -183,6 +183,7 @@ if(PROJECT_IS_TOP_LEVEL) core type_list type_traits + narrowing ranges container_traits stdint diff --git a/test/narrowing.cpp b/test/narrowing.cpp new file mode 100644 index 0000000..a4f2276 --- /dev/null +++ b/test/narrowing.cpp @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: MIT + +#include "iris_test.hpp" + +#include + +#include +#include +#include +#include +#include + +#include // IWYU pragma: keep + +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 member_ptr_test +{ +}; + +TEST_CASE("is_convertible_without_narrowing", "[type_traits]") +{ + 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); + + 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); + 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); + 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); + 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); + 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); + + // 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>); + + struct convertible_from_int + { + convertible_from_int(int); + }; + + struct not_convertible_from_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); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + struct explicit_from_int + { + explicit explicit_from_int() = default; + explicit explicit_from_int(int) {} + explicit_from_int& operator=(int) { return *this; } + }; + + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + struct convertible_to_double + { + operator double(); + }; + + // ReSharper disable once CppStaticAssertFailure + 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); + + { + 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); + } + + 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); + STATIC_CHECK(iris::is_convertible_without_narrowing_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); + + 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); + + 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); + + STATIC_CHECK(!iris::is_convertible_without_narrowing_v); + + // Function pointer to void*: not allowed as per standard, but MSVC accepts this conversion + // 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); + + using fn = void(); + using fnp = void(*)(); + STATIC_CHECK(iris::is_convertible_without_narrowing_v); +} + +// ---------------------------------------------- + +struct port +{ + port(int); +}; + +struct explicit_port +{ + explicit explicit_port(int); +}; + +struct to_long_long +{ + operator long long() const; +}; + +struct aggregate +{ + [[maybe_unused]] int i; + [[maybe_unused]] double d; +}; + +struct aggregate_port +{ + [[maybe_unused]] port p; +}; + +struct takes_port_int +{ + takes_port_int(port, int); +}; + +struct value +{ + value(long long); + value(std::string); + value(std::initializer_list); +}; + +struct assign_int +{ + assign_int& operator=(int); +}; + +TEST_CASE("is_convertible_without_any_narrowing", "[type_traits]") +{ + STATIC_CHECK(iris::is_convertible_without_any_narrowing_v); + STATIC_CHECK(!iris::is_convertible_without_any_narrowing_v); + // ReSharper disable once CppStaticAssertFailure + STATIC_CHECK(!iris::is_convertible_without_any_narrowing_v); + + STATIC_CHECK(iris::is_convertible_without_narrowing_v); + // ReSharper disable once CppStaticAssertFailure + STATIC_CHECK(!iris::is_convertible_without_any_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_any_narrowing_v); + // ReSharper disable once CppStaticAssertFailure + STATIC_CHECK(!iris::is_convertible_without_any_narrowing_v); + STATIC_CHECK(iris::is_convertible_without_any_narrowing_v>); // narrowing in the body + STATIC_CHECK(iris::is_convertible_without_any_narrowing_v); + + STATIC_CHECK(!iris::is_convertible_without_any_narrowing_v); // initializer-list constructor + STATIC_CHECK(!iris::is_convertible_without_any_narrowing_v); // initializer-list constructor +} + +TEST_CASE("is_constructible_without_any_narrowing", "[type_traits]") +{ + STATIC_CHECK(iris::is_constructible_without_any_narrowing_v>); + STATIC_CHECK(!iris::is_constructible_without_any_narrowing_v); + STATIC_CHECK(!iris::is_constructible_without_any_narrowing_v); + STATIC_CHECK(iris::is_constructible_without_any_narrowing_v); + // ReSharper disable once CppStaticAssertFailure + STATIC_CHECK(!iris::is_constructible_without_any_narrowing_v); + // ReSharper disable once CppStaticAssertFailure + STATIC_CHECK(!iris::is_constructible_without_any_narrowing_v); + STATIC_CHECK(iris::is_constructible_without_any_narrowing_v, long long>); // narrowing in the body + + // ReSharper disable once CppStaticAssertFailure + STATIC_CHECK(!iris::is_constructible_without_any_narrowing_v); + STATIC_CHECK(iris::is_constructible_without_any_narrowing_v); + STATIC_CHECK(!iris::is_constructible_without_any_narrowing_v); + STATIC_CHECK(iris::is_constructible_without_any_narrowing_v); + STATIC_CHECK(!iris::is_constructible_without_any_narrowing_v); // limitation + + STATIC_CHECK(!iris::is_constructible_without_any_narrowing_v, std::size_t>); // initializer-list constructor + STATIC_CHECK(!iris::is_constructible_without_any_narrowing_v); // initializer-list constructor +} + +TEST_CASE("is_assignable_without_any_narrowing", "[type_traits]") +{ + STATIC_CHECK(!iris::is_assignable_without_any_narrowing_v); + STATIC_CHECK(iris::is_assignable_without_any_narrowing_v); + STATIC_CHECK(!iris::is_assignable_without_any_narrowing_v); + STATIC_CHECK(iris::is_assignable_without_any_narrowing_v); + // ReSharper disable once CppStaticAssertFailure + STATIC_CHECK(!iris::is_assignable_without_any_narrowing_v); + STATIC_CHECK(iris::is_assignable_without_any_narrowing_v); + + STATIC_CHECK(!iris::is_assignable_without_any_narrowing_v); // initializer-list assignment +} diff --git a/test/type_traits.cpp b/test/type_traits.cpp index 9f7fbc8..bbc3e75 100644 --- a/test/type_traits.cpp +++ b/test/type_traits.cpp @@ -28,28 +28,6 @@ struct n_tuple; template struct n_list; -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 member_ptr_test -{ -}; - TEST_CASE("type traits") { STATIC_CHECK(iris::signed_numeric_integral); @@ -208,410 +186,6 @@ TEMPLATE_TEST_CASE("symmetric_common_type", "[type_traits]", IRIS_TEST_NARROW_FP #undef IRIS_TEST_NARROW_FP_PAIRS -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: 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); - - // 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); - - // ------------------------------------------ - - 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); - // ReSharper disable once CppStaticAssertFailure - STATIC_CHECK(!iris::is_convertible_without_narrowing::value); -} - - -TEST_CASE("is_convertible_without_narrowing: class types with conversion operator") -{ - 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 inheritance") -{ - 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: 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); - 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); - 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: 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(*)(); - 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 allowed as 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>);