c++language-lawyer

Constructor is ambiguous however there is only one candidate


In the following code there are two constructors - direct constructor from int v and deleted const int& constructor:

struct X
{
    X(int v);
    X(const int&)=delete;
};

X foo()
{
    return X(42);
}

this fails to compile because compilers (gcc,clang,msvc) consider the call to constructor ambiguous:

<source>:9:12: error: ambiguous conversion for functional-style cast from 'int' to 'X'
    return X(42);
           ^~~~
<source>:3:5: note: candidate constructor
    X(int v);
    ^
<source>:4:5: note: candidate constructor has been explicitly deleted
    X(const int&)=delete;
    ^

Here clang lists all the possible candidates it considered. gcc's list is different but even with all but one constructors deleted they all consider such a call ambiguous.

clang: https://godbolt.org/z/KnWd4WP9j

There are two candidates and one of them is explicitly deleted. In my opinion there is no ambiguity.

Without deleted constructor there is no ambiguity https://godbolt.org/z/5E44E5chn but I think it's because autogenerated const int& is used.

Deleting more constructors doesn't help either:

struct X
{
    X(int v);
    X(const int&)=delete;
    X(int&&)=delete;
    X(const X&)=delete;
    X(X&&)=delete;
};

X foo()
{
    return X{int(42)};
}

produces the same error with a longer list of candidates.

I tried with no luck to find similar constructions here on stackoverflow, on cppreference and even in the draft of the standard.

This does not look like a compiler error. All major compilers reject this code - gcc,clang, msvc. And I can see benefits of this. However I can't match this behaviour into the standard.

My question is: what parts of the c++ standard require explicitly deleted constructors to be included into search for the suitable constructor?


Solution

  • From [dcl.fct.def.delete]/1:

    A deleted definition of a function is a function definition whose function-body is a deleted-function-body or an explicitly-defaulted definition of the function where the function is defined as deleted. A deleted function is a function with a deleted definition or a function that is implicitly defined as deleted.

    Since the body of a function is not part of overload resolution, a deleted function will be selected as part of overload resolution.

    The mechanism as how deleted function works is described in [over.match]/4:

    Overload resolution results in a usable candidate if overload resolution succeeds and the selected candidate is either not a function ([over.built]), or is a function that is not deleted and is accessible from the context in which overload resolution was performed.

    As you can see, deleted function are definitely part of overload resolution, but overload resolution will only result in a usable candidate if it result in a function whose body is not a deleted function body.


    As any function, overloading for both value and lvalue reference will more often than not result in ambiguous resolution, as both functions are valid candidates:

    void f(int);
    void f(int const&) = delete;
    
    int main() {
        f(1); // error, ambiguous
    }
    

    To make the code works as expected and prevent lvalues from selecting an overload, consider simply using a rvalue reference:

    void f(int&&);
    
    int main() {
        int a = 0;
        f(1); // works
        f(a); // error
    }