deleted constructor and overload resolution ambiguity

221 views
Skip to first unread message

Jonathan Müller

unread,
Jan 21, 2017, 5:23:46 AM1/21/17
to std-pr...@isocpp.org
Consider the following code:

struct foo
{
foo() = default;
foo(int) {}
};

struct bar {};


foo operator+(foo, foo);
bar operator+(bar, int);

When writing an expression such as `foo{} + 3`, the first overload is
picked and `3` converted to `foo`.

But when we add the following constructor to bar:

bar(foo) = delete;

The overloads are ambiguous, as it can also convert `foo` to `bar` and
use the second overload, none is a better match.
This happens even though the constructor is deleted, it still
participates in overload resolution - by design.

However, I often want to use a deleted constructor to improve the error
message if bar has a lot of constructors with SFINAE. A "use of deleted
constructor" message is often a lot clearer than "no matching
constructor, this one doesn't work because ...., that one doesn't work
because ...". This is especially true if a delete with message is
supported one day.

But because of situations like the one above, I can't really do that as
it will provide a conversion considered by overload resolution.
Now we can make the deleted constructor explicit, so that it will not
participate for implicit conversion resolution,
but then we loose the "use of deleted constructor" message when someone
attempts an ill-formed implicit conversion.

My question: Is someone aware of the issue? If so, is there a proposed
resolution (either a language fix or a user workaround)?

My suggestion would be to rank a conversion using a deleted constructor
worse than everything else to resolve ambiguities, but I'm not an expert
on the implication.

Thanks,
Jonathan

ric...@oehli.at

unread,
Jan 21, 2017, 9:14:27 AM1/21/17
to ISO C++ Standard - Future Proposals
You could could mark the constructor explicit and then no conversion attempt is made.

-Richard

Nicol Bolas

unread,
Jan 21, 2017, 11:22:43 AM1/21/17
to ISO C++ Standard - Future Proposals

We didn't add `= delete` syntax to make error messages more reasonable (well OK, we did, but not in the way you mean). Part of the motivation of the feature was for cases where you want to prevent implicit conversion of some parameters from some values, so you delete the alternatives you don't want the user to invoke:

void f(float f);
void f(int) = delete; //Cannot call `f` with integers.

f
(10); //Compile error.
f
(20.0f); //OK.

The feature is working as intended. If `= delete` changed how overload resolution worked, then it wouldn't be doing its job. You are mis-applying the tool to a use case for which it was not intended.

What you want would require a new feature.

Jonathan Müller

unread,
Jan 21, 2017, 12:42:46 PM1/21/17
to std-pr...@isocpp.org
On 21.01.2017 17:22, Nicol Bolas wrote:
>
> We didn't add `= delete` syntax to make error messages more reasonable
> (well OK, we did, but not in the way you mean). Part of the motivation
> of the feature was for cases where you want to /prevent/ implicit
> conversion of some parameters from some values, so you delete the
> alternatives you don't want the user to invoke:

I'm aware of that.

> The feature is working as intended. If `= delete` changed how overload
> resolution worked, then it wouldn't be doing its job. You are
> mis-applying the tool to a use case for which it was not intended.

Let me rephrase the original code:

struct foo
{
foo() = default;
foo(int) {}

operator std::string(); // implicitly convertible to std::string,
because why not
};

struct bar
{
bar(std::string) {}
bar(foo) = delete; // I only want really strings, not foo's
// delete's intended use case
};

foo operator+(foo, foo);
bar operator+(bar, int);

foo{} + 3; // error, ambiguous, thanks to delete

Yes, the example is silly, but you'll get the point.


> What you want would require a new feature.

I don't want a new feature. I don't event want to change how `= delete`
participates in overload resolution.
I just want to change how `deleted` constructor conversions are ranked
when doing overload resolution.

Because - as far as I can tell - deleted constructors are broken.

Nicol Bolas

unread,
Jan 21, 2017, 1:13:19 PM1/21/17
to ISO C++ Standard - Future Proposals
On Saturday, January 21, 2017 at 12:42:46 PM UTC-5, Jonathan Müller wrote:
On 21.01.2017 17:22, Nicol Bolas wrote:
>
> We didn't add `= delete` syntax to make error messages more reasonable
> (well OK, we did, but not in the way you mean). Part of the motivation
> of the feature was for cases where you want to /prevent/ implicit
> conversion of some parameters from some values, so you delete the
> alternatives you don't want the user to invoke:

I'm aware of that.

> The feature is working as intended. If `= delete` changed how overload
> resolution worked, then it wouldn't be doing its job. You are
> mis-applying the tool to a use case for which it was not intended.

Let me rephrase the original code:

struct foo
{
     foo() = default;
     foo(int) {}

     operator std::string(); // implicitly convertible to std::string,
because why not
};

struct bar
{
     bar(std::string) {}
     bar(foo) = delete; // I only want really strings, not foo's
     // delete's intended use case
};

What you have written is a type that will fail to compile if a user tries to initialize it with a `foo`. What you think you have written is a type that avoids being initialized with a `foo`. That's not the same thing. The goal of `= delete` is to make your compilation fail, not to make it succeed.

`= delete` is about forcing you to be unambiguous; it is not about resolving ambiguity.

foo operator+(foo, foo);
bar operator+(bar, int);

foo{} + 3; // error, ambiguous, thanks to delete

Yes, the example is silly, but you'll get the point.

> What you want would require a new feature.

I don't want a new feature. I don't event want to change how `= delete`
participates in overload resolution.
I just want to change how `deleted` constructor conversions are ranked
when doing overload resolution.

Changing what happens "when doing overload resolution" is a part of changing how something "participates in overload resolution". So yes, that is very much what you're wanting, and it is very much a new feature.

And there's no reason why such a feature ought to be tied to `= delete`. Consider the following:

struct bar
{
     bar
(int);
     __low_ranked bar
(foo);
};

This still permits `bar` to be implicitly converted from `foo`. But if there would be an ambiguous conversion sequence, then take the one that isn't marked "__low_ranked".

Because - as far as I can tell - deleted constructors are broken.

They aren't broken; they're just not doing what you want. And it is not their purpose to be used for what you're trying to use them for.

Jonathan Müller

unread,
Jan 21, 2017, 1:24:14 PM1/21/17
to std-pr...@isocpp.org
On 21.01.2017 19:13, Nicol Bolas wrote:
>
> Changing what happens "when doing overload resolution" is a part of
> changing how something "participates in overload resolution". So yes,
> that is very much what you're wanting, and it is very much a new feature.
>
> And there's no reason why such a feature ought to be tied to `= delete`.
> Consider the following:
>
> |
> structbar
> {
> bar(int);
> __low_ranked bar(foo);
> };
> |
>
> This still permits `bar` to be implicitly converted from `foo`. But if
> there would be an ambiguous conversion sequence, then take the one that
> isn't marked "__low_ranked".

Alright, then I'm requesting a new feature.

> Because - as far as I can tell - deleted constructors are broken.
>
>
> They aren't broken; they're just not doing what you want. And it is not
> their purpose to be used for what you're trying to use them for.

I'm trying to write a type that will fail to compile if a user
initializes it with `foo`. That's what delete is for, right?

However, when I do so, this breaks a completely legitimate use case of
`foo` itself because of an ambiguity thanks to the deleted constructor.
I'd call that broken.

Jakob Riedle

unread,
Jan 24, 2017, 2:27:00 PM1/24/17
to ISO C++ Standard - Future Proposals
The overloads are ambiguous, as it can also convert `foo` to `bar` and
use the second overload, none is a better match.
This happens even though the constructor is deleted, it still
participates in overload resolution - by design.

I had this problem by myself. It also occours with private base classes...

See here:

Maybe that helps for the discussion...

Cheers,
Jakob
Reply all
Reply to author
Forward
0 new messages