Why do C++11-deleted functions participate in overload resolution?
Half of the purpose of the = delete
syntax is to be able to prevent people from calling certain functions with certain parameters. This is mainly to prevent implicit conversions in certain specific scenarios. In order to forbid a particular overload, it has to participate in overload resolution.
The answer you cite gives you a perfect example:
struct onlydouble {
onlydouble(std::intmax_t) = delete;
onlydouble(double);
};
If delete
removed the function entirely, that would make the = delete
syntax equivalent to this:
struct onlydouble2 {
onlydouble2(double);
};
You could do this:
onlydouble2 val(20);
This is legal C++. The compiler will look at all constructors; none of them take an integer type directly. But one of them can take it after an implicit conversion. So it'll call that.
onlydouble val(20);
This is not legal C++. The compiler will look at all constructors, including the delete
d ones. It will see an exact match, via std::intmax_t
(which will exactly match any integer literal). So the compiler will select it and then immediately issue an error, because it selected a delete
d function.
= delete
means "I forbid this," not merely, "This does not exist." It's a much stronger statement.
I was asking why the C++ standard says = delete means "I forbid this" instead of "this does not exist"
It's because we don't need special grammar to say "this does not exist." We get this implicitly by simply not declaring the particular "this" in question. "I forbid this" represents a construct that cannot be achieved without special grammar. So we get special grammar to say "I forbid this" and not the other thing.
The only functionality you would gain by having an explicit "this does not exist" grammar would be to prevent someone from later declaring it to exist. And that's just not useful enough to need its own grammar.
there is otherwise no way to declare that the copy constructor does not exist, and its existence can cause nonsensical ambiguities.
The copy constructor is a special member function. Every class always has a copy constructor. Just as they always have a copy assignment operator, move constructor, etc.
These functions exist; the question is only whether it is legal to call them. If you tried to say that = delete
meant that they didn't exist, then the specification would have to explain what it means for a function to not exist. This is not a concept that the specification handles.
If you attempt to call a function that hasn't been declared/defined yet, then the compiler will error. But it will error because of an undefined identifier, not because of a "function doesn't exist" error (even if your compiler reports it that way). Various constructors are all called by overload resolution, so their "existence" is handled in that regard.
In every case, there is either a function declared via identifier, or a constructor/destructor (also declared via identifier, just a type-identifier). Operator overloading hides the identifier behind syntactic sugar, but it's still there.
The C++ specification cannot handle the concept of a "function that does not exist." It can handle an overload mismatch. It can handle an overload ambiguity. But it doesn't know about what isn't there. So = delete
is defined in terms of the far more useful "attempts to call this fail" rather than the less useful "pretend I never wrote this line."
And again, re-read the first part. You cannot do that with "function doesn't exist." That's another reason why it's defined that way: because one of the main use cases of the = delete
syntax is to be able to force the user to use certain parameter types, to explicitly cast, and so forth. Basically, to foil implicit type conversions.
Your suggestion would not do that.
Deleting overloaded function. C++11. Call of overloaded ... is ambiguous
When you call
func(2.3)
you pass a double
to the function. The list of candidates contains both func(int)
and func(char)
, as overload resolution happens before delete
kicks in:
If the function is overloaded, overload resolution takes place first, and the program is only ill-formed if the deleted function was selected Ref: cppreference.com, see Avishai's answer for precise standard quotes.
Now double
can be converted both to char
and int
, hence the ambiguity.
Without deleting function with char arguments double was converted to int and func(int) was called, why now it is forbidden?
You get an ambiguity error even without delete
ing the char
version, see live here. Of course if you only define the func(int)
, then there will be no ambiguity so double
will happily be converted to an int
.
Why doesn't std::function participate in overload resolution?
This doesn't really have anything to do with "phases of translation". It's purely about the constructors of std::function
.
See, std::function<R(Args)>
doesn't require that the given function is exactly of the type R(Args)
. In particular, it doesn't require that it is given a function pointer. It can take any callable type (member function pointer, some object that has an overload of operator()
) so long as it is invokable as if it took Args
parameters and returns something convertible to R
(or if R
is void
, it can return anything).
To do that, the appropriate constructor of std::function
must be a template: template<typename F> function(F f);
. That is, it can take any function type (subject to the above restrictions).
The expression baz
represents an overload set. If you use that expression to call the overload set, that's fine. If you use that expression as a parameter to a function that takes a specific function pointer, C++ can whittle down the overload set to a single call, thus making it fine.
However, once a function is a template, and you're using template argument deduction to figure out what that parameter is, C++ no longer has the ability to determine what the correct overload in the overload set is. So you must specify it directly.
Why does overload resolution not fall back to constructor with reference to base class when copy-constructor is deleted?
Because = delete
is a still a definition of the constructor (it is defined as deleted). Deleted functions are still possible candidates in overload resolution, and, in this case, the copy constructor is still the most viable candidate, and it is selected. Since a deleted function is referred to, the program is ill-formed.
From the standard, [dcl.fct.def.delete]:
- A deleted definition of a function is a function definition whose function-body is of the form
= delete ;
[...] A deleted function is a function with a deleted definition or a function that is implicitly defined as deleted.- A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.
And it is pretty easy to see why a definition for B::B(B&)
is more viable than B::B(Base&)
for an lvalue of type B
Function doesn't participate in overload resolution in inheritance
Overloading won't take place through different scopes.
For fun(42.42f);
, the name fun
has to be found. According to the rule of unqualified name lookup:
For an unqualified name, that is a name that does not appear to the right of a scope resolution operator ::, name lookup examines the scopes as described below, until it finds at least one declaration of any kind, at which time the lookup stops and no further scopes are examined.
That means the name will be found at the scope of class Derived
, i.e. Derived::fun()
. Then the name lookup stops, the names in the scope of Base
won't be considered at all.
If you remove the definition of Derived::fun
, there would be no names found at the scope of Derived
, then the scope of the base class (i.e. Base
) will be examined further and Base::foo
will be found. Then everything works fine.
It's also worth noting that overload resolution happens after name lookup.
With std::optional, what does it mean to remove the move constructor from overload resolution?
Yes, SFINAE does not work for constructors, use base classes forcing the compiler to do the right thing.
It means it is not defined and the class cannot be move constructed. More interesting question is why is it needed?
I am not 100% sure I have the right answer to that.
TL;DR Returning std::optional<NonMoveable>
generates compiler errors if the move constructor of optional
is present. On the other hand, returning NonMoveable
directly fallbacks to copy constructor.
First, the constraint does not break anything. The constructor cannot be implemented if T
cannot be move constructed.
Second, all methods of std::optional
are very tricky due to std::optional<std::optional<T>>
issue which could easily lead to ambiguous calls if proper constraints are not taken, optional(U&& value)
is really susceptible to this.
The main reason is, I believe, because we want optional<T>
to act as T
whenever possible and there is one edge case, that I am aware of, when the existence of std::optional
's move constructor for non-moveable T
leads to unnecessary compiler errors. Coincidentally, it is the case of returning std::optional
by value from functions, something I do very often.
For a variable x
of type T
, return x
in a function T foo()
calls move constructor if it accessible, copy if not.
Take these simple definitions:
#include <utility>
struct CopyOnly {
CopyOnly() = default;
CopyOnly(const CopyOnly &) = default;
CopyOnly(CopyOnly &&) = delete;
CopyOnly &operator=(const CopyOnly &) = default;
CopyOnly &operator=(CopyOnly &&) = delete;
~CopyOnly() = default;
};
template <typename T> struct Opt {
Opt() = default;
Opt(const Opt &other) : m_value(other.m_value) {}
Opt(Opt &&other) : m_value(std::move(other.m_value)) {
// Ordinary move ctor.
// Same as =default, just writing for clarity.
}
// Ignore how `T` is actually stored to be "optional".
T m_value;
};
and this example
template <typename T> T foo(const T &t) {
auto x = t;
return x;
}
int main() {
Opt<int> opt_int;
CopyOnly copy;
Opt<CopyOnly> opt_copy;
foo(opt_int);//#1
foo(copy);//#2
foo(opt_copy);//#3
}
return x
:
- Calls move constructor because
opt_int
can be moved. - Calls copy constructor as a fallback because it cannot be moved.
- Compiler error because
Opt<CopyOnly>
has accessible move constructor so it is chosen, but its instantiation leads to an error duem_value(std::move(other.m_value))
trying to explicitly calls deleted move ctor.
If one disables the move constructor, copy constructor is chosen and the code is identical to #2.
Related Topics
When Should You Use Constexpr Capability in C++11
Examples of When a Bitwise Swap() Is a Bad Idea
Std::Cin.Getline( ) Vs. Std::Cin
What Is the Meaning of the Term "Free Function" in C++
How Is Std::Function Implemented
What How to Use Instead of the Arrow Operator, '-≫'
Initializer_List and Template Type Deduction
Is 0 a Decimal Literal or an Octal Literal
How to Find If a Given Key Exists in a C++ Std::Map
Type of Integer Literals Not Int by Default
Dynamic_Cast and Static_Cast in C++
Double Precision - Decimal Places
What Do 1.#Inf00, -1.#Ind00 and -1.#Ind Mean
How to Read a File Line by Line or a Whole Text File At Once