Skip to content

Instantly share code, notes, and snippets.

@seiren-naru-shirayuri
Last active February 28, 2022 14:56
Show Gist options
  • Save seiren-naru-shirayuri/22f3cc2cc6c1d83e58695882736afa9b to your computer and use it in GitHub Desktop.
Save seiren-naru-shirayuri/22f3cc2cc6c1d83e58695882736afa9b to your computer and use it in GitHub Desktop.
A trait that test if one type is implicitly convertible to another type without narrowing.
!is_non_narrowing_convertible

A trait that test if one type is implicitly convertible to another type without narrowing.

Tested on MSVC 19.29 with /std:c++20, GCC 11.2 with -std=c++20, clang 12 (clang-cl) with /std:c++latest.

This gist is released under GLWTPL.

2021/12/21 update:

  1. implement P1957R2, which considers pointer-to-bool conversion as narrowing.
  2. add is_nothrow_non_narrowing_convertible which detects non-throwing-ness besides non-narrowing-ness.

is_non_narrowing_convertible, is_nothrow_non_narrowing_convertible

template <typename From, typename To>
struct is_non_narrowing_convertible;
template<typename From, typename To>
struct is_nothrow_non_narrowing_convertible;

1) If the imaginary function definition To test() { return { std::declval<From>() }; } is well-formed, (that is, either std::declval<From>() can be converted to To using implicit conversions without narrowing, or both From and To are possibly cv-qualified void), provides the member constant value equal to true. Otherwise value is false. For the purposes of this check, the use of std::declval in the return statement is not considered an odr-use.

Access checks are performed as if from a context unrelated to either type. Only the validity of the immediate context of the expression in the return statement (including conversions to the return type) is considered.

2) Same as 1), but the conversion is also noexcept.

From and To shall each be a complete type, (possibly cv-qualified) void, or an array of unknown bound. Otherwise, the behavior is undefined.

If an instantiation of a template above depends, directly or indirectly, on an incomplete type, and that instantiation could yield a different result if that type were hypothetically completed, the behavior is undefined.

The behavior of a program that adds specializations for any of the templates described on this page is undefined.

Helper variable template

template <typename From, typename To>
inline constexpr bool is_non_narrowing_convertible_v = is_non_narrowing_convertible<From, To>::value;

template <typename From, typename To>
inline constexpr bool is_nothrow_non_narrowing_convertible_v = is_nothrow_non_narrowing_convertible<From, To>::value;
#pragma once
#ifndef IS_NON_NARROWING_CONVERTIBLE_H
#define IS_NON_NARROWING_CONVERTIBLE_H
#include <type_traits>
template <typename From, typename To>
auto is_non_narrowing_convertible_helper_function(int) -> decltype(std::declval<void(To)>()({ std::declval<From>() }), std::true_type{});
template <typename From, typename To>
auto is_non_narrowing_convertible_helper_function(...) -> std::false_type;
template <typename From, typename To, bool = (std::is_arithmetic_v<From> || std::is_enum_v<From> || std::is_pointer_v<From> || std::is_member_pointer_v<From>) && (std::is_arithmetic_v<To> || std::is_enum_v<To>)>
struct is_non_narrowing_convertible_helper;
template <typename From, typename To>
struct is_non_narrowing_convertible_helper<From, To, true> : decltype(is_non_narrowing_convertible_helper_function<From, To>(0)) {};
template <typename From, typename To>
struct is_non_narrowing_convertible_helper<From, To, false> : std::is_convertible<From, To> {};
template <typename From, typename To>
struct is_non_narrowing_convertible : is_non_narrowing_convertible_helper<From, To> {};
template <typename From, typename To>
inline constexpr bool is_non_narrowing_convertible_v = is_non_narrowing_convertible<From, To>::value;
template <typename From, typename To>
struct is_nothrow_non_narrowing_convertible : std::conjunction<is_non_narrowing_convertible<From, To>, std::is_nothrow_convertible<From, To>> {};
template <typename From, typename To>
inline constexpr bool is_nothrow_non_narrowing_convertible_v = is_nothrow_non_narrowing_convertible<From, To>::value;
#endif
#include <iostream>
#include "is_non_narrowing_convertible.h"
struct A {};
struct B
{
operator int()
{
return 0;
}
operator long() noexcept
{
return 1;
}
};
struct C
{
operator A()
{
return A{};
}
explicit operator B()
{
return B{};
}
};
struct D
{
D() = default;
D(A) {}
explicit D(B) {}
};
enum E1 : long long {};
enum E2 : short {};
int main()
{
std::cout << std::boolalpha
<< is_non_narrowing_convertible_v<int, A> << '\n' // non-convertible types
<< is_non_narrowing_convertible_v<C, A> << '\n' // user-defined conversion via non-explicit converting operator
<< is_non_narrowing_convertible_v<A, D> << '\n' // user-defined conversion via non-explicit converting constructor
<< is_non_narrowing_convertible_v<C, B> << '\n' // user-defined conversion via explicit converting operator
<< is_non_narrowing_convertible_v<B, D> << '\n' // user-defined conversion via explicit converting constructor
<< is_non_narrowing_convertible_v<char, int> << '\n' // conversion from small integer type to large integer type
<< is_non_narrowing_convertible_v<long long, short> << '\n' // conversion from large integer type to small integer type
<< is_non_narrowing_convertible_v<int, unsigned int> << '\n' // conversion from signed integer type to unsigned integer type
<< is_non_narrowing_convertible_v<char, long double> << '\n' // conversion from integer type to floating point type
<< is_non_narrowing_convertible_v<float, int> << '\n' // conversion from floating point type to integer type
<< is_non_narrowing_convertible_v<float, double> << '\n' // conversion from small floating point type to large floating point type
<< is_non_narrowing_convertible_v<long double, float> << '\n' // conversion from large floating point type to small floating point type
<< is_non_narrowing_convertible_v<int, bool> << '\n' // conversion from integer type to bool
<< is_non_narrowing_convertible_v<bool, int> << '\n' // conversion from bool to integer type
<< is_non_narrowing_convertible_v<int*, bool> << '\n' // conversion from pointer type to bool
<< is_non_narrowing_convertible_v<int A::*, bool> << '\n' // conversion from pointer to member type to bool
<< is_non_narrowing_convertible_v<E1, short> << '\n' // conversion from unscoped enumeration type of larger underlying type to smaller integer type
<< is_non_narrowing_convertible_v<E2, long> << '\n' // conversion from unscoped enumeration type of smaller underlying type to larger integer type
<< is_non_narrowing_convertible_v<int, int&> << '\n' // conversion from non-reference type to reference
<< is_non_narrowing_convertible_v<int&, int> << '\n' // conversion from reference to non-reference type
<< is_nothrow_non_narrowing_convertible_v<B, int> << '\n' // user-defined conversion via non-noexcept converting operator
<< is_nothrow_non_narrowing_convertible_v<B, long> << '\n' // user-defined conversion via noexcept converting operator
<< std::endl;
/*
Output:
false
true
true
false
false
true
false
false
false
false
true
false
false
true
false
false
false
true
false
true
false
true
*/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment