public inbox for libstdc++@gcc.gnu.org
 help / color / mirror / Atom feed
From: Marek Polacek <polacek@redhat.com>
To: Jason Merrill <jason@redhat.com>
Cc: GCC Patches <gcc-patches@gcc.gnu.org>,
	libstdc++ <libstdc++@gcc.gnu.org>,
	Jonathan Wakely <jwakely@redhat.com>
Subject: Re: [PATCH] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477]
Date: Thu, 14 Jul 2022 13:43:49 -0400	[thread overview]
Message-ID: <YtBV1TwNVSgJIddf@redhat.com> (raw)
In-Reply-To: <16983f2a-f56a-1462-c2db-3e4bc6fa6ad7@redhat.com>

On Tue, Jul 12, 2022 at 04:15:00PM -0400, Jason Merrill wrote:
> On 7/12/22 16:10, Jason Merrill wrote:
> > On 7/8/22 13:41, Marek Polacek wrote:
> > > This patch implements C++23 P2255R2, which adds two new type traits to
> > > detect reference binding to a temporary.  They can be used to detect code
> > > like
> > > 
> > >    std::tuple<const std::string&> t("meow");
> > > 
> > > which is incorrect because it always creates a dangling reference,
> > > because
> > > the std::string temporary is created inside the selected constructor of
> > > std::tuple, and not outside it.
> > > 
> > > There are two new compiler builtins,
> > > __reference_constructs_from_temporary
> > > and __reference_converts_from_temporary.  The former is used to simulate
> > > direct- and the latter copy-initialization context.  But I had a
> > > hard time
> > > finding a test where there's actually a difference.  Under DR 2267, both
> > > of these are invalid:
> > > 
> > >    struct A { } a;
> > >    struct B { explicit B(const A&); };
> > >    const B &b1{a};
> > >    const B &b2(a);
> > > 
> > > so I had to peruse [over.match.ref], and eventually realized that the
> > > difference can be seen here:
> > > 
> > >    struct G {
> > >      operator int(); // #1
> > >      explicit operator int&&(); // #2
> > >    };
> > > 
> > > int&& r1(G{}); // use #2 (no temporary)
> > > int&& r2 = G{}; // use #1 (a temporary is created to be bound to int&&)
> > > 
> > > The implementation itself was rather straightforward because we already
> > > have conv_binds_ref_to_prvalue.  The main function here is
> > > reference_from_temporary.  The renaming to ref_conv_binds_to_temporary_p
> > > is because previously the function didn't distinguish between an invalid
> > > conversion and one that binds to a prvalue.
> > > 
> > > The patch also adds the relevant class and variable templates to
> > > <type_traits>.
> > > 
> > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > > 
> > >     PR c++/104477
> > > 
> > > gcc/c-family/ChangeLog:
> > > 
> > >     * c-common.cc (c_common_reswords): Add
> > >     __reference_constructs_from_temporary and
> > >     __reference_converts_from_temporary.
> > >     * c-common.h (enum rid): Add RID_REF_CONSTRUCTS_FROM_TEMPORARY and
> > >     RID_REF_CONVERTS_FROM_TEMPORARY.
> > > 
> > > gcc/cp/ChangeLog:
> > > 
> > >     * call.cc (ref_conv_binds_directly_p): Rename to ...
> > >     (ref_conv_binds_to_temporary_p): ... this.  Add a new bool
> > >     parameter.  Return true only if the conversion is valid and
> > >     conv_binds_ref_to_prvalue returns true.
> > >     * constraint.cc (diagnose_trait_expr): Handle
> > >     CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > >     * cp-tree.h (enum cp_trait_kind): Add
> > > CPTK_REF_CONSTRUCTS_FROM_TEMPORARY
> > >     and CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > >     (ref_conv_binds_directly_p): Rename to ...
> > >     (ref_conv_binds_to_temporary_p): ... this.
> > >     (reference_from_temporary): Declare.
> > >     * cxx-pretty-print.cc (pp_cxx_trait_expression): Handle
> > >     CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > >     * method.cc (reference_from_temporary): New.
> > >     * parser.cc (cp_parser_primary_expression): Handle
> > >     RID_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > RID_REF_CONVERTS_FROM_TEMPORARY.
> > >     (cp_parser_trait_expr): Likewise.
> > >     (warn_for_range_copy): Adjust to call ref_conv_binds_to_temporary_p.
> > >     * semantics.cc (trait_expr_value): Handle
> > >     CPTK_REF_CONSTRUCTS_FROM_TEMPORARY and
> > > CPTK_REF_CONVERTS_FROM_TEMPORARY.
> > >     (finish_trait_expr): Likewise.
> > > 
> > > libstdc++-v3/ChangeLog:
> > > 
> > >     * include/std/type_traits (reference_constructs_from_temporary,
> > >     reference_converts_from_temporary): New class templates.
> > >     (reference_constructs_from_temporary_v,
> > >     reference_converts_from_temporary_v): New variable templates.
> > >     (__cpp_lib_reference_from_temporary): Define for C++23.
> > >     * include/std/version (__cpp_lib_reference_from_temporary):
> > > Define for
> > >     C++23.
> > >     * testsuite/20_util/variable_templates_for_traits.cc: Test
> > >     reference_constructs_from_temporary_v and
> > >     reference_converts_from_temporary_v.
> > >     * testsuite/20_util/reference_from_temporary/value.cc: New test.
> > >     * testsuite/20_util/reference_from_temporary/value2.cc: New test.
> > >     * testsuite/20_util/reference_from_temporary/version.cc: New test.
> > > 
> > > gcc/testsuite/ChangeLog:
> > > 
> > >     * g++.dg/ext/reference_constructs_from_temporary1.C: New test.
> > >     * g++.dg/ext/reference_converts_from_temporary1.C: New test.
> > > ---
> > >   gcc/c-family/c-common.cc                      |   4 +
> > >   gcc/c-family/c-common.h                       |   2 +
> > >   gcc/cp/call.cc                                |  14 +-
> > >   gcc/cp/constraint.cc                          |   8 +
> > >   gcc/cp/cp-tree.h                              |   7 +-
> > >   gcc/cp/cxx-pretty-print.cc                    |   6 +
> > >   gcc/cp/method.cc                              |  28 +++
> > >   gcc/cp/parser.cc                              |  14 +-
> > >   gcc/cp/semantics.cc                           |   8 +
> > >   .../reference_constructs_from_temporary1.C    | 214 ++++++++++++++++++
> > >   .../ext/reference_converts_from_temporary1.C  | 214 ++++++++++++++++++
> > >   libstdc++-v3/include/std/type_traits          |  39 ++++
> > >   libstdc++-v3/include/std/version              |   5 +-
> > >   .../20_util/reference_from_temporary/value.cc | 110 +++++++++
> > >   .../reference_from_temporary/value2.cc        |  28 +++
> > >   .../reference_from_temporary/version.cc       |  27 +++
> > >   .../20_util/variable_templates_for_traits.cc  |  14 ++
> > >   17 files changed, 730 insertions(+), 12 deletions(-)
> > >   create mode 100644
> > > gcc/testsuite/g++.dg/ext/reference_constructs_from_temporary1.C
> > >   create mode 100644
> > > gcc/testsuite/g++.dg/ext/reference_converts_from_temporary1.C
> > >   create mode 100644
> > > libstdc++-v3/testsuite/20_util/reference_from_temporary/value.cc
> > >   create mode 100644
> > > libstdc++-v3/testsuite/20_util/reference_from_temporary/value2.cc
> > >   create mode 100644
> > > libstdc++-v3/testsuite/20_util/reference_from_temporary/version.cc
> > > 
> > > diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> > > index 1b8e73f7bc5..655c3aefee6 100644
> > > --- a/gcc/c-family/c-common.cc
> > > +++ b/gcc/c-family/c-common.cc
> > > @@ -537,6 +537,10 @@ 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 },
> > > +  { "__reference_constructs_from_temporary",
> > > RID_REF_CONSTRUCTS_FROM_TEMPORARY,
> > > +                    D_CXXONLY },
> > > +  { "__reference_converts_from_temporary",
> > > RID_REF_CONVERTS_FROM_TEMPORARY,
> > > +                    D_CXXONLY },
> > >     /* C++ transactional memory.  */
> > >     { "synchronized",    RID_SYNCHRONIZED, D_CXX_OBJC | D_TRANSMEM },
> > > diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> > > index c0900848965..f9064393b4e 100644
> > > --- a/gcc/c-family/c-common.h
> > > +++ b/gcc/c-family/c-common.h
> > > @@ -184,6 +184,8 @@ enum rid
> > >     RID_IS_UNION,                RID_UNDERLYING_TYPE,
> > >     RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
> > >     RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
> > > +  RID_REF_CONSTRUCTS_FROM_TEMPORARY,
> > > +  RID_REF_CONVERTS_FROM_TEMPORARY,
> > >     /* C++11 */
> > >     RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR,
> > > RID_STATIC_ASSERT,
> > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> > > index fc98552fda2..1ba209f61f1 100644
> > > --- a/gcc/cp/call.cc
> > > +++ b/gcc/cp/call.cc
> > > @@ -9109,21 +9109,23 @@ conv_binds_ref_to_prvalue (conversion *c)
> > >     return conv_is_prvalue (next_conversion (c));
> > >   }
> > > -/* True iff converting EXPR to a reference type TYPE does not involve
> > > -   creating a temporary.  */
> > > +/* True iff converting EXPR to a reference type TYPE binds the
> > > reference to
> > > +   a temporary.  DIRECT_INIT_P says whether the conversion should
> > > be done
> > > +   in direct- or copy-initialization context.  */
> > >   bool
> > > -ref_conv_binds_directly_p (tree type, tree expr)
> > > +ref_conv_binds_to_temporary_p (tree type, tree expr,
> > > +                   bool direct_init_p /*= false*/)
> > >   {
> > >     gcc_assert (TYPE_REF_P (type));
> > >     /* Get the high-water mark for the CONVERSION_OBSTACK.  */
> > >     void *p = conversion_obstack_alloc (0);
> > > +  const int flags = direct_init_p ? LOOKUP_NORMAL : LOOKUP_IMPLICIT;
> > >     conversion *conv = implicit_conversion (type, TREE_TYPE (expr), expr,
> > > -                      /*c_cast_p=*/false,
> > > -                      LOOKUP_IMPLICIT, tf_none);
> > > -  bool ret = conv && !conv->bad_p && !conv_binds_ref_to_prvalue (conv);
> > > +                      /*c_cast_p=*/false, flags, tf_none);
> > > +  bool ret = conv && !conv->bad_p && conv_binds_ref_to_prvalue (conv);
> > >     /* Free all the conversions we allocated.  */
> > >     obstack_free (&conversion_obstack, p);
> > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > > index 591155cee22..648cc9d176d 100644
> > > --- a/gcc/cp/constraint.cc
> > > +++ b/gcc/cp/constraint.cc
> > > @@ -3687,6 +3687,14 @@ 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_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > +      inform (loc, "  %qT is not a reference that binds to a temporary "
> > > +          "object of type %qT (direct-initialization)", t1, t2);
> > > +      break;
> > > +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> > > +      inform (loc, "  %qT is not a reference that binds to a temporary "
> > > +          "object of type %qT (copy-initialization)", t1, t2);
> > > +      break;
> > >       case CPTK_BASES:
> > >       case CPTK_DIRECT_BASES:
> > >       case CPTK_UNDERLYING_TYPE:
> > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > > index 2fde4f83b41..c3bed31e455 100644
> > > --- a/gcc/cp/cp-tree.h
> > > +++ b/gcc/cp/cp-tree.h
> > > @@ -1397,7 +1397,9 @@ enum cp_trait_kind
> > >     CPTK_IS_ASSIGNABLE,
> > >     CPTK_IS_CONSTRUCTIBLE,
> > >     CPTK_IS_NOTHROW_ASSIGNABLE,
> > > -  CPTK_IS_NOTHROW_CONSTRUCTIBLE
> > > +  CPTK_IS_NOTHROW_CONSTRUCTIBLE,
> > > +  CPTK_REF_CONSTRUCTS_FROM_TEMPORARY,
> > > +  CPTK_REF_CONVERTS_FROM_TEMPORARY
> > >   };
> > >   /* The types that we are processing.  */
> > > @@ -6520,7 +6522,7 @@ extern bool sufficient_parms_p
> > > (const_tree);
> > >   extern tree type_decays_to            (tree);
> > >   extern tree extract_call_expr            (tree);
> > >   extern tree build_trivial_dtor_call        (tree, bool = false);
> > > -extern bool ref_conv_binds_directly_p        (tree, tree);
> > > +extern bool ref_conv_binds_to_temporary_p    (tree, tree, bool = false);
> > >   extern tree build_user_type_conversion        (tree, tree, int,
> > >                            tsubst_flags_t);
> > >   extern tree build_new_function_call        (tree, vec<tree, va_gc> **,
> > > @@ -7105,6 +7107,7 @@ 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 reference_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);
> > >   extern void explain_implicit_non_constexpr    (tree);
> > > diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
> > > index 7e4db2e413b..44590830a61 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_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > +      pp_cxx_ws_string (pp, "__reference_constructs_from_temporary");
> > > +      break;
> > > +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> > > +      pp_cxx_ws_string (pp, "__reference_converts_from_temporary");
> > > +      break;
> > >       default:
> > >         gcc_unreachable ();
> > > diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
> > > index 0dffd648b0b..dd9715b6725 100644
> > > --- a/gcc/cp/method.cc
> > > +++ b/gcc/cp/method.cc
> > > @@ -2211,6 +2211,34 @@ is_xible (enum tree_code code, tree to, tree from)
> > >     return !!expr;
> > >   }
> > > +/* Return true iff conjunction_v<is_reference<T>,
> > > is_constructible<T, U>> is
> > > +   true, and the initialization
> > > +     T t(VAL<U>); // DIRECT_INIT_P
> > > +   or
> > > +     T t = VAL<U>; // !DIRECT_INIT_P
> > > +   binds t to a temporary object whose lifetime is extended.
> > > +   VAL<T> is defined in [meta.unary.prop]:
> > > +   -- If T is a reference or function type, VAL<T> is an expression
> > > with the
> > > +   same type and value category as declval<T>().
> > > +   -- Otherwise, VAL<T> is a prvalue that initially has type T.   */
> > > +
> > > +bool
> > > +reference_from_temporary (tree to, tree from, bool direct_init_p)
> > > +{
> > > +  /* Check is_reference<T>.  */
> > > +  if (!TYPE_REF_P (to))
> > > +    return false;
> > > +  /* Check is_constructible<T, U>.
> > > +     ??? This check doesn't seem to be necessary; if T isn't
> > > constructible
> > > +     from U, we won't be able to create a conversion.  */
> > > +  if (!is_xible (INIT_EXPR, to, build_tree_list (NULL_TREE, from)))
> > > +    return false;
> > 
> > I agree with the comment, did you try leaving this out?  If it stays I'd
> > think it needs to consider direct_init_p.
> > 
> > > +  tree val = build_stub_object (from);
> > > +  if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
> > > +    val = CLASS_TYPE_P (from) ? force_rvalue (val, tf_none) :
> > > rvalue (val);
> > > +  return ref_conv_binds_to_temporary_p (to, val, direct_init_p);
> > > +}
> > > +
> > >   /* 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 bf9ea3685f8..edee94bda13 100644
> > > --- a/gcc/cp/parser.cc
> > > +++ b/gcc/cp/parser.cc
> > > @@ -5917,6 +5917,8 @@ cp_parser_primary_expression (cp_parser *parser,
> > >       case RID_IS_CONSTRUCTIBLE:
> > >       case RID_IS_NOTHROW_ASSIGNABLE:
> > >       case RID_IS_NOTHROW_CONSTRUCTIBLE:
> > > +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > +    case RID_REF_CONVERTS_FROM_TEMPORARY:
> > >         return cp_parser_trait_expr (parser, token->keyword);
> > >       // C++ concepts
> > > @@ -10988,6 +10990,14 @@ cp_parser_trait_expr (cp_parser* parser,
> > > enum rid keyword)
> > >         kind = CPTK_IS_NOTHROW_CONSTRUCTIBLE;
> > >         variadic = true;
> > >         break;
> > > +    case RID_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > +      kind = CPTK_REF_CONSTRUCTS_FROM_TEMPORARY;
> > > +      binary = true;
> > > +      break;
> > > +    case RID_REF_CONVERTS_FROM_TEMPORARY:
> > > +      kind = CPTK_REF_CONVERTS_FROM_TEMPORARY;
> > > +      binary = true;
> > > +      break;
> > >       default:
> > >         gcc_unreachable ();
> > >       }
> > > @@ -13811,7 +13821,7 @@ warn_for_range_copy (tree decl, tree expr)
> > >     if (TYPE_REF_P (type))
> > >       {
> > > -      if (glvalue_p (expr) && !ref_conv_binds_directly_p (type, expr))
> > > +      if (glvalue_p (expr) && ref_conv_binds_to_temporary_p (type,
> > > expr))
> > >       {
> > >         auto_diagnostic_group d;
> > >         if (warning_at (loc, OPT_Wrange_loop_construct,
> > > @@ -13842,7 +13852,7 @@ warn_for_range_copy (tree decl, tree expr)
> > >     tree rtype = cp_build_reference_type (type, /*rval*/false);
> > >     /* If we could initialize the reference directly, it wouldn't
> > > involve any
> > >        copies.  */
> > > -  if (!ref_conv_binds_directly_p (rtype, expr))
> > > +  if (ref_conv_binds_to_temporary_p (rtype, expr))
> > >       return;
> > 
> > I think this case wants the old handling of invalid conversions you
> > mentioned in your intro; we don't want to suggest changing to a
> > reference if that's ill-formed.
> > 
> > In passing we might change the comment to "If we can initialize a
> > reference directly, suggest that to avoid the copy." and move it above
> > the rtype declaration.
> 
> Hmm, and I suspect we get false positives when expr is a prvalue, so
> initializing a non-reference variable of the same cv-unqualified type
> involves no extra copy?

I couldn't provoke a false positive here.  Note that ref_conv_binds_to_temporary_p
always gets a reference so copy elision isn't applicable here.

Marek


  reply	other threads:[~2022-07-14 17:43 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-07-08 17:41 [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477] Marek Polacek
2022-07-11 10:19 ` Jonathan Wakely
2022-07-12 17:16   ` [PATCH v2] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477] Marek Polacek
2022-07-12 20:10 ` [PATCH] " Jason Merrill
2022-07-12 20:15   ` Jason Merrill
2022-07-14 17:43     ` Marek Polacek [this message]
2022-07-15  3:48       ` Jason Merrill
2022-07-15 15:32         ` Marek Polacek
2022-07-15 19:59           ` [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477] Ville Voutilainen
2022-07-15 20:25             ` [PATCH] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477] Marek Polacek
2022-07-17 21:12               ` Stephan Bergmann
2022-07-19 10:08                 ` [PATCH] c++: Add __reference_con{struc, ver}ts_from_temporary [PR104477] Jonathan Wakely
2022-07-19 11:10                   ` Jonathan Wakely
2022-07-19 14:14                     ` Jonathan Wakely
2022-07-14 17:41   ` [PATCH v3] c++: Add __reference_con{struc,ver}ts_from_temporary [PR104477] Marek Polacek

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=YtBV1TwNVSgJIddf@redhat.com \
    --to=polacek@redhat.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=jason@redhat.com \
    --cc=jwakely@redhat.com \
    --cc=libstdc++@gcc.gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).