* [PATCH] c++: Implement __is_{nothrow_,}convertible [PR106784] @ 2022-09-22 13:39 Marek Polacek 2022-09-22 22:14 ` Jason Merrill 0 siblings, 1 reply; 10+ messages in thread From: Marek Polacek @ 2022-09-22 13:39 UTC (permalink / raw) To: GCC Patches, Jason Merrill, libstdc++, Jonathan Wakely To improve compile times, the C++ library could use compiler built-ins rather than implementing std::is_convertible (and _nothrow) as class templates. This patch adds the built-ins. We already have __is_constructible and __is_assignable, and the nothrow forms of those. Microsoft (and clang, for compatibility) also provide an alias called __is_convertible_to. I did not add it, but it would be trivial to do so. I noticed that our __is_assignable doesn't implement the "Access checks are performed as if from a context unrelated to either type" requirement, therefore std::is_assignable / __is_assignable give two different results here: class S { operator int(); friend void g(); // #1 }; void g () { // #1 doesn't matter static_assert(std::is_assignable<int&, S>::value, ""); static_assert(__is_assignable(int&, S), ""); } This is not a problem if __is_assignable is not meant to be used by the users. This patch doesn't make libstdc++ use the new built-ins, but I had to rename a class otherwise its name would clash with the new built-in. Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? PR c++/106784 gcc/c-family/ChangeLog: * c-common.cc (c_common_reswords): Add __is_convertible and __is_nothrow_convertible. * c-common.h (enum rid): Add RID_IS_CONVERTIBLE and RID_IS_NOTHROW_CONVERTIBLE. gcc/cp/ChangeLog: * constraint.cc (diagnose_trait_expr): Handle CPTK_IS_CONVERTIBLE and CPTK_IS_NOTHROW_CONVERTIBLE. * cp-objcp-common.cc (names_builtin_p): Handle RID_IS_CONVERTIBLE RID_IS_NOTHROW_CONVERTIBLE. * cp-tree.h (enum cp_trait_kind): Add CPTK_IS_CONVERTIBLE and CPTK_IS_NOTHROW_CONVERTIBLE. (is_convertible): Declare. (is_nothrow_convertible): Likewise. * cxx-pretty-print.cc (pp_cxx_trait_expression): Handle CPTK_IS_CONVERTIBLE and CPTK_IS_NOTHROW_CONVERTIBLE. * method.cc (is_convertible): New. (is_nothrow_convertible): Likewise. * parser.cc (cp_parser_primary_expression): Handle RID_IS_CONVERTIBLE and RID_IS_NOTHROW_CONVERTIBLE. (cp_parser_trait_expr): Likewise. * semantics.cc (trait_expr_value): Handle CPTK_IS_CONVERTIBLE and CPTK_IS_NOTHROW_CONVERTIBLE. (finish_trait_expr): Likewise. libstdc++-v3/ChangeLog: * include/std/type_traits: Rename __is_nothrow_convertible to __is_nothrow_convertible_lib. * testsuite/20_util/is_nothrow_convertible/value_ext.cc: Likewise. gcc/testsuite/ChangeLog: * g++.dg/ext/has-builtin-1.C: Enhance to test __is_convertible and __is_nothrow_convertible. * g++.dg/ext/is_convertible1.C: New test. * g++.dg/ext/is_convertible2.C: New test. * g++.dg/ext/is_nothrow_convertible1.C: New test. * g++.dg/ext/is_nothrow_convertible2.C: New test. --- gcc/c-family/c-common.cc | 2 + gcc/c-family/c-common.h | 1 + gcc/cp/constraint.cc | 6 + gcc/cp/cp-objcp-common.cc | 2 + gcc/cp/cp-tree.h | 4 + gcc/cp/cxx-pretty-print.cc | 6 + gcc/cp/method.cc | 31 ++ gcc/cp/parser.cc | 10 + gcc/cp/semantics.cc | 8 + gcc/testsuite/g++.dg/ext/has-builtin-1.C | 6 + gcc/testsuite/g++.dg/ext/is_convertible1.C | 269 +++++++++++++++++ gcc/testsuite/g++.dg/ext/is_convertible2.C | 46 +++ .../g++.dg/ext/is_nothrow_convertible1.C | 270 ++++++++++++++++++ .../g++.dg/ext/is_nothrow_convertible2.C | 19 ++ libstdc++-v3/include/std/type_traits | 4 +- .../is_nothrow_convertible/value_ext.cc | 4 +- 16 files changed, 684 insertions(+), 4 deletions(-) create mode 100644 gcc/testsuite/g++.dg/ext/is_convertible1.C create mode 100644 gcc/testsuite/g++.dg/ext/is_convertible2.C create mode 100644 gcc/testsuite/g++.dg/ext/is_nothrow_convertible1.C create mode 100644 gcc/testsuite/g++.dg/ext/is_nothrow_convertible2.C diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc index c0f15f4cab1..dce3045c9f2 100644 --- a/gcc/c-family/c-common.cc +++ b/gcc/c-family/c-common.cc @@ -541,6 +541,8 @@ const struct c_common_resword c_common_reswords[] = { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY }, { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY }, { "__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE, D_CXXONLY }, + { "__is_convertible", RID_IS_CONVERTIBLE, D_CXXONLY }, + { "__is_nothrow_convertible", RID_IS_NOTHROW_CONVERTIBLE, D_CXXONLY }, { "__reference_constructs_from_temporary", RID_REF_CONSTRUCTS_FROM_TEMPORARY, D_CXXONLY }, { "__reference_converts_from_temporary", RID_REF_CONVERTS_FROM_TEMPORARY, diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h index 2f592f5cd58..31397d80029 100644 --- a/gcc/c-family/c-common.h +++ b/gcc/c-family/c-common.h @@ -184,6 +184,7 @@ enum rid RID_IS_UNION, RID_UNDERLYING_TYPE, RID_IS_ASSIGNABLE, RID_IS_CONSTRUCTIBLE, RID_IS_NOTHROW_ASSIGNABLE, RID_IS_NOTHROW_CONSTRUCTIBLE, + RID_IS_CONVERTIBLE, RID_IS_NOTHROW_CONVERTIBLE, RID_REF_CONSTRUCTS_FROM_TEMPORARY, RID_REF_CONVERTS_FROM_TEMPORARY, diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc index 568318f0ba1..5839bfb4b52 100644 --- a/gcc/cp/constraint.cc +++ b/gcc/cp/constraint.cc @@ -3697,6 +3697,12 @@ diagnose_trait_expr (tree expr, tree args) case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS: inform (loc, " %qT does not have unique object representations", t1); break; + case CPTK_IS_CONVERTIBLE: + inform (loc, " %qT is not convertible from %qE", t2, t1); + break; + case CPTK_IS_NOTHROW_CONVERTIBLE: + inform (loc, " %qT is not %<nothrow%> convertible from %qE", t2, t1); + break; case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY: inform (loc, " %qT is not a reference that binds to a temporary " "object of type %qT (direct-initialization)", t1, t2); diff --git a/gcc/cp/cp-objcp-common.cc b/gcc/cp/cp-objcp-common.cc index 1ffac08c32f..64975699351 100644 --- a/gcc/cp/cp-objcp-common.cc +++ b/gcc/cp/cp-objcp-common.cc @@ -463,6 +463,8 @@ names_builtin_p (const char *name) case RID_IS_NOTHROW_ASSIGNABLE: case RID_IS_NOTHROW_CONSTRUCTIBLE: case RID_UNDERLYING_TYPE: + case RID_IS_CONVERTIBLE: + case RID_IS_NOTHROW_CONVERTIBLE: case RID_REF_CONSTRUCTS_FROM_TEMPORARY: case RID_REF_CONVERTS_FROM_TEMPORARY: return true; diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index f19ecafc266..e4d89207e2a 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -1407,6 +1407,8 @@ enum cp_trait_kind CPTK_IS_CONSTRUCTIBLE, CPTK_IS_NOTHROW_ASSIGNABLE, CPTK_IS_NOTHROW_CONSTRUCTIBLE, + CPTK_IS_CONVERTIBLE, + CPTK_IS_NOTHROW_CONVERTIBLE, CPTK_REF_CONSTRUCTS_FROM_TEMPORARY, CPTK_REF_CONVERTS_FROM_TEMPORARY }; @@ -7116,6 +7118,8 @@ extern tree forward_parm (tree); extern bool is_trivially_xible (enum tree_code, tree, tree); extern bool is_nothrow_xible (enum tree_code, tree, tree); extern bool is_xible (enum tree_code, tree, tree); +extern bool is_convertible (tree, tree); +extern bool is_nothrow_convertible (tree, tree); extern bool ref_xes_from_temporary (tree, tree, bool); extern tree get_defaulted_eh_spec (tree, tsubst_flags_t = tf_warning_or_error); extern bool maybe_explain_implicit_delete (tree); diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc index 44590830a61..e18143e39a9 100644 --- a/gcc/cp/cxx-pretty-print.cc +++ b/gcc/cp/cxx-pretty-print.cc @@ -2696,6 +2696,12 @@ pp_cxx_trait_expression (cxx_pretty_printer *pp, tree t) case CPTK_IS_NOTHROW_CONSTRUCTIBLE: pp_cxx_ws_string (pp, "__is_nothrow_constructible"); break; + case CPTK_IS_CONVERTIBLE: + pp_cxx_ws_string (pp, "__is_convertible"); + break; + case CPTK_IS_NOTHROW_CONVERTIBLE: + pp_cxx_ws_string (pp, "__is_nothrow_convertible"); + break; case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY: pp_cxx_ws_string (pp, "__reference_constructs_from_temporary"); break; diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc index 573ef016f82..c35a59fe56c 100644 --- a/gcc/cp/method.cc +++ b/gcc/cp/method.cc @@ -2236,6 +2236,37 @@ ref_xes_from_temporary (tree to, tree from, bool direct_init_p) return ref_conv_binds_directly (to, val, direct_init_p).is_false (); } +/* Return true if FROM can be converted to TO using implicit conversions, + or both FROM and TO are possibly cv-qualified void. NB: This doesn't + implement the "Access checks are performed as if from a context unrelated + to either type" restriction. */ + +bool +is_convertible (tree from, tree to) +{ + if (VOID_TYPE_P (from) && VOID_TYPE_P (to)) + return true; + tree expr = build_stub_object (from); + expr = perform_implicit_conversion (to, expr, tf_none); + if (expr == error_mark_node) + return false; + return !!expr; +} + +/* Like is_convertible, but the conversion is also noexcept. */ + +bool +is_nothrow_convertible (tree from, tree to) +{ + if (VOID_TYPE_P (from) && VOID_TYPE_P (to)) + return true; + tree expr = build_stub_object (from); + expr = perform_implicit_conversion (to, expr, tf_none); + if (expr == NULL_TREE || expr == error_mark_node) + return false; + return expr_noexcept_p (expr, tf_none); +} + /* Categorize various special_function_kinds. */ #define SFK_CTOR_P(sfk) \ ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor) diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 3cbe0d69de1..bb83d1c78f6 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -5922,6 +5922,8 @@ cp_parser_primary_expression (cp_parser *parser, case RID_IS_CONSTRUCTIBLE: case RID_IS_NOTHROW_ASSIGNABLE: case RID_IS_NOTHROW_CONSTRUCTIBLE: + case RID_IS_CONVERTIBLE: + case RID_IS_NOTHROW_CONVERTIBLE: case RID_REF_CONSTRUCTS_FROM_TEMPORARY: case RID_REF_CONVERTS_FROM_TEMPORARY: return cp_parser_trait_expr (parser, token->keyword); @@ -11008,6 +11010,14 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword) kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE; variadic = true; break; + case RID_IS_CONVERTIBLE: + kind = CPTK_IS_CONVERTIBLE; + binary = true; + break; + case RID_IS_NOTHROW_CONVERTIBLE: + kind = CPTK_IS_NOTHROW_CONVERTIBLE; + binary = true; + break; case RID_REF_CONSTRUCTS_FROM_TEMPORARY: kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY; binary = true; diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 86562071612..92fc795df40 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -12044,6 +12044,12 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2) case CPTK_IS_NOTHROW_CONSTRUCTIBLE: return is_nothrow_xible (INIT_EXPR, type1, type2); + case CPTK_IS_CONVERTIBLE: + return is_convertible (type1, type2); + + case CPTK_IS_NOTHROW_CONVERTIBLE: + return is_nothrow_convertible (type1, type2); + case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY: return ref_xes_from_temporary (type1, type2, /*direct_init=*/true); @@ -12165,6 +12171,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2) case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE: case CPTK_IS_NOTHROW_ASSIGNABLE: case CPTK_IS_NOTHROW_CONSTRUCTIBLE: + case CPTK_IS_CONVERTIBLE: + case CPTK_IS_NOTHROW_CONVERTIBLE: case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY: case CPTK_REF_CONVERTS_FROM_TEMPORARY: if (!check_trait_type (type1) diff --git a/gcc/testsuite/g++.dg/ext/has-builtin-1.C b/gcc/testsuite/g++.dg/ext/has-builtin-1.C index fe25cb2f669..17dabf648cf 100644 --- a/gcc/testsuite/g++.dg/ext/has-builtin-1.C +++ b/gcc/testsuite/g++.dg/ext/has-builtin-1.C @@ -131,3 +131,9 @@ #if !__has_builtin (__builtin_is_pointer_interconvertible_with_class) # error "__has_builtin (__builtin_is_pointer_interconvertible_with_class) failed" #endif +#if !__has_builtin (__is_convertible) +# error "__has_builtin (__is_convertible) failed" +#endif +#if !__has_builtin (__is_nothrow_convertible) +# error "__has_builtin (__is_nothrow_convertible) failed" +#endif diff --git a/gcc/testsuite/g++.dg/ext/is_convertible1.C b/gcc/testsuite/g++.dg/ext/is_convertible1.C new file mode 100644 index 00000000000..2e72945bceb --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/is_convertible1.C @@ -0,0 +1,269 @@ +// PR c++/106784 +// { dg-do compile { target c++11 } } + +#define SA(X) static_assert((X),#X) + +template<typename From, typename To> +struct is_convertible { + static const bool value = __is_convertible(From, To); +}; + +struct from_int { + from_int(int); +}; + +struct from_charp { + from_charp(const char *); +}; + +struct to_int { + operator int(); +}; + +typedef int Fn(int); +typedef char Arr[10]; +enum E { XYZZY }; + +SA(!__is_convertible(int, void)); +SA(__is_convertible(int, int)); +SA(__is_convertible(int, from_int)); +SA(__is_convertible(long, from_int)); +SA(__is_convertible(double, from_int)); +SA(__is_convertible(const int, from_int)); +SA(__is_convertible(const int&, from_int)); +SA(__is_convertible(to_int, int)); +SA(__is_convertible(to_int, const int&)); +SA(__is_convertible(to_int, long)); +SA(!__is_convertible(to_int, int&)); +SA(!__is_convertible(to_int, from_int)); +SA(!__is_convertible(int, Fn)); +SA(!__is_convertible(int, Fn*)); +SA(!__is_convertible(int, Fn&)); +SA(!__is_convertible(int, Arr)); +SA(!__is_convertible(int, Arr&)); +SA(!__is_convertible(int, int&)); +SA(__is_convertible(int, const int&)); +SA(!__is_convertible(const int, int&)); +SA(__is_convertible(const int, const int&)); +SA(!__is_convertible(int, int*)); + +SA(!__is_convertible(int, E)); +SA(__is_convertible(E, int)); + +SA(__is_convertible(int&, int)); +SA(__is_convertible(int&, int&)); +SA(__is_convertible(int&, const int&)); +SA(!__is_convertible(const int&, int&)); +SA(__is_convertible(const int&, const int&)); +SA(!__is_convertible(int&, int*)); +SA(!__is_convertible(int&, void)); +SA(!__is_convertible(int&, Fn)); +SA(!__is_convertible(int&, Fn*)); +SA(!__is_convertible(int&, Fn&)); +SA(!__is_convertible(int&, Arr)); +SA(!__is_convertible(int&, Arr&)); + +SA(!__is_convertible(int*, int)); +SA(!__is_convertible(int*, int&)); +SA(!__is_convertible(int*, void)); +SA(__is_convertible(int*, int*)); +SA(__is_convertible(int*, const int*)); +SA(!__is_convertible(const int*, int*)); +SA(__is_convertible(const int*, const int*)); +SA(!__is_convertible(int*, Fn)); +SA(!__is_convertible(int*, Fn*)); +SA(!__is_convertible(int*, Fn&)); +SA(!__is_convertible(int*, Arr)); +SA(!__is_convertible(int*, Arr&)); +SA(!__is_convertible(int*, float*)); + +SA(__is_convertible(void, void)); +SA(!__is_convertible(void, char)); +SA(!__is_convertible(void, char&)); +SA(!__is_convertible(void, char*)); +SA(!__is_convertible(char, void)); +SA(__is_convertible(const void, void)); +SA(__is_convertible(void, const void)); +SA(__is_convertible(const void, const void)); +SA(!__is_convertible(void, Fn)); +SA(!__is_convertible(void, Fn&)); +SA(!__is_convertible(void, Fn*)); +SA(!__is_convertible(void, Arr)); +SA(!__is_convertible(void, Arr&)); + +SA(!__is_convertible(Fn, void)); +SA(!__is_convertible(Fn, Fn)); +SA(__is_convertible(Fn, Fn*)); +SA(__is_convertible(Fn, Fn&)); +SA(!__is_convertible(int(int), int(int))); +SA(__is_convertible(int(int), int(&)(int))); +SA(__is_convertible(int(int), int(&&)(int))); +SA(__is_convertible(int(int), int(*)(int))); +SA(__is_convertible(int(int), int(*const)(int))); +SA(!__is_convertible(int(int), char)); +SA(!__is_convertible(int(int), char*)); +SA(!__is_convertible(int(int), char&)); + +SA(!__is_convertible(Fn&, void)); +SA(!__is_convertible(Fn&, Fn)); +SA(__is_convertible(Fn&, Fn&)); +SA(__is_convertible(Fn&, Fn*)); +SA(!__is_convertible(Fn&, Arr)); +SA(!__is_convertible(Fn&, Arr&)); +SA(!__is_convertible(Fn&, char)); +SA(!__is_convertible(Fn&, char&)); +SA(!__is_convertible(Fn&, char*)); + +SA(!__is_convertible(Fn*, void)); +SA(!__is_convertible(Fn*, Fn)); +SA(!__is_convertible(Fn*, Fn&)); +SA(__is_convertible(Fn*, Fn*)); +SA(!__is_convertible(Fn*, Arr)); +SA(!__is_convertible(Fn*, Arr&)); +SA(!__is_convertible(Fn*, char)); +SA(!__is_convertible(Fn*, char&)); +SA(!__is_convertible(Fn*, char*)); + +SA(!__is_convertible(Arr, void)); +SA(!__is_convertible(Arr, Fn)); +SA(!__is_convertible(Arr, Fn*)); +SA(!__is_convertible(Arr, Fn&)); +SA(!__is_convertible(Arr, Arr)); +SA(!__is_convertible(Arr, Arr&)); +SA(__is_convertible(Arr, const Arr&)); +SA(!__is_convertible(Arr, volatile Arr&)); +SA(!__is_convertible(Arr, const volatile Arr&)); +SA(!__is_convertible(const Arr, Arr&)); +SA(__is_convertible(const Arr, const Arr&)); +SA(__is_convertible(Arr, Arr&&)); +SA(__is_convertible(Arr, const Arr&&)); +SA(__is_convertible(Arr, volatile Arr&&)); +SA(__is_convertible(Arr, const volatile Arr&&)); +SA(__is_convertible(const Arr, const Arr&&)); +SA(!__is_convertible(Arr&, Arr&&)); +SA(!__is_convertible(Arr&&, Arr&)); +SA(!__is_convertible(Arr, char)); +SA(__is_convertible(Arr, char*)); +SA(__is_convertible(Arr, const char*)); +SA(!__is_convertible(Arr, char&)); +SA(!__is_convertible(const Arr, char*)); +SA(__is_convertible(const Arr, const char*)); +SA(!__is_convertible(int, int[1])); +SA(!__is_convertible(int[1], int[1])); +SA(!__is_convertible(int[1], int(&)[1])); +SA(__is_convertible(int(&)[1], int(&)[1])); +SA(__is_convertible(int(&)[1], const int(&)[1])); +SA(!__is_convertible(const int(&)[1], int(&)[1])); +SA(!__is_convertible(int[1][1], int*)); +SA(!__is_convertible(int[][1], int*)); + +SA(!__is_convertible(Arr&, void)); +SA(!__is_convertible(Arr&, Fn)); +SA(!__is_convertible(Arr&, Fn*)); +SA(!__is_convertible(Arr&, Fn&)); +SA(!__is_convertible(Arr&, Arr)); +SA(__is_convertible(Arr&, Arr&)); +SA(__is_convertible(Arr&, const Arr&)); +SA(!__is_convertible(const Arr&, Arr&)); +SA(__is_convertible(const Arr&, const Arr&)); +SA(!__is_convertible(Arr&, char)); +SA(__is_convertible(Arr&, char*)); +SA(__is_convertible(Arr&, const char*)); +SA(!__is_convertible(Arr&, char&)); +SA(!__is_convertible(const Arr&, char*)); +SA(__is_convertible(const Arr&, const char*)); +SA(__is_convertible(Arr, from_charp)); +SA(__is_convertible(Arr&, from_charp)); + +struct B { }; +struct D : B { }; + +SA(__is_convertible(D, B)); +SA(__is_convertible(D*, B*)); +SA(__is_convertible(D&, B&)); +SA(!__is_convertible(B, D)); +SA(!__is_convertible(B*, D*)); +SA(!__is_convertible(B&, D&)); + +/* These are taken from LLVM's test/SemaCXX/type-traits.cpp. */ + +struct I { + int i; + I(int _i) : i(_i) { } + operator int() const { + return i; + } +}; + +struct F +{ + float f; + F(float _f) : f(_f) {} + F(const I& obj) + : f(static_cast<float>(obj.i)) {} + operator float() const { + return f; + } + operator I() const { + return I(static_cast<int>(f)); + } +}; + +SA(__is_convertible(I, I)); +SA(__is_convertible(I, const I)); +SA(__is_convertible(I, int)); +SA(__is_convertible(int, I)); +SA(__is_convertible(I, F)); +SA(__is_convertible(F, I)); +SA(__is_convertible(F, float)); +SA(__is_convertible(float, F)); + +template<typename> +struct X { + template<typename U> X(const X<U>&); +}; + +SA(__is_convertible(X<int>, X<float>)); +SA(__is_convertible(X<float>, X<int>)); + +struct Abstract { + virtual void f() = 0; +}; + +SA(!__is_convertible(Abstract, Abstract)); + +class hidden { + hidden(const hidden&); + friend void test (); +}; + +SA(__is_convertible(hidden&, hidden&)); +SA(__is_convertible(hidden&, const hidden&)); +SA(__is_convertible(hidden&, volatile hidden&)); +SA(__is_convertible(hidden&, const volatile hidden&)); +SA(__is_convertible(const hidden&, const hidden&)); +SA(__is_convertible(const hidden&, const volatile hidden&)); +SA(__is_convertible(volatile hidden&, const volatile hidden&)); +SA(__is_convertible(const volatile hidden&, const volatile hidden&)); +SA(!__is_convertible(const hidden&, hidden&)); + +void +test () +{ + /* __is_convertible(hidden, hidden) should be false despite the + friend declaration above, because "Access checks are performed + as if from a context unrelated to either type", but we don't + implement that for the built-in (std::is_convertible works as + expected). This is the case for __is_assignable as well. */ + //SA(!__is_convertible(hidden, hidden)); +} + +void +test2 () +{ + struct X { }; + struct Y { + explicit Y(X); // not viable for implicit conversions + }; + SA(!__is_convertible(X, Y)); +} diff --git a/gcc/testsuite/g++.dg/ext/is_convertible2.C b/gcc/testsuite/g++.dg/ext/is_convertible2.C new file mode 100644 index 00000000000..9b46e264379 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/is_convertible2.C @@ -0,0 +1,46 @@ +// PR c++/106784 +// { dg-do compile { target c++20 } } +// Adapted from <https://en.cppreference.com/w/cpp/types/is_convertible>. + +#include <string> +#include <string_view> + +#define SA(X) static_assert((X),#X) + +class E { public: template<class T> E(T&&) { } }; + +int main() +{ + class A {}; + class B : public A {}; + class C {}; + class D { public: operator C() { return c; } C c; }; + + SA(__is_convertible(B*, A*)); + SA(!__is_convertible(A*, B*)); + SA(__is_convertible(D, C)); + SA(!__is_convertible(B*, C*)); + SA(__is_convertible(A, E)); + + using std::operator "" s, std::operator "" sv; + + auto stringify = []<typename T>(T x) { + if constexpr (std::is_convertible_v<T, std::string> or + std::is_convertible_v<T, std::string_view>) { + return x; + } else { + return std::to_string(x); + } + }; + + const char* three = "three"; + + SA(!__is_convertible(std::string_view, std::string)); + SA(__is_convertible(std::string, std::string_view)); + + auto s1 = stringify("one"s); + auto s2 = stringify("two"sv); + auto s3 = stringify(three); + auto s4 = stringify(42); + auto s5 = stringify(42.); +} diff --git a/gcc/testsuite/g++.dg/ext/is_nothrow_convertible1.C b/gcc/testsuite/g++.dg/ext/is_nothrow_convertible1.C new file mode 100644 index 00000000000..bb7243e4611 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/is_nothrow_convertible1.C @@ -0,0 +1,270 @@ +// PR c++/106784 +// { dg-do compile { target c++11 } } +// Like is_convertible1.C, but conversion functions are made noexcept. + +#define SA(X) static_assert((X),#X) + +template<typename From, typename To> +struct is_nothrow_convertible { + static const bool value = __is_nothrow_convertible(From, To); +}; + +struct from_int { + from_int(int) noexcept; +}; + +struct from_charp { + from_charp(const char *) noexcept; +}; + +struct to_int { + operator int() noexcept; +}; + +typedef int Fn(int); +typedef char Arr[10]; +enum E { XYZZY }; + +SA(!__is_nothrow_convertible(int, void)); +SA(__is_nothrow_convertible(int, int)); +SA(__is_nothrow_convertible(int, from_int)); +SA(__is_nothrow_convertible(long, from_int)); +SA(__is_nothrow_convertible(double, from_int)); +SA(__is_nothrow_convertible(const int, from_int)); +SA(__is_nothrow_convertible(const int&, from_int)); +SA(__is_nothrow_convertible(to_int, int)); +SA(__is_nothrow_convertible(to_int, const int&)); +SA(__is_nothrow_convertible(to_int, long)); +SA(!__is_nothrow_convertible(to_int, int&)); +SA(!__is_nothrow_convertible(to_int, from_int)); +SA(!__is_nothrow_convertible(int, Fn)); +SA(!__is_nothrow_convertible(int, Fn*)); +SA(!__is_nothrow_convertible(int, Fn&)); +SA(!__is_nothrow_convertible(int, Arr)); +SA(!__is_nothrow_convertible(int, Arr&)); +SA(!__is_nothrow_convertible(int, int&)); +SA(__is_nothrow_convertible(int, const int&)); +SA(!__is_nothrow_convertible(const int, int&)); +SA(__is_nothrow_convertible(const int, const int&)); +SA(!__is_nothrow_convertible(int, int*)); + +SA(!__is_nothrow_convertible(int, E)); +SA(__is_nothrow_convertible(E, int)); + +SA(__is_nothrow_convertible(int&, int)); +SA(__is_nothrow_convertible(int&, int&)); +SA(__is_nothrow_convertible(int&, const int&)); +SA(!__is_nothrow_convertible(const int&, int&)); +SA(__is_nothrow_convertible(const int&, const int&)); +SA(!__is_nothrow_convertible(int&, int*)); +SA(!__is_nothrow_convertible(int&, void)); +SA(!__is_nothrow_convertible(int&, Fn)); +SA(!__is_nothrow_convertible(int&, Fn*)); +SA(!__is_nothrow_convertible(int&, Fn&)); +SA(!__is_nothrow_convertible(int&, Arr)); +SA(!__is_nothrow_convertible(int&, Arr&)); + +SA(!__is_nothrow_convertible(int*, int)); +SA(!__is_nothrow_convertible(int*, int&)); +SA(!__is_nothrow_convertible(int*, void)); +SA(__is_nothrow_convertible(int*, int*)); +SA(__is_nothrow_convertible(int*, const int*)); +SA(!__is_nothrow_convertible(const int*, int*)); +SA(__is_nothrow_convertible(const int*, const int*)); +SA(!__is_nothrow_convertible(int*, Fn)); +SA(!__is_nothrow_convertible(int*, Fn*)); +SA(!__is_nothrow_convertible(int*, Fn&)); +SA(!__is_nothrow_convertible(int*, Arr)); +SA(!__is_nothrow_convertible(int*, Arr&)); +SA(!__is_nothrow_convertible(int*, float*)); + +SA(__is_nothrow_convertible(void, void)); +SA(!__is_nothrow_convertible(void, char)); +SA(!__is_nothrow_convertible(void, char&)); +SA(!__is_nothrow_convertible(void, char*)); +SA(!__is_nothrow_convertible(char, void)); +SA(__is_nothrow_convertible(const void, void)); +SA(__is_nothrow_convertible(void, const void)); +SA(__is_nothrow_convertible(const void, const void)); +SA(!__is_nothrow_convertible(void, Fn)); +SA(!__is_nothrow_convertible(void, Fn&)); +SA(!__is_nothrow_convertible(void, Fn*)); +SA(!__is_nothrow_convertible(void, Arr)); +SA(!__is_nothrow_convertible(void, Arr&)); + +SA(!__is_nothrow_convertible(Fn, void)); +SA(!__is_nothrow_convertible(Fn, Fn)); +SA(__is_nothrow_convertible(Fn, Fn*)); +SA(__is_nothrow_convertible(Fn, Fn&)); +SA(!__is_nothrow_convertible(int(int), int(int))); +SA(__is_nothrow_convertible(int(int), int(&)(int))); +SA(__is_nothrow_convertible(int(int), int(&&)(int))); +SA(__is_nothrow_convertible(int(int), int(*)(int))); +SA(__is_nothrow_convertible(int(int), int(*const)(int))); +SA(!__is_nothrow_convertible(int(int), char)); +SA(!__is_nothrow_convertible(int(int), char*)); +SA(!__is_nothrow_convertible(int(int), char&)); + +SA(!__is_nothrow_convertible(Fn&, void)); +SA(!__is_nothrow_convertible(Fn&, Fn)); +SA(__is_nothrow_convertible(Fn&, Fn&)); +SA(__is_nothrow_convertible(Fn&, Fn*)); +SA(!__is_nothrow_convertible(Fn&, Arr)); +SA(!__is_nothrow_convertible(Fn&, Arr&)); +SA(!__is_nothrow_convertible(Fn&, char)); +SA(!__is_nothrow_convertible(Fn&, char&)); +SA(!__is_nothrow_convertible(Fn&, char*)); + +SA(!__is_nothrow_convertible(Fn*, void)); +SA(!__is_nothrow_convertible(Fn*, Fn)); +SA(!__is_nothrow_convertible(Fn*, Fn&)); +SA(__is_nothrow_convertible(Fn*, Fn*)); +SA(!__is_nothrow_convertible(Fn*, Arr)); +SA(!__is_nothrow_convertible(Fn*, Arr&)); +SA(!__is_nothrow_convertible(Fn*, char)); +SA(!__is_nothrow_convertible(Fn*, char&)); +SA(!__is_nothrow_convertible(Fn*, char*)); + +SA(!__is_nothrow_convertible(Arr, void)); +SA(!__is_nothrow_convertible(Arr, Fn)); +SA(!__is_nothrow_convertible(Arr, Fn*)); +SA(!__is_nothrow_convertible(Arr, Fn&)); +SA(!__is_nothrow_convertible(Arr, Arr)); +SA(!__is_nothrow_convertible(Arr, Arr&)); +SA(__is_nothrow_convertible(Arr, const Arr&)); +SA(!__is_nothrow_convertible(Arr, volatile Arr&)); +SA(!__is_nothrow_convertible(Arr, const volatile Arr&)); +SA(!__is_nothrow_convertible(const Arr, Arr&)); +SA(__is_nothrow_convertible(const Arr, const Arr&)); +SA(__is_nothrow_convertible(Arr, Arr&&)); +SA(__is_nothrow_convertible(Arr, const Arr&&)); +SA(__is_nothrow_convertible(Arr, volatile Arr&&)); +SA(__is_nothrow_convertible(Arr, const volatile Arr&&)); +SA(__is_nothrow_convertible(const Arr, const Arr&&)); +SA(!__is_nothrow_convertible(Arr&, Arr&&)); +SA(!__is_nothrow_convertible(Arr&&, Arr&)); +SA(!__is_nothrow_convertible(Arr, char)); +SA(__is_nothrow_convertible(Arr, char*)); +SA(__is_nothrow_convertible(Arr, const char*)); +SA(!__is_nothrow_convertible(Arr, char&)); +SA(!__is_nothrow_convertible(const Arr, char*)); +SA(__is_nothrow_convertible(const Arr, const char*)); +SA(!__is_nothrow_convertible(int, int[1])); +SA(!__is_nothrow_convertible(int[1], int[1])); +SA(!__is_nothrow_convertible(int[1], int(&)[1])); +SA(__is_nothrow_convertible(int(&)[1], int(&)[1])); +SA(__is_nothrow_convertible(int(&)[1], const int(&)[1])); +SA(!__is_nothrow_convertible(const int(&)[1], int(&)[1])); +SA(!__is_nothrow_convertible(int[1][1], int*)); +SA(!__is_nothrow_convertible(int[][1], int*)); + +SA(!__is_nothrow_convertible(Arr&, void)); +SA(!__is_nothrow_convertible(Arr&, Fn)); +SA(!__is_nothrow_convertible(Arr&, Fn*)); +SA(!__is_nothrow_convertible(Arr&, Fn&)); +SA(!__is_nothrow_convertible(Arr&, Arr)); +SA(__is_nothrow_convertible(Arr&, Arr&)); +SA(__is_nothrow_convertible(Arr&, const Arr&)); +SA(!__is_nothrow_convertible(const Arr&, Arr&)); +SA(__is_nothrow_convertible(const Arr&, const Arr&)); +SA(!__is_nothrow_convertible(Arr&, char)); +SA(__is_nothrow_convertible(Arr&, char*)); +SA(__is_nothrow_convertible(Arr&, const char*)); +SA(!__is_nothrow_convertible(Arr&, char&)); +SA(!__is_nothrow_convertible(const Arr&, char*)); +SA(__is_nothrow_convertible(const Arr&, const char*)); +SA(__is_nothrow_convertible(Arr, from_charp)); +SA(__is_nothrow_convertible(Arr&, from_charp)); + +struct B { }; +struct D : B { }; + +SA(__is_nothrow_convertible(D, B)); +SA(__is_nothrow_convertible(D*, B*)); +SA(__is_nothrow_convertible(D&, B&)); +SA(!__is_nothrow_convertible(B, D)); +SA(!__is_nothrow_convertible(B*, D*)); +SA(!__is_nothrow_convertible(B&, D&)); + +/* These are taken from LLVM's test/SemaCXX/type-traits.cpp. */ + +struct I { + int i; + I(int _i) noexcept : i(_i) { } + operator int() const noexcept { + return i; + } +}; + +struct F +{ + float f; + F(float _f) noexcept : f(_f) {} + F(const I& obj) noexcept + : f(static_cast<float>(obj.i)) {} + operator float() const noexcept { + return f; + } + operator I() const noexcept { + return I(static_cast<int>(f)); + } +}; + +SA(__is_nothrow_convertible(I, I)); +SA(__is_nothrow_convertible(I, const I)); +SA(__is_nothrow_convertible(I, int)); +SA(__is_nothrow_convertible(int, I)); +SA(__is_nothrow_convertible(I, F)); +SA(__is_nothrow_convertible(F, I)); +SA(__is_nothrow_convertible(F, float)); +SA(__is_nothrow_convertible(float, F)); + +template<typename> +struct X { + template<typename U> X(const X<U>&) noexcept; +}; + +SA(__is_nothrow_convertible(X<int>, X<float>)); +SA(__is_nothrow_convertible(X<float>, X<int>)); + +struct Abstract { + virtual void f() = 0; +}; + +SA(!__is_nothrow_convertible(Abstract, Abstract)); + +class hidden { + hidden(const hidden&); + friend void test (); +}; + +SA(__is_nothrow_convertible(hidden&, hidden&)); +SA(__is_nothrow_convertible(hidden&, const hidden&)); +SA(__is_nothrow_convertible(hidden&, volatile hidden&)); +SA(__is_nothrow_convertible(hidden&, const volatile hidden&)); +SA(__is_nothrow_convertible(const hidden&, const hidden&)); +SA(__is_nothrow_convertible(const hidden&, const volatile hidden&)); +SA(__is_nothrow_convertible(volatile hidden&, const volatile hidden&)); +SA(__is_nothrow_convertible(const volatile hidden&, const volatile hidden&)); +SA(!__is_nothrow_convertible(const hidden&, hidden&)); + +void +test () +{ + /* __is_nothrow_convertible(hidden, hidden) should be false despite the + friend declaration above, because "Access checks are performed + as if from a context unrelated to either type", but we don't + implement that for the built-in (std::is_convertible works as + expected). This is the case for __is_assignable as well. */ + //SA(!__is_nothrow_convertible(hidden, hidden)); +} + +void +test2 () +{ + struct X { }; + struct Y { + explicit Y(X); // not viable for implicit conversions + }; + SA(!__is_nothrow_convertible(X, Y)); +} diff --git a/gcc/testsuite/g++.dg/ext/is_nothrow_convertible2.C b/gcc/testsuite/g++.dg/ext/is_nothrow_convertible2.C new file mode 100644 index 00000000000..aa089173b75 --- /dev/null +++ b/gcc/testsuite/g++.dg/ext/is_nothrow_convertible2.C @@ -0,0 +1,19 @@ +// PR c++/106784 +// { dg-do compile { target c++11 } } + +#define SA(X) static_assert((X),#X) + +struct A { }; +struct B { }; + +struct M { + operator A(); + operator B() noexcept; + M(const A&); + M(const B&) noexcept; +}; + +SA(!__is_nothrow_convertible(A, M)); +SA(!__is_nothrow_convertible(M, A)); +SA(__is_nothrow_convertible(B, M)); +SA(__is_nothrow_convertible(M, B)); diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits index 94e73eafd2f..1797b9e97f7 100644 --- a/libstdc++-v3/include/std/type_traits +++ b/libstdc++-v3/include/std/type_traits @@ -1453,7 +1453,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // is_nothrow_convertible for C++11 template<typename _From, typename _To> - struct __is_nothrow_convertible + struct __is_nothrow_convertible_lib : public __is_nt_convertible_helper<_From, _To>::type { }; @@ -2999,7 +2999,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION struct __is_nt_invocable_impl<_Result, _Ret, __void_t<typename _Result::type>> : __or_<is_void<_Ret>, - __is_nothrow_convertible<typename _Result::type, _Ret>>::type + __is_nothrow_convertible_lib<typename _Result::type, _Ret>>::type { }; /// @endcond diff --git a/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value_ext.cc b/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value_ext.cc index 0f896428537..d736d2ca260 100644 --- a/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value_ext.cc +++ b/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value_ext.cc @@ -19,10 +19,10 @@ #include <type_traits> -// Test the non-standard __is_nothrow_convertible trait +// Test the non-standard __is_nothrow_convertible_lib trait template<typename From, typename To> - using is_nothrow_convertible = std::__is_nothrow_convertible<From, To>; + using is_nothrow_convertible = std::__is_nothrow_convertible_lib<From, To>; #define IS_NT_CONVERTIBLE_DEFINED #include "value.cc" base-commit: 32d8123cd6ce87acb557aec230e8359051316f9f -- 2.37.3 ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] c++: Implement __is_{nothrow_,}convertible [PR106784] 2022-09-22 13:39 [PATCH] c++: Implement __is_{nothrow_,}convertible [PR106784] Marek Polacek @ 2022-09-22 22:14 ` Jason Merrill 2022-09-23 14:34 ` Marek Polacek 2022-09-23 14:40 ` Jonathan Wakely 0 siblings, 2 replies; 10+ messages in thread From: Jason Merrill @ 2022-09-22 22:14 UTC (permalink / raw) To: Marek Polacek, GCC Patches, libstdc++, Jonathan Wakely On 9/22/22 09:39, Marek Polacek wrote: > To improve compile times, the C++ library could use compiler built-ins > rather than implementing std::is_convertible (and _nothrow) as class > templates. This patch adds the built-ins. We already have > __is_constructible and __is_assignable, and the nothrow forms of those. > > Microsoft (and clang, for compatibility) also provide an alias called > __is_convertible_to. I did not add it, but it would be trivial to do > so. > > I noticed that our __is_assignable doesn't implement the "Access checks > are performed as if from a context unrelated to either type" requirement, > therefore std::is_assignable / __is_assignable give two different results > here: > > class S { > operator int(); > friend void g(); // #1 > }; > > void > g () > { > // #1 doesn't matter > static_assert(std::is_assignable<int&, S>::value, ""); > static_assert(__is_assignable(int&, S), ""); > } > > This is not a problem if __is_assignable is not meant to be used by > the users. That's fine, it's not. > This patch doesn't make libstdc++ use the new built-ins, but I had to > rename a class otherwise its name would clash with the new built-in. Sigh, that's going to be a hassle when comparing compiler versions on preprocessed code. > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? > > PR c++/106784 > > gcc/c-family/ChangeLog: > > * c-common.cc (c_common_reswords): Add __is_convertible and > __is_nothrow_convertible. > * c-common.h (enum rid): Add RID_IS_CONVERTIBLE and > RID_IS_NOTHROW_CONVERTIBLE. > > gcc/cp/ChangeLog: > > * constraint.cc (diagnose_trait_expr): Handle CPTK_IS_CONVERTIBLE > and CPTK_IS_NOTHROW_CONVERTIBLE. > * cp-objcp-common.cc (names_builtin_p): Handle RID_IS_CONVERTIBLE > RID_IS_NOTHROW_CONVERTIBLE. > * cp-tree.h (enum cp_trait_kind): Add CPTK_IS_CONVERTIBLE and > CPTK_IS_NOTHROW_CONVERTIBLE. > (is_convertible): Declare. > (is_nothrow_convertible): Likewise. > * cxx-pretty-print.cc (pp_cxx_trait_expression): Handle > CPTK_IS_CONVERTIBLE and CPTK_IS_NOTHROW_CONVERTIBLE. > * method.cc (is_convertible): New. > (is_nothrow_convertible): Likewise. > * parser.cc (cp_parser_primary_expression): Handle RID_IS_CONVERTIBLE > and RID_IS_NOTHROW_CONVERTIBLE. > (cp_parser_trait_expr): Likewise. > * semantics.cc (trait_expr_value): Handle CPTK_IS_CONVERTIBLE and > CPTK_IS_NOTHROW_CONVERTIBLE. > (finish_trait_expr): Likewise. > > libstdc++-v3/ChangeLog: > > * include/std/type_traits: Rename __is_nothrow_convertible to > __is_nothrow_convertible_lib. > * testsuite/20_util/is_nothrow_convertible/value_ext.cc: Likewise. > > gcc/testsuite/ChangeLog: > > * g++.dg/ext/has-builtin-1.C: Enhance to test __is_convertible and > __is_nothrow_convertible. > * g++.dg/ext/is_convertible1.C: New test. > * g++.dg/ext/is_convertible2.C: New test. > * g++.dg/ext/is_nothrow_convertible1.C: New test. > * g++.dg/ext/is_nothrow_convertible2.C: New test. > --- > gcc/c-family/c-common.cc | 2 + > gcc/c-family/c-common.h | 1 + > gcc/cp/constraint.cc | 6 + > gcc/cp/cp-objcp-common.cc | 2 + > gcc/cp/cp-tree.h | 4 + > gcc/cp/cxx-pretty-print.cc | 6 + > gcc/cp/method.cc | 31 ++ > gcc/cp/parser.cc | 10 + > gcc/cp/semantics.cc | 8 + > gcc/testsuite/g++.dg/ext/has-builtin-1.C | 6 + > gcc/testsuite/g++.dg/ext/is_convertible1.C | 269 +++++++++++++++++ > gcc/testsuite/g++.dg/ext/is_convertible2.C | 46 +++ > .../g++.dg/ext/is_nothrow_convertible1.C | 270 ++++++++++++++++++ > .../g++.dg/ext/is_nothrow_convertible2.C | 19 ++ > libstdc++-v3/include/std/type_traits | 4 +- > .../is_nothrow_convertible/value_ext.cc | 4 +- > 16 files changed, 684 insertions(+), 4 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/ext/is_convertible1.C > create mode 100644 gcc/testsuite/g++.dg/ext/is_convertible2.C > create mode 100644 gcc/testsuite/g++.dg/ext/is_nothrow_convertible1.C > create mode 100644 gcc/testsuite/g++.dg/ext/is_nothrow_convertible2.C > > diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc > index c0f15f4cab1..dce3045c9f2 100644 > --- a/gcc/c-family/c-common.cc > +++ b/gcc/c-family/c-common.cc > @@ -541,6 +541,8 @@ const struct c_common_resword c_common_reswords[] = > { "__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY }, > { "__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY }, > { "__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE, D_CXXONLY }, > + { "__is_convertible", RID_IS_CONVERTIBLE, D_CXXONLY }, > + { "__is_nothrow_convertible", RID_IS_NOTHROW_CONVERTIBLE, D_CXXONLY }, > { "__reference_constructs_from_temporary", RID_REF_CONSTRUCTS_FROM_TEMPORARY, > D_CXXONLY }, > { "__reference_converts_from_temporary", RID_REF_CONVERTS_FROM_TEMPORARY, > diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h > index 2f592f5cd58..31397d80029 100644 > --- a/gcc/c-family/c-common.h > +++ b/gcc/c-family/c-common.h > @@ -184,6 +184,7 @@ enum rid > RID_IS_UNION, RID_UNDERLYING_TYPE, > RID_IS_ASSIGNABLE, RID_IS_CONSTRUCTIBLE, > RID_IS_NOTHROW_ASSIGNABLE, RID_IS_NOTHROW_CONSTRUCTIBLE, > + RID_IS_CONVERTIBLE, RID_IS_NOTHROW_CONVERTIBLE, > RID_REF_CONSTRUCTS_FROM_TEMPORARY, > RID_REF_CONVERTS_FROM_TEMPORARY, > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc > index 568318f0ba1..5839bfb4b52 100644 > --- a/gcc/cp/constraint.cc > +++ b/gcc/cp/constraint.cc > @@ -3697,6 +3697,12 @@ diagnose_trait_expr (tree expr, tree args) > case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS: > inform (loc, " %qT does not have unique object representations", t1); > break; > + case CPTK_IS_CONVERTIBLE: > + inform (loc, " %qT is not convertible from %qE", t2, t1); > + break; > + case CPTK_IS_NOTHROW_CONVERTIBLE: > + inform (loc, " %qT is not %<nothrow%> convertible from %qE", t2, t1); It's odd that the existing diagnostics quote "nothrow", which is not a keyword. I wonder why these library traits didn't use "noexcept"? > + break; > case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY: > inform (loc, " %qT is not a reference that binds to a temporary " > "object of type %qT (direct-initialization)", t1, t2); > diff --git a/gcc/cp/cp-objcp-common.cc b/gcc/cp/cp-objcp-common.cc > index 1ffac08c32f..64975699351 100644 > --- a/gcc/cp/cp-objcp-common.cc > +++ b/gcc/cp/cp-objcp-common.cc > @@ -463,6 +463,8 @@ names_builtin_p (const char *name) > case RID_IS_NOTHROW_ASSIGNABLE: > case RID_IS_NOTHROW_CONSTRUCTIBLE: > case RID_UNDERLYING_TYPE: > + case RID_IS_CONVERTIBLE: > + case RID_IS_NOTHROW_CONVERTIBLE: > case RID_REF_CONSTRUCTS_FROM_TEMPORARY: > case RID_REF_CONVERTS_FROM_TEMPORARY: > return true; > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h > index f19ecafc266..e4d89207e2a 100644 > --- a/gcc/cp/cp-tree.h > +++ b/gcc/cp/cp-tree.h > @@ -1407,6 +1407,8 @@ enum cp_trait_kind > CPTK_IS_CONSTRUCTIBLE, > CPTK_IS_NOTHROW_ASSIGNABLE, > CPTK_IS_NOTHROW_CONSTRUCTIBLE, > + CPTK_IS_CONVERTIBLE, > + CPTK_IS_NOTHROW_CONVERTIBLE, > CPTK_REF_CONSTRUCTS_FROM_TEMPORARY, > CPTK_REF_CONVERTS_FROM_TEMPORARY > }; > @@ -7116,6 +7118,8 @@ extern tree forward_parm (tree); > extern bool is_trivially_xible (enum tree_code, tree, tree); > extern bool is_nothrow_xible (enum tree_code, tree, tree); > extern bool is_xible (enum tree_code, tree, tree); > +extern bool is_convertible (tree, tree); > +extern bool is_nothrow_convertible (tree, tree); > extern bool ref_xes_from_temporary (tree, tree, bool); > extern tree get_defaulted_eh_spec (tree, tsubst_flags_t = tf_warning_or_error); > extern bool maybe_explain_implicit_delete (tree); > diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc > index 44590830a61..e18143e39a9 100644 > --- a/gcc/cp/cxx-pretty-print.cc > +++ b/gcc/cp/cxx-pretty-print.cc > @@ -2696,6 +2696,12 @@ pp_cxx_trait_expression (cxx_pretty_printer *pp, tree t) > case CPTK_IS_NOTHROW_CONSTRUCTIBLE: > pp_cxx_ws_string (pp, "__is_nothrow_constructible"); > break; > + case CPTK_IS_CONVERTIBLE: > + pp_cxx_ws_string (pp, "__is_convertible"); > + break; > + case CPTK_IS_NOTHROW_CONVERTIBLE: > + pp_cxx_ws_string (pp, "__is_nothrow_convertible"); > + break; > case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY: > pp_cxx_ws_string (pp, "__reference_constructs_from_temporary"); > break; > diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc > index 573ef016f82..c35a59fe56c 100644 > --- a/gcc/cp/method.cc > +++ b/gcc/cp/method.cc > @@ -2236,6 +2236,37 @@ ref_xes_from_temporary (tree to, tree from, bool direct_init_p) > return ref_conv_binds_directly (to, val, direct_init_p).is_false (); > } > > +/* Return true if FROM can be converted to TO using implicit conversions, > + or both FROM and TO are possibly cv-qualified void. NB: This doesn't > + implement the "Access checks are performed as if from a context unrelated > + to either type" restriction. */ > + > +bool > +is_convertible (tree from, tree to) You didn't want to add conversion to is*_xible? > +{ > + if (VOID_TYPE_P (from) && VOID_TYPE_P (to)) > + return true; > + tree expr = build_stub_object (from); > + expr = perform_implicit_conversion (to, expr, tf_none); > + if (expr == error_mark_node) > + return false; > + return !!expr; > +} > + > +/* Like is_convertible, but the conversion is also noexcept. */ > + > +bool > +is_nothrow_convertible (tree from, tree to) > +{ > + if (VOID_TYPE_P (from) && VOID_TYPE_P (to)) > + return true; > + tree expr = build_stub_object (from); > + expr = perform_implicit_conversion (to, expr, tf_none); > + if (expr == NULL_TREE || expr == error_mark_node) > + return false; > + return expr_noexcept_p (expr, tf_none); > +} > + > /* Categorize various special_function_kinds. */ > #define SFK_CTOR_P(sfk) \ > ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor) > diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc > index 3cbe0d69de1..bb83d1c78f6 100644 > --- a/gcc/cp/parser.cc > +++ b/gcc/cp/parser.cc > @@ -5922,6 +5922,8 @@ cp_parser_primary_expression (cp_parser *parser, > case RID_IS_CONSTRUCTIBLE: > case RID_IS_NOTHROW_ASSIGNABLE: > case RID_IS_NOTHROW_CONSTRUCTIBLE: > + case RID_IS_CONVERTIBLE: > + case RID_IS_NOTHROW_CONVERTIBLE: > case RID_REF_CONSTRUCTS_FROM_TEMPORARY: > case RID_REF_CONVERTS_FROM_TEMPORARY: > return cp_parser_trait_expr (parser, token->keyword); > @@ -11008,6 +11010,14 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword) > kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE; > variadic = true; > break; > + case RID_IS_CONVERTIBLE: > + kind = CPTK_IS_CONVERTIBLE; > + binary = true; > + break; > + case RID_IS_NOTHROW_CONVERTIBLE: > + kind = CPTK_IS_NOTHROW_CONVERTIBLE; > + binary = true; > + break; > case RID_REF_CONSTRUCTS_FROM_TEMPORARY: > kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY; > binary = true; > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc > index 86562071612..92fc795df40 100644 > --- a/gcc/cp/semantics.cc > +++ b/gcc/cp/semantics.cc > @@ -12044,6 +12044,12 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2) > case CPTK_IS_NOTHROW_CONSTRUCTIBLE: > return is_nothrow_xible (INIT_EXPR, type1, type2); > > + case CPTK_IS_CONVERTIBLE: > + return is_convertible (type1, type2); > + > + case CPTK_IS_NOTHROW_CONVERTIBLE: > + return is_nothrow_convertible (type1, type2); > + > case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY: > return ref_xes_from_temporary (type1, type2, /*direct_init=*/true); > > @@ -12165,6 +12171,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2) > case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE: > case CPTK_IS_NOTHROW_ASSIGNABLE: > case CPTK_IS_NOTHROW_CONSTRUCTIBLE: > + case CPTK_IS_CONVERTIBLE: > + case CPTK_IS_NOTHROW_CONVERTIBLE: > case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY: > case CPTK_REF_CONVERTS_FROM_TEMPORARY: > if (!check_trait_type (type1) > diff --git a/gcc/testsuite/g++.dg/ext/has-builtin-1.C b/gcc/testsuite/g++.dg/ext/has-builtin-1.C > index fe25cb2f669..17dabf648cf 100644 > --- a/gcc/testsuite/g++.dg/ext/has-builtin-1.C > +++ b/gcc/testsuite/g++.dg/ext/has-builtin-1.C > @@ -131,3 +131,9 @@ > #if !__has_builtin (__builtin_is_pointer_interconvertible_with_class) > # error "__has_builtin (__builtin_is_pointer_interconvertible_with_class) failed" > #endif > +#if !__has_builtin (__is_convertible) > +# error "__has_builtin (__is_convertible) failed" > +#endif > +#if !__has_builtin (__is_nothrow_convertible) > +# error "__has_builtin (__is_nothrow_convertible) failed" > +#endif > diff --git a/gcc/testsuite/g++.dg/ext/is_convertible1.C b/gcc/testsuite/g++.dg/ext/is_convertible1.C > new file mode 100644 > index 00000000000..2e72945bceb > --- /dev/null > +++ b/gcc/testsuite/g++.dg/ext/is_convertible1.C > @@ -0,0 +1,269 @@ > +// PR c++/106784 > +// { dg-do compile { target c++11 } } > + > +#define SA(X) static_assert((X),#X) > + > +template<typename From, typename To> > +struct is_convertible { > + static const bool value = __is_convertible(From, To); > +}; > + > +struct from_int { > + from_int(int); > +}; > + > +struct from_charp { > + from_charp(const char *); > +}; > + > +struct to_int { > + operator int(); > +}; > + > +typedef int Fn(int); > +typedef char Arr[10]; > +enum E { XYZZY }; > + > +SA(!__is_convertible(int, void)); > +SA(__is_convertible(int, int)); > +SA(__is_convertible(int, from_int)); > +SA(__is_convertible(long, from_int)); > +SA(__is_convertible(double, from_int)); > +SA(__is_convertible(const int, from_int)); > +SA(__is_convertible(const int&, from_int)); > +SA(__is_convertible(to_int, int)); > +SA(__is_convertible(to_int, const int&)); > +SA(__is_convertible(to_int, long)); > +SA(!__is_convertible(to_int, int&)); > +SA(!__is_convertible(to_int, from_int)); > +SA(!__is_convertible(int, Fn)); > +SA(!__is_convertible(int, Fn*)); > +SA(!__is_convertible(int, Fn&)); > +SA(!__is_convertible(int, Arr)); > +SA(!__is_convertible(int, Arr&)); > +SA(!__is_convertible(int, int&)); > +SA(__is_convertible(int, const int&)); > +SA(!__is_convertible(const int, int&)); > +SA(__is_convertible(const int, const int&)); > +SA(!__is_convertible(int, int*)); > + > +SA(!__is_convertible(int, E)); > +SA(__is_convertible(E, int)); > + > +SA(__is_convertible(int&, int)); > +SA(__is_convertible(int&, int&)); > +SA(__is_convertible(int&, const int&)); > +SA(!__is_convertible(const int&, int&)); > +SA(__is_convertible(const int&, const int&)); > +SA(!__is_convertible(int&, int*)); > +SA(!__is_convertible(int&, void)); > +SA(!__is_convertible(int&, Fn)); > +SA(!__is_convertible(int&, Fn*)); > +SA(!__is_convertible(int&, Fn&)); > +SA(!__is_convertible(int&, Arr)); > +SA(!__is_convertible(int&, Arr&)); > + > +SA(!__is_convertible(int*, int)); > +SA(!__is_convertible(int*, int&)); > +SA(!__is_convertible(int*, void)); > +SA(__is_convertible(int*, int*)); > +SA(__is_convertible(int*, const int*)); > +SA(!__is_convertible(const int*, int*)); > +SA(__is_convertible(const int*, const int*)); > +SA(!__is_convertible(int*, Fn)); > +SA(!__is_convertible(int*, Fn*)); > +SA(!__is_convertible(int*, Fn&)); > +SA(!__is_convertible(int*, Arr)); > +SA(!__is_convertible(int*, Arr&)); > +SA(!__is_convertible(int*, float*)); > + > +SA(__is_convertible(void, void)); > +SA(!__is_convertible(void, char)); > +SA(!__is_convertible(void, char&)); > +SA(!__is_convertible(void, char*)); > +SA(!__is_convertible(char, void)); > +SA(__is_convertible(const void, void)); > +SA(__is_convertible(void, const void)); > +SA(__is_convertible(const void, const void)); > +SA(!__is_convertible(void, Fn)); > +SA(!__is_convertible(void, Fn&)); > +SA(!__is_convertible(void, Fn*)); > +SA(!__is_convertible(void, Arr)); > +SA(!__is_convertible(void, Arr&)); > + > +SA(!__is_convertible(Fn, void)); > +SA(!__is_convertible(Fn, Fn)); > +SA(__is_convertible(Fn, Fn*)); > +SA(__is_convertible(Fn, Fn&)); > +SA(!__is_convertible(int(int), int(int))); > +SA(__is_convertible(int(int), int(&)(int))); > +SA(__is_convertible(int(int), int(&&)(int))); > +SA(__is_convertible(int(int), int(*)(int))); > +SA(__is_convertible(int(int), int(*const)(int))); > +SA(!__is_convertible(int(int), char)); > +SA(!__is_convertible(int(int), char*)); > +SA(!__is_convertible(int(int), char&)); > + > +SA(!__is_convertible(Fn&, void)); > +SA(!__is_convertible(Fn&, Fn)); > +SA(__is_convertible(Fn&, Fn&)); > +SA(__is_convertible(Fn&, Fn*)); > +SA(!__is_convertible(Fn&, Arr)); > +SA(!__is_convertible(Fn&, Arr&)); > +SA(!__is_convertible(Fn&, char)); > +SA(!__is_convertible(Fn&, char&)); > +SA(!__is_convertible(Fn&, char*)); > + > +SA(!__is_convertible(Fn*, void)); > +SA(!__is_convertible(Fn*, Fn)); > +SA(!__is_convertible(Fn*, Fn&)); > +SA(__is_convertible(Fn*, Fn*)); > +SA(!__is_convertible(Fn*, Arr)); > +SA(!__is_convertible(Fn*, Arr&)); > +SA(!__is_convertible(Fn*, char)); > +SA(!__is_convertible(Fn*, char&)); > +SA(!__is_convertible(Fn*, char*)); > + > +SA(!__is_convertible(Arr, void)); > +SA(!__is_convertible(Arr, Fn)); > +SA(!__is_convertible(Arr, Fn*)); > +SA(!__is_convertible(Arr, Fn&)); > +SA(!__is_convertible(Arr, Arr)); > +SA(!__is_convertible(Arr, Arr&)); > +SA(__is_convertible(Arr, const Arr&)); > +SA(!__is_convertible(Arr, volatile Arr&)); > +SA(!__is_convertible(Arr, const volatile Arr&)); > +SA(!__is_convertible(const Arr, Arr&)); > +SA(__is_convertible(const Arr, const Arr&)); > +SA(__is_convertible(Arr, Arr&&)); > +SA(__is_convertible(Arr, const Arr&&)); > +SA(__is_convertible(Arr, volatile Arr&&)); > +SA(__is_convertible(Arr, const volatile Arr&&)); > +SA(__is_convertible(const Arr, const Arr&&)); > +SA(!__is_convertible(Arr&, Arr&&)); > +SA(!__is_convertible(Arr&&, Arr&)); > +SA(!__is_convertible(Arr, char)); > +SA(__is_convertible(Arr, char*)); > +SA(__is_convertible(Arr, const char*)); > +SA(!__is_convertible(Arr, char&)); > +SA(!__is_convertible(const Arr, char*)); > +SA(__is_convertible(const Arr, const char*)); > +SA(!__is_convertible(int, int[1])); > +SA(!__is_convertible(int[1], int[1])); > +SA(!__is_convertible(int[1], int(&)[1])); > +SA(__is_convertible(int(&)[1], int(&)[1])); > +SA(__is_convertible(int(&)[1], const int(&)[1])); > +SA(!__is_convertible(const int(&)[1], int(&)[1])); > +SA(!__is_convertible(int[1][1], int*)); > +SA(!__is_convertible(int[][1], int*)); > + > +SA(!__is_convertible(Arr&, void)); > +SA(!__is_convertible(Arr&, Fn)); > +SA(!__is_convertible(Arr&, Fn*)); > +SA(!__is_convertible(Arr&, Fn&)); > +SA(!__is_convertible(Arr&, Arr)); > +SA(__is_convertible(Arr&, Arr&)); > +SA(__is_convertible(Arr&, const Arr&)); > +SA(!__is_convertible(const Arr&, Arr&)); > +SA(__is_convertible(const Arr&, const Arr&)); > +SA(!__is_convertible(Arr&, char)); > +SA(__is_convertible(Arr&, char*)); > +SA(__is_convertible(Arr&, const char*)); > +SA(!__is_convertible(Arr&, char&)); > +SA(!__is_convertible(const Arr&, char*)); > +SA(__is_convertible(const Arr&, const char*)); > +SA(__is_convertible(Arr, from_charp)); > +SA(__is_convertible(Arr&, from_charp)); > + > +struct B { }; > +struct D : B { }; > + > +SA(__is_convertible(D, B)); > +SA(__is_convertible(D*, B*)); > +SA(__is_convertible(D&, B&)); > +SA(!__is_convertible(B, D)); > +SA(!__is_convertible(B*, D*)); > +SA(!__is_convertible(B&, D&)); > + > +/* These are taken from LLVM's test/SemaCXX/type-traits.cpp. */ > + > +struct I { > + int i; > + I(int _i) : i(_i) { } > + operator int() const { > + return i; > + } > +}; > + > +struct F > +{ > + float f; > + F(float _f) : f(_f) {} > + F(const I& obj) > + : f(static_cast<float>(obj.i)) {} > + operator float() const { > + return f; > + } > + operator I() const { > + return I(static_cast<int>(f)); > + } > +}; > + > +SA(__is_convertible(I, I)); > +SA(__is_convertible(I, const I)); > +SA(__is_convertible(I, int)); > +SA(__is_convertible(int, I)); > +SA(__is_convertible(I, F)); > +SA(__is_convertible(F, I)); > +SA(__is_convertible(F, float)); > +SA(__is_convertible(float, F)); > + > +template<typename> > +struct X { > + template<typename U> X(const X<U>&); > +}; > + > +SA(__is_convertible(X<int>, X<float>)); > +SA(__is_convertible(X<float>, X<int>)); > + > +struct Abstract { > + virtual void f() = 0; > +}; > + > +SA(!__is_convertible(Abstract, Abstract)); > + > +class hidden { > + hidden(const hidden&); > + friend void test (); > +}; > + > +SA(__is_convertible(hidden&, hidden&)); > +SA(__is_convertible(hidden&, const hidden&)); > +SA(__is_convertible(hidden&, volatile hidden&)); > +SA(__is_convertible(hidden&, const volatile hidden&)); > +SA(__is_convertible(const hidden&, const hidden&)); > +SA(__is_convertible(const hidden&, const volatile hidden&)); > +SA(__is_convertible(volatile hidden&, const volatile hidden&)); > +SA(__is_convertible(const volatile hidden&, const volatile hidden&)); > +SA(!__is_convertible(const hidden&, hidden&)); > + > +void > +test () > +{ > + /* __is_convertible(hidden, hidden) should be false despite the > + friend declaration above, because "Access checks are performed > + as if from a context unrelated to either type", but we don't > + implement that for the built-in (std::is_convertible works as > + expected). This is the case for __is_assignable as well. */ > + //SA(!__is_convertible(hidden, hidden)); > +} > + > +void > +test2 () > +{ > + struct X { }; > + struct Y { > + explicit Y(X); // not viable for implicit conversions > + }; > + SA(!__is_convertible(X, Y)); > +} > diff --git a/gcc/testsuite/g++.dg/ext/is_convertible2.C b/gcc/testsuite/g++.dg/ext/is_convertible2.C > new file mode 100644 > index 00000000000..9b46e264379 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/ext/is_convertible2.C > @@ -0,0 +1,46 @@ > +// PR c++/106784 > +// { dg-do compile { target c++20 } } > +// Adapted from <https://en.cppreference.com/w/cpp/types/is_convertible>. > + > +#include <string> > +#include <string_view> > + > +#define SA(X) static_assert((X),#X) > + > +class E { public: template<class T> E(T&&) { } }; > + > +int main() > +{ > + class A {}; > + class B : public A {}; > + class C {}; > + class D { public: operator C() { return c; } C c; }; > + > + SA(__is_convertible(B*, A*)); > + SA(!__is_convertible(A*, B*)); > + SA(__is_convertible(D, C)); > + SA(!__is_convertible(B*, C*)); > + SA(__is_convertible(A, E)); > + > + using std::operator "" s, std::operator "" sv; > + > + auto stringify = []<typename T>(T x) { > + if constexpr (std::is_convertible_v<T, std::string> or > + std::is_convertible_v<T, std::string_view>) { > + return x; > + } else { > + return std::to_string(x); > + } > + }; > + > + const char* three = "three"; > + > + SA(!__is_convertible(std::string_view, std::string)); > + SA(__is_convertible(std::string, std::string_view)); > + > + auto s1 = stringify("one"s); > + auto s2 = stringify("two"sv); > + auto s3 = stringify(three); > + auto s4 = stringify(42); > + auto s5 = stringify(42.); > +} > diff --git a/gcc/testsuite/g++.dg/ext/is_nothrow_convertible1.C b/gcc/testsuite/g++.dg/ext/is_nothrow_convertible1.C > new file mode 100644 > index 00000000000..bb7243e4611 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/ext/is_nothrow_convertible1.C > @@ -0,0 +1,270 @@ > +// PR c++/106784 > +// { dg-do compile { target c++11 } } > +// Like is_convertible1.C, but conversion functions are made noexcept. > + > +#define SA(X) static_assert((X),#X) > + > +template<typename From, typename To> > +struct is_nothrow_convertible { > + static const bool value = __is_nothrow_convertible(From, To); > +}; > + > +struct from_int { > + from_int(int) noexcept; > +}; > + > +struct from_charp { > + from_charp(const char *) noexcept; > +}; > + > +struct to_int { > + operator int() noexcept; > +}; > + > +typedef int Fn(int); > +typedef char Arr[10]; > +enum E { XYZZY }; > + > +SA(!__is_nothrow_convertible(int, void)); > +SA(__is_nothrow_convertible(int, int)); > +SA(__is_nothrow_convertible(int, from_int)); > +SA(__is_nothrow_convertible(long, from_int)); > +SA(__is_nothrow_convertible(double, from_int)); > +SA(__is_nothrow_convertible(const int, from_int)); > +SA(__is_nothrow_convertible(const int&, from_int)); > +SA(__is_nothrow_convertible(to_int, int)); > +SA(__is_nothrow_convertible(to_int, const int&)); > +SA(__is_nothrow_convertible(to_int, long)); > +SA(!__is_nothrow_convertible(to_int, int&)); > +SA(!__is_nothrow_convertible(to_int, from_int)); > +SA(!__is_nothrow_convertible(int, Fn)); > +SA(!__is_nothrow_convertible(int, Fn*)); > +SA(!__is_nothrow_convertible(int, Fn&)); > +SA(!__is_nothrow_convertible(int, Arr)); > +SA(!__is_nothrow_convertible(int, Arr&)); > +SA(!__is_nothrow_convertible(int, int&)); > +SA(__is_nothrow_convertible(int, const int&)); > +SA(!__is_nothrow_convertible(const int, int&)); > +SA(__is_nothrow_convertible(const int, const int&)); > +SA(!__is_nothrow_convertible(int, int*)); > + > +SA(!__is_nothrow_convertible(int, E)); > +SA(__is_nothrow_convertible(E, int)); > + > +SA(__is_nothrow_convertible(int&, int)); > +SA(__is_nothrow_convertible(int&, int&)); > +SA(__is_nothrow_convertible(int&, const int&)); > +SA(!__is_nothrow_convertible(const int&, int&)); > +SA(__is_nothrow_convertible(const int&, const int&)); > +SA(!__is_nothrow_convertible(int&, int*)); > +SA(!__is_nothrow_convertible(int&, void)); > +SA(!__is_nothrow_convertible(int&, Fn)); > +SA(!__is_nothrow_convertible(int&, Fn*)); > +SA(!__is_nothrow_convertible(int&, Fn&)); > +SA(!__is_nothrow_convertible(int&, Arr)); > +SA(!__is_nothrow_convertible(int&, Arr&)); > + > +SA(!__is_nothrow_convertible(int*, int)); > +SA(!__is_nothrow_convertible(int*, int&)); > +SA(!__is_nothrow_convertible(int*, void)); > +SA(__is_nothrow_convertible(int*, int*)); > +SA(__is_nothrow_convertible(int*, const int*)); > +SA(!__is_nothrow_convertible(const int*, int*)); > +SA(__is_nothrow_convertible(const int*, const int*)); > +SA(!__is_nothrow_convertible(int*, Fn)); > +SA(!__is_nothrow_convertible(int*, Fn*)); > +SA(!__is_nothrow_convertible(int*, Fn&)); > +SA(!__is_nothrow_convertible(int*, Arr)); > +SA(!__is_nothrow_convertible(int*, Arr&)); > +SA(!__is_nothrow_convertible(int*, float*)); > + > +SA(__is_nothrow_convertible(void, void)); > +SA(!__is_nothrow_convertible(void, char)); > +SA(!__is_nothrow_convertible(void, char&)); > +SA(!__is_nothrow_convertible(void, char*)); > +SA(!__is_nothrow_convertible(char, void)); > +SA(__is_nothrow_convertible(const void, void)); > +SA(__is_nothrow_convertible(void, const void)); > +SA(__is_nothrow_convertible(const void, const void)); > +SA(!__is_nothrow_convertible(void, Fn)); > +SA(!__is_nothrow_convertible(void, Fn&)); > +SA(!__is_nothrow_convertible(void, Fn*)); > +SA(!__is_nothrow_convertible(void, Arr)); > +SA(!__is_nothrow_convertible(void, Arr&)); > + > +SA(!__is_nothrow_convertible(Fn, void)); > +SA(!__is_nothrow_convertible(Fn, Fn)); > +SA(__is_nothrow_convertible(Fn, Fn*)); > +SA(__is_nothrow_convertible(Fn, Fn&)); > +SA(!__is_nothrow_convertible(int(int), int(int))); > +SA(__is_nothrow_convertible(int(int), int(&)(int))); > +SA(__is_nothrow_convertible(int(int), int(&&)(int))); > +SA(__is_nothrow_convertible(int(int), int(*)(int))); > +SA(__is_nothrow_convertible(int(int), int(*const)(int))); > +SA(!__is_nothrow_convertible(int(int), char)); > +SA(!__is_nothrow_convertible(int(int), char*)); > +SA(!__is_nothrow_convertible(int(int), char&)); > + > +SA(!__is_nothrow_convertible(Fn&, void)); > +SA(!__is_nothrow_convertible(Fn&, Fn)); > +SA(__is_nothrow_convertible(Fn&, Fn&)); > +SA(__is_nothrow_convertible(Fn&, Fn*)); > +SA(!__is_nothrow_convertible(Fn&, Arr)); > +SA(!__is_nothrow_convertible(Fn&, Arr&)); > +SA(!__is_nothrow_convertible(Fn&, char)); > +SA(!__is_nothrow_convertible(Fn&, char&)); > +SA(!__is_nothrow_convertible(Fn&, char*)); > + > +SA(!__is_nothrow_convertible(Fn*, void)); > +SA(!__is_nothrow_convertible(Fn*, Fn)); > +SA(!__is_nothrow_convertible(Fn*, Fn&)); > +SA(__is_nothrow_convertible(Fn*, Fn*)); > +SA(!__is_nothrow_convertible(Fn*, Arr)); > +SA(!__is_nothrow_convertible(Fn*, Arr&)); > +SA(!__is_nothrow_convertible(Fn*, char)); > +SA(!__is_nothrow_convertible(Fn*, char&)); > +SA(!__is_nothrow_convertible(Fn*, char*)); > + > +SA(!__is_nothrow_convertible(Arr, void)); > +SA(!__is_nothrow_convertible(Arr, Fn)); > +SA(!__is_nothrow_convertible(Arr, Fn*)); > +SA(!__is_nothrow_convertible(Arr, Fn&)); > +SA(!__is_nothrow_convertible(Arr, Arr)); > +SA(!__is_nothrow_convertible(Arr, Arr&)); > +SA(__is_nothrow_convertible(Arr, const Arr&)); > +SA(!__is_nothrow_convertible(Arr, volatile Arr&)); > +SA(!__is_nothrow_convertible(Arr, const volatile Arr&)); > +SA(!__is_nothrow_convertible(const Arr, Arr&)); > +SA(__is_nothrow_convertible(const Arr, const Arr&)); > +SA(__is_nothrow_convertible(Arr, Arr&&)); > +SA(__is_nothrow_convertible(Arr, const Arr&&)); > +SA(__is_nothrow_convertible(Arr, volatile Arr&&)); > +SA(__is_nothrow_convertible(Arr, const volatile Arr&&)); > +SA(__is_nothrow_convertible(const Arr, const Arr&&)); > +SA(!__is_nothrow_convertible(Arr&, Arr&&)); > +SA(!__is_nothrow_convertible(Arr&&, Arr&)); > +SA(!__is_nothrow_convertible(Arr, char)); > +SA(__is_nothrow_convertible(Arr, char*)); > +SA(__is_nothrow_convertible(Arr, const char*)); > +SA(!__is_nothrow_convertible(Arr, char&)); > +SA(!__is_nothrow_convertible(const Arr, char*)); > +SA(__is_nothrow_convertible(const Arr, const char*)); > +SA(!__is_nothrow_convertible(int, int[1])); > +SA(!__is_nothrow_convertible(int[1], int[1])); > +SA(!__is_nothrow_convertible(int[1], int(&)[1])); > +SA(__is_nothrow_convertible(int(&)[1], int(&)[1])); > +SA(__is_nothrow_convertible(int(&)[1], const int(&)[1])); > +SA(!__is_nothrow_convertible(const int(&)[1], int(&)[1])); > +SA(!__is_nothrow_convertible(int[1][1], int*)); > +SA(!__is_nothrow_convertible(int[][1], int*)); > + > +SA(!__is_nothrow_convertible(Arr&, void)); > +SA(!__is_nothrow_convertible(Arr&, Fn)); > +SA(!__is_nothrow_convertible(Arr&, Fn*)); > +SA(!__is_nothrow_convertible(Arr&, Fn&)); > +SA(!__is_nothrow_convertible(Arr&, Arr)); > +SA(__is_nothrow_convertible(Arr&, Arr&)); > +SA(__is_nothrow_convertible(Arr&, const Arr&)); > +SA(!__is_nothrow_convertible(const Arr&, Arr&)); > +SA(__is_nothrow_convertible(const Arr&, const Arr&)); > +SA(!__is_nothrow_convertible(Arr&, char)); > +SA(__is_nothrow_convertible(Arr&, char*)); > +SA(__is_nothrow_convertible(Arr&, const char*)); > +SA(!__is_nothrow_convertible(Arr&, char&)); > +SA(!__is_nothrow_convertible(const Arr&, char*)); > +SA(__is_nothrow_convertible(const Arr&, const char*)); > +SA(__is_nothrow_convertible(Arr, from_charp)); > +SA(__is_nothrow_convertible(Arr&, from_charp)); > + > +struct B { }; > +struct D : B { }; > + > +SA(__is_nothrow_convertible(D, B)); > +SA(__is_nothrow_convertible(D*, B*)); > +SA(__is_nothrow_convertible(D&, B&)); > +SA(!__is_nothrow_convertible(B, D)); > +SA(!__is_nothrow_convertible(B*, D*)); > +SA(!__is_nothrow_convertible(B&, D&)); > + > +/* These are taken from LLVM's test/SemaCXX/type-traits.cpp. */ > + > +struct I { > + int i; > + I(int _i) noexcept : i(_i) { } > + operator int() const noexcept { > + return i; > + } > +}; > + > +struct F > +{ > + float f; > + F(float _f) noexcept : f(_f) {} > + F(const I& obj) noexcept > + : f(static_cast<float>(obj.i)) {} > + operator float() const noexcept { > + return f; > + } > + operator I() const noexcept { > + return I(static_cast<int>(f)); > + } > +}; > + > +SA(__is_nothrow_convertible(I, I)); > +SA(__is_nothrow_convertible(I, const I)); > +SA(__is_nothrow_convertible(I, int)); > +SA(__is_nothrow_convertible(int, I)); > +SA(__is_nothrow_convertible(I, F)); > +SA(__is_nothrow_convertible(F, I)); > +SA(__is_nothrow_convertible(F, float)); > +SA(__is_nothrow_convertible(float, F)); > + > +template<typename> > +struct X { > + template<typename U> X(const X<U>&) noexcept; > +}; > + > +SA(__is_nothrow_convertible(X<int>, X<float>)); > +SA(__is_nothrow_convertible(X<float>, X<int>)); > + > +struct Abstract { > + virtual void f() = 0; > +}; > + > +SA(!__is_nothrow_convertible(Abstract, Abstract)); > + > +class hidden { > + hidden(const hidden&); > + friend void test (); > +}; > + > +SA(__is_nothrow_convertible(hidden&, hidden&)); > +SA(__is_nothrow_convertible(hidden&, const hidden&)); > +SA(__is_nothrow_convertible(hidden&, volatile hidden&)); > +SA(__is_nothrow_convertible(hidden&, const volatile hidden&)); > +SA(__is_nothrow_convertible(const hidden&, const hidden&)); > +SA(__is_nothrow_convertible(const hidden&, const volatile hidden&)); > +SA(__is_nothrow_convertible(volatile hidden&, const volatile hidden&)); > +SA(__is_nothrow_convertible(const volatile hidden&, const volatile hidden&)); > +SA(!__is_nothrow_convertible(const hidden&, hidden&)); > + > +void > +test () > +{ > + /* __is_nothrow_convertible(hidden, hidden) should be false despite the > + friend declaration above, because "Access checks are performed > + as if from a context unrelated to either type", but we don't > + implement that for the built-in (std::is_convertible works as > + expected). This is the case for __is_assignable as well. */ > + //SA(!__is_nothrow_convertible(hidden, hidden)); > +} > + > +void > +test2 () > +{ > + struct X { }; > + struct Y { > + explicit Y(X); // not viable for implicit conversions > + }; > + SA(!__is_nothrow_convertible(X, Y)); > +} > diff --git a/gcc/testsuite/g++.dg/ext/is_nothrow_convertible2.C b/gcc/testsuite/g++.dg/ext/is_nothrow_convertible2.C > new file mode 100644 > index 00000000000..aa089173b75 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/ext/is_nothrow_convertible2.C > @@ -0,0 +1,19 @@ > +// PR c++/106784 > +// { dg-do compile { target c++11 } } > + > +#define SA(X) static_assert((X),#X) > + > +struct A { }; > +struct B { }; > + > +struct M { > + operator A(); > + operator B() noexcept; > + M(const A&); > + M(const B&) noexcept; > +}; > + > +SA(!__is_nothrow_convertible(A, M)); > +SA(!__is_nothrow_convertible(M, A)); > +SA(__is_nothrow_convertible(B, M)); > +SA(__is_nothrow_convertible(M, B)); > diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits > index 94e73eafd2f..1797b9e97f7 100644 > --- a/libstdc++-v3/include/std/type_traits > +++ b/libstdc++-v3/include/std/type_traits > @@ -1453,7 +1453,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > // is_nothrow_convertible for C++11 > template<typename _From, typename _To> > - struct __is_nothrow_convertible > + struct __is_nothrow_convertible_lib > : public __is_nt_convertible_helper<_From, _To>::type > { }; > > @@ -2999,7 +2999,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > struct __is_nt_invocable_impl<_Result, _Ret, > __void_t<typename _Result::type>> > : __or_<is_void<_Ret>, > - __is_nothrow_convertible<typename _Result::type, _Ret>>::type > + __is_nothrow_convertible_lib<typename _Result::type, _Ret>>::type > { }; > /// @endcond > > diff --git a/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value_ext.cc b/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value_ext.cc > index 0f896428537..d736d2ca260 100644 > --- a/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value_ext.cc > +++ b/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value_ext.cc > @@ -19,10 +19,10 @@ > > #include <type_traits> > > -// Test the non-standard __is_nothrow_convertible trait > +// Test the non-standard __is_nothrow_convertible_lib trait > > template<typename From, typename To> > - using is_nothrow_convertible = std::__is_nothrow_convertible<From, To>; > + using is_nothrow_convertible = std::__is_nothrow_convertible_lib<From, To>; > > #define IS_NT_CONVERTIBLE_DEFINED > #include "value.cc" > > base-commit: 32d8123cd6ce87acb557aec230e8359051316f9f ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] c++: Implement __is_{nothrow_,}convertible [PR106784] 2022-09-22 22:14 ` Jason Merrill @ 2022-09-23 14:34 ` Marek Polacek 2022-09-23 14:43 ` Jonathan Wakely 2022-09-23 15:54 ` Jason Merrill 2022-09-23 14:40 ` Jonathan Wakely 1 sibling, 2 replies; 10+ messages in thread From: Marek Polacek @ 2022-09-23 14:34 UTC (permalink / raw) To: Jason Merrill; +Cc: GCC Patches, libstdc++, Jonathan Wakely On Thu, Sep 22, 2022 at 06:14:44PM -0400, Jason Merrill wrote: > On 9/22/22 09:39, Marek Polacek wrote: > > To improve compile times, the C++ library could use compiler built-ins > > rather than implementing std::is_convertible (and _nothrow) as class > > templates. This patch adds the built-ins. We already have > > __is_constructible and __is_assignable, and the nothrow forms of those. > > > > Microsoft (and clang, for compatibility) also provide an alias called > > __is_convertible_to. I did not add it, but it would be trivial to do > > so. > > > > I noticed that our __is_assignable doesn't implement the "Access checks > > are performed as if from a context unrelated to either type" requirement, > > therefore std::is_assignable / __is_assignable give two different results > > here: > > > > class S { > > operator int(); > > friend void g(); // #1 > > }; > > > > void > > g () > > { > > // #1 doesn't matter > > static_assert(std::is_assignable<int&, S>::value, ""); > > static_assert(__is_assignable(int&, S), ""); > > } > > > > This is not a problem if __is_assignable is not meant to be used by > > the users. > > That's fine, it's not. Okay then. libstdc++ needs to make sure then that it's handled right. > > This patch doesn't make libstdc++ use the new built-ins, but I had to > > rename a class otherwise its name would clash with the new built-in. > > Sigh, that's going to be a hassle when comparing compiler versions on > preprocessed code. Yeah, I guess :/. Kind of like __integer_pack / __make_integer_seq. > > --- a/gcc/cp/constraint.cc > > +++ b/gcc/cp/constraint.cc > > @@ -3697,6 +3697,12 @@ diagnose_trait_expr (tree expr, tree args) > > case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS: > > inform (loc, " %qT does not have unique object representations", t1); > > break; > > + case CPTK_IS_CONVERTIBLE: > > + inform (loc, " %qT is not convertible from %qE", t2, t1); > > + break; > > + case CPTK_IS_NOTHROW_CONVERTIBLE: > > + inform (loc, " %qT is not %<nothrow%> convertible from %qE", t2, t1); > > It's odd that the existing diagnostics quote "nothrow", which is not a > keyword. I wonder why these library traits didn't use "noexcept"? Eh, yeah, only "throw" is. The quotes were deliberately added in <https://gcc.gnu.org/pipermail/gcc-patches/2019-May/522333.html>. Should I prepare a separate patch to use "%<noexcept%>" rather than "%<nothrow%>"? OTOH, the traits have "nothrow" in their names, so maybe just go back to "nothrow"? > > --- a/gcc/cp/method.cc > > +++ b/gcc/cp/method.cc > > @@ -2236,6 +2236,37 @@ ref_xes_from_temporary (tree to, tree from, bool direct_init_p) > > return ref_conv_binds_directly (to, val, direct_init_p).is_false (); > > } > > +/* Return true if FROM can be converted to TO using implicit conversions, > > + or both FROM and TO are possibly cv-qualified void. NB: This doesn't > > + implement the "Access checks are performed as if from a context unrelated > > + to either type" restriction. */ > > + > > +bool > > +is_convertible (tree from, tree to) > > You didn't want to add conversion to is*_xible? No, it didn't look like a good fit. It does things we don't need, and also has if VOID_TYPE_P -> return error_mark_node; which would be wrong for __is_convertible. I realized I'm not testing passing an incomplete type to the built-in, but since that is UB, I reckon we don't need to test it (we issue "error: invalid use of incomplete type"). Marek ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] c++: Implement __is_{nothrow_,}convertible [PR106784] 2022-09-23 14:34 ` Marek Polacek @ 2022-09-23 14:43 ` Jonathan Wakely 2022-09-23 16:34 ` Jonathan Wakely 2022-09-23 15:54 ` Jason Merrill 1 sibling, 1 reply; 10+ messages in thread From: Jonathan Wakely @ 2022-09-23 14:43 UTC (permalink / raw) To: Marek Polacek; +Cc: Jason Merrill, GCC Patches, libstdc++ On Fri, 23 Sept 2022 at 15:34, Marek Polacek wrote: > > On Thu, Sep 22, 2022 at 06:14:44PM -0400, Jason Merrill wrote: > > On 9/22/22 09:39, Marek Polacek wrote: > > > To improve compile times, the C++ library could use compiler built-ins > > > rather than implementing std::is_convertible (and _nothrow) as class > > > templates. This patch adds the built-ins. We already have > > > __is_constructible and __is_assignable, and the nothrow forms of those. > > > > > > Microsoft (and clang, for compatibility) also provide an alias called > > > __is_convertible_to. I did not add it, but it would be trivial to do > > > so. > > > > > > I noticed that our __is_assignable doesn't implement the "Access checks > > > are performed as if from a context unrelated to either type" requirement, > > > therefore std::is_assignable / __is_assignable give two different results > > > here: > > > > > > class S { > > > operator int(); > > > friend void g(); // #1 > > > }; > > > > > > void > > > g () > > > { > > > // #1 doesn't matter > > > static_assert(std::is_assignable<int&, S>::value, ""); > > > static_assert(__is_assignable(int&, S), ""); > > > } > > > > > > This is not a problem if __is_assignable is not meant to be used by > > > the users. > > > > That's fine, it's not. > > Okay then. libstdc++ needs to make sure then that it's handled right. That's fine, the type traits in libstdc++ are always "a context unrelated to either type", unless users do something idiotic like declare std::is_assignable as a friend. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1339r1.pdf wants to explicitly say that's idiotic. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] c++: Implement __is_{nothrow_,}convertible [PR106784] 2022-09-23 14:43 ` Jonathan Wakely @ 2022-09-23 16:34 ` Jonathan Wakely 2022-09-23 16:37 ` Marek Polacek 0 siblings, 1 reply; 10+ messages in thread From: Jonathan Wakely @ 2022-09-23 16:34 UTC (permalink / raw) To: Marek Polacek; +Cc: Jason Merrill, GCC Patches, libstdc++ On Fri, 23 Sept 2022 at 15:43, Jonathan Wakely wrote: > > On Fri, 23 Sept 2022 at 15:34, Marek Polacek wrote: > > > > On Thu, Sep 22, 2022 at 06:14:44PM -0400, Jason Merrill wrote: > > > On 9/22/22 09:39, Marek Polacek wrote: > > > > To improve compile times, the C++ library could use compiler built-ins > > > > rather than implementing std::is_convertible (and _nothrow) as class > > > > templates. This patch adds the built-ins. We already have > > > > __is_constructible and __is_assignable, and the nothrow forms of those. > > > > > > > > Microsoft (and clang, for compatibility) also provide an alias called > > > > __is_convertible_to. I did not add it, but it would be trivial to do > > > > so. > > > > > > > > I noticed that our __is_assignable doesn't implement the "Access checks > > > > are performed as if from a context unrelated to either type" requirement, > > > > therefore std::is_assignable / __is_assignable give two different results > > > > here: > > > > > > > > class S { > > > > operator int(); > > > > friend void g(); // #1 > > > > }; > > > > > > > > void > > > > g () > > > > { > > > > // #1 doesn't matter > > > > static_assert(std::is_assignable<int&, S>::value, ""); > > > > static_assert(__is_assignable(int&, S), ""); > > > > } > > > > > > > > This is not a problem if __is_assignable is not meant to be used by > > > > the users. > > > > > > That's fine, it's not. > > > > Okay then. libstdc++ needs to make sure then that it's handled right. > > That's fine, the type traits in libstdc++ are always "a context > unrelated to either type", unless users do something idiotic like > declare std::is_assignable as a friend. > > https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1339r1.pdf > wants to explicitly say that's idiotic. And I just checked that a variable template like std::is_assignable_v also counts as "a context unrelated to either type", even when instantiated inside a member function of the type: #include <type_traits> template<typename T, typename U> constexpr bool is_assignable_v = __is_assignable(T, U); class S { operator int(); friend void g(); // #1 }; void g () { // #1 doesn't matter static_assert(std::is_assignable<int&, S>::value, ""); static_assert(std::is_assignable_v<int&, S>, ""); static_assert(__is_assignable(int&, S), ""); } The first two assertions are consistent, and fail, which is what we want. The direct use of the built-in succeeds, but we don't care. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] c++: Implement __is_{nothrow_,}convertible [PR106784] 2022-09-23 16:34 ` Jonathan Wakely @ 2022-09-23 16:37 ` Marek Polacek 0 siblings, 0 replies; 10+ messages in thread From: Marek Polacek @ 2022-09-23 16:37 UTC (permalink / raw) To: Jonathan Wakely; +Cc: Jason Merrill, GCC Patches, libstdc++ On Fri, Sep 23, 2022 at 05:34:21PM +0100, Jonathan Wakely wrote: > On Fri, 23 Sept 2022 at 15:43, Jonathan Wakely wrote: > > > > On Fri, 23 Sept 2022 at 15:34, Marek Polacek wrote: > > > > > > On Thu, Sep 22, 2022 at 06:14:44PM -0400, Jason Merrill wrote: > > > > On 9/22/22 09:39, Marek Polacek wrote: > > > > > To improve compile times, the C++ library could use compiler built-ins > > > > > rather than implementing std::is_convertible (and _nothrow) as class > > > > > templates. This patch adds the built-ins. We already have > > > > > __is_constructible and __is_assignable, and the nothrow forms of those. > > > > > > > > > > Microsoft (and clang, for compatibility) also provide an alias called > > > > > __is_convertible_to. I did not add it, but it would be trivial to do > > > > > so. > > > > > > > > > > I noticed that our __is_assignable doesn't implement the "Access checks > > > > > are performed as if from a context unrelated to either type" requirement, > > > > > therefore std::is_assignable / __is_assignable give two different results > > > > > here: > > > > > > > > > > class S { > > > > > operator int(); > > > > > friend void g(); // #1 > > > > > }; > > > > > > > > > > void > > > > > g () > > > > > { > > > > > // #1 doesn't matter > > > > > static_assert(std::is_assignable<int&, S>::value, ""); > > > > > static_assert(__is_assignable(int&, S), ""); > > > > > } > > > > > > > > > > This is not a problem if __is_assignable is not meant to be used by > > > > > the users. > > > > > > > > That's fine, it's not. > > > > > > Okay then. libstdc++ needs to make sure then that it's handled right. > > > > That's fine, the type traits in libstdc++ are always "a context > > unrelated to either type", unless users do something idiotic like > > declare std::is_assignable as a friend. > > > > https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1339r1.pdf > > wants to explicitly say that's idiotic. > > And I just checked that a variable template like std::is_assignable_v > also counts as "a context unrelated to either type", even when > instantiated inside a member function of the type: > > #include <type_traits> > > template<typename T, typename U> > constexpr bool is_assignable_v = __is_assignable(T, U); > > class S { > operator int(); > friend void g(); // #1 > }; > > void > g () > { > // #1 doesn't matter > static_assert(std::is_assignable<int&, S>::value, ""); > static_assert(std::is_assignable_v<int&, S>, ""); > static_assert(__is_assignable(int&, S), ""); > } > > The first two assertions are consistent, and fail, which is what we > want. The direct use of the built-in succeeds, but we don't care. Great, thanks. Marek ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] c++: Implement __is_{nothrow_,}convertible [PR106784] 2022-09-23 14:34 ` Marek Polacek 2022-09-23 14:43 ` Jonathan Wakely @ 2022-09-23 15:54 ` Jason Merrill 2022-09-23 16:16 ` Marek Polacek 1 sibling, 1 reply; 10+ messages in thread From: Jason Merrill @ 2022-09-23 15:54 UTC (permalink / raw) To: Marek Polacek; +Cc: GCC Patches, libstdc++, Jonathan Wakely On 9/23/22 10:34, Marek Polacek wrote: > On Thu, Sep 22, 2022 at 06:14:44PM -0400, Jason Merrill wrote: >> On 9/22/22 09:39, Marek Polacek wrote: >>> To improve compile times, the C++ library could use compiler built-ins >>> rather than implementing std::is_convertible (and _nothrow) as class >>> templates. This patch adds the built-ins. We already have >>> __is_constructible and __is_assignable, and the nothrow forms of those. >>> >>> Microsoft (and clang, for compatibility) also provide an alias called >>> __is_convertible_to. I did not add it, but it would be trivial to do >>> so. >>> >>> I noticed that our __is_assignable doesn't implement the "Access checks >>> are performed as if from a context unrelated to either type" requirement, >>> therefore std::is_assignable / __is_assignable give two different results >>> here: >>> >>> class S { >>> operator int(); >>> friend void g(); // #1 >>> }; >>> >>> void >>> g () >>> { >>> // #1 doesn't matter >>> static_assert(std::is_assignable<int&, S>::value, ""); >>> static_assert(__is_assignable(int&, S), ""); >>> } >>> >>> This is not a problem if __is_assignable is not meant to be used by >>> the users. >> >> That's fine, it's not. > > Okay then. libstdc++ needs to make sure then that it's handled right. > >>> This patch doesn't make libstdc++ use the new built-ins, but I had to >>> rename a class otherwise its name would clash with the new built-in. >> >> Sigh, that's going to be a hassle when comparing compiler versions on >> preprocessed code. > > Yeah, I guess :/. Kind of like __integer_pack / __make_integer_seq. > >>> --- a/gcc/cp/constraint.cc >>> +++ b/gcc/cp/constraint.cc >>> @@ -3697,6 +3697,12 @@ diagnose_trait_expr (tree expr, tree args) >>> case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS: >>> inform (loc, " %qT does not have unique object representations", t1); >>> break; >>> + case CPTK_IS_CONVERTIBLE: >>> + inform (loc, " %qT is not convertible from %qE", t2, t1); >>> + break; >>> + case CPTK_IS_NOTHROW_CONVERTIBLE: >>> + inform (loc, " %qT is not %<nothrow%> convertible from %qE", t2, t1); >> >> It's odd that the existing diagnostics quote "nothrow", which is not a >> keyword. I wonder why these library traits didn't use "noexcept"? > > Eh, yeah, only "throw" is. The quotes were deliberately added in > <https://gcc.gnu.org/pipermail/gcc-patches/2019-May/522333.html>. Should > I prepare a separate patch to use "%<noexcept%>" rather than "%<nothrow%>"? > OTOH, the traits have "nothrow" in their names, so maybe just go back to > "nothrow"? The latter, I think. Or possibly "no-throw". I guess -Wformat-diag wants "nothrow" quoted because of the attribute of that name. >>> --- a/gcc/cp/method.cc >>> +++ b/gcc/cp/method.cc >>> @@ -2236,6 +2236,37 @@ ref_xes_from_temporary (tree to, tree from, bool direct_init_p) >>> return ref_conv_binds_directly (to, val, direct_init_p).is_false (); >>> } >>> +/* Return true if FROM can be converted to TO using implicit conversions, >>> + or both FROM and TO are possibly cv-qualified void. NB: This doesn't >>> + implement the "Access checks are performed as if from a context unrelated >>> + to either type" restriction. */ >>> + >>> +bool >>> +is_convertible (tree from, tree to) >> >> You didn't want to add conversion to is*_xible? > > No, it didn't look like a good fit. It does things we don't need, and > also has if VOID_TYPE_P -> return error_mark_node; which would be wrong > for __is_convertible. > > I realized I'm not testing passing an incomplete type to the built-in, > but since that is UB, I reckon we don't need to test it (we issue > "error: invalid use of incomplete type"). But your patch does test that, in the existing call to check_trait_type from finish_trait_expr? The patch is OK. Jason ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] c++: Implement __is_{nothrow_,}convertible [PR106784] 2022-09-23 15:54 ` Jason Merrill @ 2022-09-23 16:16 ` Marek Polacek 0 siblings, 0 replies; 10+ messages in thread From: Marek Polacek @ 2022-09-23 16:16 UTC (permalink / raw) To: Jason Merrill; +Cc: GCC Patches, libstdc++, Jonathan Wakely On Fri, Sep 23, 2022 at 11:54:53AM -0400, Jason Merrill wrote: > On 9/23/22 10:34, Marek Polacek wrote: > > On Thu, Sep 22, 2022 at 06:14:44PM -0400, Jason Merrill wrote: > > > On 9/22/22 09:39, Marek Polacek wrote: > > > > To improve compile times, the C++ library could use compiler built-ins > > > > rather than implementing std::is_convertible (and _nothrow) as class > > > > templates. This patch adds the built-ins. We already have > > > > __is_constructible and __is_assignable, and the nothrow forms of those. > > > > > > > > Microsoft (and clang, for compatibility) also provide an alias called > > > > __is_convertible_to. I did not add it, but it would be trivial to do > > > > so. > > > > > > > > I noticed that our __is_assignable doesn't implement the "Access checks > > > > are performed as if from a context unrelated to either type" requirement, > > > > therefore std::is_assignable / __is_assignable give two different results > > > > here: > > > > > > > > class S { > > > > operator int(); > > > > friend void g(); // #1 > > > > }; > > > > > > > > void > > > > g () > > > > { > > > > // #1 doesn't matter > > > > static_assert(std::is_assignable<int&, S>::value, ""); > > > > static_assert(__is_assignable(int&, S), ""); > > > > } > > > > > > > > This is not a problem if __is_assignable is not meant to be used by > > > > the users. > > > > > > That's fine, it's not. > > Okay then. libstdc++ needs to make sure then that it's handled right. > > > > > > This patch doesn't make libstdc++ use the new built-ins, but I had to > > > > rename a class otherwise its name would clash with the new built-in. > > > > > > Sigh, that's going to be a hassle when comparing compiler versions on > > > preprocessed code. > > > > Yeah, I guess :/. Kind of like __integer_pack / __make_integer_seq. > > > > > > --- a/gcc/cp/constraint.cc > > > > +++ b/gcc/cp/constraint.cc > > > > @@ -3697,6 +3697,12 @@ diagnose_trait_expr (tree expr, tree args) > > > > case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS: > > > > inform (loc, " %qT does not have unique object representations", t1); > > > > break; > > > > + case CPTK_IS_CONVERTIBLE: > > > > + inform (loc, " %qT is not convertible from %qE", t2, t1); > > > > + break; > > > > + case CPTK_IS_NOTHROW_CONVERTIBLE: > > > > + inform (loc, " %qT is not %<nothrow%> convertible from %qE", t2, t1); > > > > > > It's odd that the existing diagnostics quote "nothrow", which is not a > > > keyword. I wonder why these library traits didn't use "noexcept"? > > > > Eh, yeah, only "throw" is. The quotes were deliberately added in > > <https://gcc.gnu.org/pipermail/gcc-patches/2019-May/522333.html>. Should > > I prepare a separate patch to use "%<noexcept%>" rather than "%<nothrow%>"? > > OTOH, the traits have "nothrow" in their names, so maybe just go back to > > "nothrow"? > > The latter, I think. Or possibly "no-throw". I guess -Wformat-diag wants > "nothrow" quoted because of the attribute of that name. OK, let me see. > > > > --- a/gcc/cp/method.cc > > > > +++ b/gcc/cp/method.cc > > > > @@ -2236,6 +2236,37 @@ ref_xes_from_temporary (tree to, tree from, bool direct_init_p) > > > > return ref_conv_binds_directly (to, val, direct_init_p).is_false (); > > > > } > > > > +/* Return true if FROM can be converted to TO using implicit conversions, > > > > + or both FROM and TO are possibly cv-qualified void. NB: This doesn't > > > > + implement the "Access checks are performed as if from a context unrelated > > > > + to either type" restriction. */ > > > > + > > > > +bool > > > > +is_convertible (tree from, tree to) > > > > > > You didn't want to add conversion to is*_xible? > > > > No, it didn't look like a good fit. It does things we don't need, and > > also has if VOID_TYPE_P -> return error_mark_node; which would be wrong > > for __is_convertible. > > > > I realized I'm not testing passing an incomplete type to the built-in, > > but since that is UB, I reckon we don't need to test it (we issue > > "error: invalid use of incomplete type"). > > But your patch does test that, in the existing call to check_trait_type from > finish_trait_expr? Yes, it eventually checks complete_type_or_else. > The patch is OK. Thanks, Marek ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] c++: Implement __is_{nothrow_,}convertible [PR106784] 2022-09-22 22:14 ` Jason Merrill 2022-09-23 14:34 ` Marek Polacek @ 2022-09-23 14:40 ` Jonathan Wakely 2022-09-23 15:04 ` Marek Polacek 1 sibling, 1 reply; 10+ messages in thread From: Jonathan Wakely @ 2022-09-23 14:40 UTC (permalink / raw) To: Jason Merrill; +Cc: Marek Polacek, GCC Patches, libstdc++ On Thu, 22 Sept 2022 at 23:14, Jason Merrill wrote: > On 9/22/22 09:39, Marek Polacek wrote: > > This patch doesn't make libstdc++ use the new built-ins, but I had to > > rename a class otherwise its name would clash with the new built-in. > > Sigh, that's going to be a hassle when comparing compiler versions on > preprocessed code. Good point. Clang has some gross hacks that we could consider. When it sees a declaration that uses a built-in name, it disables the built-in for the remainder of the translation unit. It does this precisely to allow a new Clang to compile old std::lib headers where a built-in like __is_assignable was used as a normal class template, not the built-in (because no such built-in existed at the time the library code was written). For us, this is only really a problem when bisecting bugs and using a newer compiler to compile .ii files from odler headers, but for Clang combining a new Clang with older libstdc++ headers is a hard requirement (recall that when Clang was first deployed to macOS it had to consume the system's libstdc++ 4.2 headers). It's a big kluge, but it would mean that a new GCC could happily consume preprocessed code from older libstdc++ headers. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] c++: Implement __is_{nothrow_,}convertible [PR106784] 2022-09-23 14:40 ` Jonathan Wakely @ 2022-09-23 15:04 ` Marek Polacek 0 siblings, 0 replies; 10+ messages in thread From: Marek Polacek @ 2022-09-23 15:04 UTC (permalink / raw) To: Jonathan Wakely; +Cc: Jason Merrill, GCC Patches, libstdc++ On Fri, Sep 23, 2022 at 03:40:23PM +0100, Jonathan Wakely wrote: > On Thu, 22 Sept 2022 at 23:14, Jason Merrill wrote: > > On 9/22/22 09:39, Marek Polacek wrote: > > > This patch doesn't make libstdc++ use the new built-ins, but I had to > > > rename a class otherwise its name would clash with the new built-in. > > > > Sigh, that's going to be a hassle when comparing compiler versions on > > preprocessed code. > > Good point. Clang has some gross hacks that we could consider. When it > sees a declaration that uses a built-in name, it disables the built-in > for the remainder of the translation unit. It does this precisely to > allow a new Clang to compile old std::lib headers where a built-in > like __is_assignable was used as a normal class template, not the > built-in (because no such built-in existed at the time the library > code was written). For us, this is only really a problem when > bisecting bugs and using a newer compiler to compile .ii files from > odler headers, but for Clang combining a new Clang with older > libstdc++ headers is a hard requirement (recall that when Clang was > first deployed to macOS it had to consume the system's libstdc++ 4.2 > headers). > > It's a big kluge, but it would mean that a new GCC could happily > consume preprocessed code from older libstdc++ headers. Ah, you're right, it must be this lib/Parse/ParseDeclCXX.cpp code: // GNU libstdc++ 4.2 and libc++ use certain intrinsic names as the // name of struct templates, but some are keywords in GCC >= 4.3 // and Clang. Therefore, when we see the token sequence "struct // X", make X into a normal identifier rather than a keyword, to // allow libstdc++ 4.2 and libc++ to work properly. TryKeywordIdentFallback(true); Whether we want to do this, I'm not sure. Marek ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2022-09-23 16:37 UTC | newest] Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2022-09-22 13:39 [PATCH] c++: Implement __is_{nothrow_,}convertible [PR106784] Marek Polacek 2022-09-22 22:14 ` Jason Merrill 2022-09-23 14:34 ` Marek Polacek 2022-09-23 14:43 ` Jonathan Wakely 2022-09-23 16:34 ` Jonathan Wakely 2022-09-23 16:37 ` Marek Polacek 2022-09-23 15:54 ` Jason Merrill 2022-09-23 16:16 ` Marek Polacek 2022-09-23 14:40 ` Jonathan Wakely 2022-09-23 15:04 ` Marek Polacek
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).