* [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-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: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: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
* 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-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
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).