Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 92 additions & 15 deletions include/iris/type_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,39 +223,116 @@ template<class T>
constexpr bool is_trivially_swappable_v = is_trivially_swappable<T>::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<class To, class From>
inline constexpr bool reference_converts_from_temporary_workaround_v =
#if defined(__cpp_lib_reference_from_temporary)
std::reference_converts_from_temporary_v<To, From>;
#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<class From, class To>
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<void, void>
template<class From, class To>
requires requires (From&& x) {
{ std::type_identity_t<To[]>{std::forward<From>(x)} } -> std::same_as<To[1]>;
}
struct is_convertible_without_narrowing_array_check<From, To>
: std::true_type
{};

// ----------------------------------------------

// Array of reference cannot be formed, handle special case.
template<class From, class To>
struct is_convertible_without_narrowing_dispatch
: is_convertible_without_narrowing_array_check<From, To>
{
static_assert(!std::is_reference_v<To>);
};

template<class From, class To>
using is_never_narrowing_family = std::disjunction<
std::is_same<std::remove_cvref_t<From>, std::remove_cvref_t<To>>,
std::is_base_of<std::remove_cvref_t<From>, std::remove_cvref_t<To>>,
std::is_function<std::remove_cvref_t<To>>,
std::is_array<std::remove_cvref_t<To>>
>;
template<class From, class To>
requires
std::is_reference_v<To> &&
is_never_narrowing_family<From, To>::value
struct is_convertible_without_narrowing_dispatch<From, To>
: std::true_type
{};

template<class From, class To>
requires
requires (From&& x) {
{ std::type_identity_t<To[]>{std::forward<From>(x)} } -> std::same_as<To[1]>;
}
std::is_reference_v<To> &&
(!is_never_narrowing_family<From, To>::value) &&
(!reference_converts_from_temporary_workaround_v<To, From>)
struct is_convertible_without_narrowing_dispatch<From, To>
: std::true_type
{};

template<class From, class To>
requires
std::is_reference_v<To> &&
(!is_never_narrowing_family<From, To>::value) &&
reference_converts_from_temporary_workaround_v<To, From>
struct is_convertible_without_narrowing_dispatch<From, To>
: is_convertible_without_narrowing_array_check<
From,
std::remove_reference_t<To> // temporary type is copy-list-initialized
>
{};

// ----------------------------------------------

template<class From, class To>
struct is_convertible_without_narrowing_impl
: is_convertible_without_narrowing_dispatch<From, To>
{};

// Corner case mentioned on the paper: void
// cv variants are already handled via `std::is_convertible`.
template<class From, class To>
requires std::is_void_v<To>
struct is_convertible_without_narrowing_impl<From, To>
: 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<class From, class To>
requires std::is_null_pointer_v<From> && std::same_as<std::remove_cvref_t<To>, bool>
struct is_convertible_without_narrowing_impl<From, To> : std::false_type
{};

} // namespace detail

template<class From, class To>
struct is_convertible_without_narrowing
: std::conjunction<
std::is_convertible<From, To>,
detail::is_convertible_without_narrowing_impl<From, To>
>
struct is_convertible_without_narrowing : std::false_type
{};

template<class From, class To>
requires std::is_convertible_v<From, To>
struct is_convertible_without_narrowing<From, To>
: detail::is_convertible_without_narrowing_impl<From, To>
{};

template<class From, class To>
Expand Down
118 changes: 76 additions & 42 deletions test/type_traits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,6 @@ struct n_tuple;
template<int... Ns>
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 {};
Expand All @@ -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<int>);
};

struct member_ptr_test
{
int member;
void func();
};

} // anonymous
Expand Down Expand Up @@ -266,8 +235,26 @@ TEST_CASE("is_convertible_without_narrowing: pointer types")
STATIC_CHECK(!iris::is_convertible_without_narrowing_v<void (member_ptr_test::*)(), int>);
}

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<int, float>);
STATIC_CHECK(!iris::is_convertible_without_narrowing_v<int const, float>);

// 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<std::integral_constant<int, 42>, 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<int, convertible_from_int>);
STATIC_CHECK(!iris::is_convertible_without_narrowing_v<convertible_from_int, int>);
Expand All @@ -276,15 +263,31 @@ TEST_CASE("is_convertible_without_narrowing: class types with implicit ctor")
STATIC_CHECK(!iris::is_convertible_without_narrowing_v<int, not_convertible_from_int>);
STATIC_CHECK(!iris::is_convertible_without_narrowing_v<not_convertible_from_int, int>);
STATIC_CHECK(!iris::is_convertible_without_narrowing_v<int, std::string>);
}

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<int, explicit_from_int>);
STATIC_CHECK(!iris::is_convertible_without_narrowing_v<explicit_from_int, int>);

// ------------------------------------------

struct convertible_to_double
{
operator double();
};
STATIC_CHECK(!iris::is_convertible_without_narrowing<double, float>::value);
STATIC_CHECK(!iris::is_convertible_without_narrowing<convertible_to_double, float>::value);
}


TEST_CASE("is_convertible_without_narrowing: class types with conversion operator")
{
// Implicit conversion operator
Expand All @@ -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<derived&, base>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<const derived&, base>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<derived const&, base>);
}

TEST_CASE("is_convertible_without_narrowing: aggregate types")
Expand Down Expand Up @@ -350,6 +353,9 @@ TEST_CASE("is_convertible_without_narrowing: void")
{
// void to void: is_convertible_v<void, void> is true per the standard.
STATIC_CHECK(iris::is_convertible_without_narrowing_v<void, void>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<void, void const>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<void const, void const>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<void const, void>);

// void to/from anything else: not convertible
STATIC_CHECK(!iris::is_convertible_without_narrowing_v<void, int>);
Expand All @@ -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<const int, int>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<int, const int>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<const int, long long>);
STATIC_CHECK(!iris::is_convertible_without_narrowing_v<const long long, int>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<volatile int, int>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<const volatile int, int>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<int const, int>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<int, int const>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<int const, long long>);
STATIC_CHECK(!iris::is_convertible_without_narrowing_v<long long const, int>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<int volatile, int>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<int const volatile, int>);
}

TEST_CASE("is_convertible_without_narrowing: reference types as From")
Expand All @@ -382,6 +388,34 @@ TEST_CASE("is_convertible_without_narrowing: reference types as From")
STATIC_CHECK(!iris::is_convertible_without_narrowing_v<long long&&, int>);
}

TEST_CASE("is_convertible_without_narrowing: reference types as To")
{
STATIC_CHECK(std::is_convertible_v<int&, int&>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<int&, int&>);

STATIC_CHECK(std::is_convertible_v<int&, int const&>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<int&, int const&>);

STATIC_CHECK(!std::is_convertible_v<int const&, int&>);
STATIC_CHECK(!iris::is_convertible_without_narrowing_v<int const&, int&>);

STATIC_CHECK(!std::is_convertible_v<int, int&>);
STATIC_CHECK(!iris::is_convertible_without_narrowing_v<int, int&>);

STATIC_CHECK(std::is_convertible_v<float, double const&>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<float, double const&>);

// --------------------------------------------------

using F = void();

STATIC_CHECK(std::is_convertible_v<F&, F&>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<F&, F&>);

STATIC_CHECK(std::is_convertible_v<F, F&>);
STATIC_CHECK(iris::is_convertible_without_narrowing_v<F, F&>);
}

TEST_CASE("is_convertible_without_narrowing: function pointers")
{
using fp = void(*)();
Expand Down
Loading