c++: Explicit constructor called in copy-initialization [PR90320]

This test is rejected with a bogus "use of deleted function" error
starting with r225705 whereby convert_like_real/ck_base no longer
sets LOOKUP_ONLYCONVERTING for user_conv_p conversions.  This does
not seem to be always correct.  To recap, when we have something like
T t = x where T is a class type and the type of x is not T or derived
from T, we perform copy-initialization, something like:
  1. choose a user-defined conversion to convert x to T, the result is
     a prvalue,
  2. use this prvalue to direct-initialize t.

In the second step, explicit constructors should be considered, since
we're direct-initializing.  This is what r225705 fixed.

In this PR we are dealing with the first step, I think, where explicit
constructors should be skipped.  [over.match.copy] says "The converting
constructors of T are candidate functions" which clearly eliminates
explicit constructors.  But we also have to copy-initialize the argument
we are passing to such a converting constructor, and here we should
disregard explicit constructors too.

In this testcase we have

  V v = m;

and we choose V::V(M) to convert m to V.  But we wrongly choose
the explicit M::M<M&>(M&) to copy-initialize the argument; it's
a better match for a non-const lvalue than the implicit M::M(const M&)
but because it's explicit, we shouldn't use it.

When convert_like is processing the ck_user conversion -- the convfn is
V::V(M) -- it can see that cand->flags contains LOOKUP_ONLYCONVERTING,
but then when we're in build_over_call for this convfn, we have no way
to pass the flag to convert_like for the argument 'm', because convert_like
doesn't take flags.  Fixed by creating a new conversion flag, copy_init_p,
set in ck_base/ck_rvalue to signal that explicit constructors should be
skipped.

LOOKUP_COPY_PARM looks relevant, but again, it's a LOOKUP_* flag, so
can't pass it to convert_like.  DR 899 also seemed related, but that
deals with direct-init contexts only.

	PR c++/90320
	* call.c (struct conversion): Add copy_init_p.
	(standard_conversion): Set copy_init_p in ck_base and ck_rvalue
	if FLAGS demands LOOKUP_ONLYCONVERTING.
	(convert_like_real) <case ck_base>: If copy_init_p is set, or
	LOOKUP_ONLYCONVERTING into FLAGS.

	* g++.dg/cpp0x/explicit13.C: New test.
	* g++.dg/cpp0x/explicit14.C: New test.
This commit is contained in:
Marek Polacek 2020-04-22 20:12:47 -04:00
parent c808635706
commit feb801f622
5 changed files with 66 additions and 5 deletions

View file

@ -1,3 +1,12 @@
2020-04-22 Marek Polacek <polacek@redhat.com>
PR c++/90320
* call.c (struct conversion): Add copy_init_p.
(standard_conversion): Set copy_init_p in ck_base and ck_rvalue
if FLAGS demands LOOKUP_ONLYCONVERTING.
(convert_like_real) <case ck_base>: If copy_init_p is set, or
LOOKUP_ONLYCONVERTING into FLAGS.
2020-04-26 Iain Sandoe <iain@sandoe.co.uk>
PR c++/94752

View file

@ -92,7 +92,7 @@ struct conversion {
language standards, e.g. disregarding pointer qualifiers or
converting integers to pointers. */
BOOL_BITFIELD bad_p : 1;
/* If KIND is ck_ref_bind ck_base_conv, true to indicate that a
/* If KIND is ck_ref_bind or ck_base, true to indicate that a
temporary should be created to hold the result of the
conversion. If KIND is ck_ambig or ck_user, true means force
copy-initialization. */
@ -111,6 +111,10 @@ struct conversion {
/* Whether check_narrowing should only check TREE_CONSTANTs; used
in build_converted_constant_expr. */
BOOL_BITFIELD check_narrowing_const_only: 1;
/* True if this conversion is taking place in a copy-initialization context
and we should only consider converting constructors. Only set in
ck_base and ck_rvalue. */
BOOL_BITFIELD copy_init_p : 1;
/* The type of the expression resulting from the conversion. */
tree type;
union {
@ -1252,6 +1256,10 @@ standard_conversion (tree to, tree from, tree expr, bool c_cast_p,
if (flags & LOOKUP_PREFER_RVALUE)
/* Tell convert_like_real to set LOOKUP_PREFER_RVALUE. */
conv->rvaluedness_matches_p = true;
/* If we're performing copy-initialization, remember to skip
explicit constructors. */
if (flags & LOOKUP_ONLYCONVERTING)
conv->copy_init_p = true;
}
/* Allow conversion between `__complex__' data types. */
@ -1528,6 +1536,10 @@ standard_conversion (tree to, tree from, tree expr, bool c_cast_p,
if (flags & LOOKUP_PREFER_RVALUE)
/* Tell convert_like_real to set LOOKUP_PREFER_RVALUE. */
conv->rvaluedness_matches_p = true;
/* If we're performing copy-initialization, remember to skip
explicit constructors. */
if (flags & LOOKUP_ONLYCONVERTING)
conv->copy_init_p = true;
}
else
return NULL;
@ -7653,12 +7665,16 @@ convert_like_real (conversion *convs, tree expr, tree fn, int argnum,
type is the same class as, or a derived class of, the class of the
destination [is treated as direct-initialization]. [dcl.init] */
flags = LOOKUP_NORMAL;
/* This conversion is being done in the context of a user-defined
conversion (i.e. the second step of copy-initialization), so
don't allow any more. */
if (convs->user_conv_p)
/* This conversion is being done in the context of a user-defined
conversion (i.e. the second step of copy-initialization), so
don't allow any more. */
flags |= LOOKUP_NO_CONVERSION;
else
/* We might be performing a conversion of the argument
to the user-defined conversion, i.e., not a conversion of the
result of the user-defined conversion. In which case we skip
explicit constructors. */
if (convs->copy_init_p)
flags |= LOOKUP_ONLYCONVERTING;
if (convs->rvaluedness_matches_p)
/* standard_conversion got LOOKUP_PREFER_RVALUE. */

View file

@ -1,3 +1,9 @@
2020-04-22 Marek Polacek <polacek@redhat.com>
PR c++/90320
* g++.dg/cpp0x/explicit13.C: New test.
* g++.dg/cpp0x/explicit14.C: New test.
2020-04-27 Iain Buclaw <ibuclaw@gdcproject.org>
PR d/89418

View file

@ -0,0 +1,14 @@
// PR c++/90320
// { dg-do compile { target c++11 } }
struct M {
M() = default;
template <typename T> explicit M(T&&) = delete;
};
struct V {
V(M m);
};
M m;
V v = m;

View file

@ -0,0 +1,16 @@
// PR c++/90320
// { dg-do compile { target c++11 } }
struct B { };
struct M : B {
M() = default;
template <typename T> explicit M(T&&) = delete;
};
struct V {
V(B);
};
M m;
V v = m;