What is the purpose of `operator auto() = delete` in C++? - c++

A class in C++ can define one or several conversion operators. Some of them can be with auto-deduction of resulting type: operator auto. And all compilers allow the programmer to mark any operator as deleted, and operator auto as well. For concrete type the deletion means that an attempt to call such conversion will result in compilation error. But what could be the purpose of operator auto() = delete?
Consider an example:
struct A {
operator auto() = delete;
};
struct B : A {
operator auto() { return 1; }
};
int main() {
B b;
A a = b; // error in Clang
int i = b; // error in Clang and GCC
int j = a; // error in Clang and GCC and MSVC
}
Since the compiler is unable to deduce the resulting type, it actually prohibits any conversion from this class or from derived classes with the error:
function 'operator auto' with deduced return type cannot be used before it is defined.
Demo: https://gcc.godbolt.org/z/zz77M5zsx
Side note that compilers slightly diverge in what conversions are still allowed (e.g. GCC and MSVC permit the conversion to base class), which one of them is right here?

But what could be the purpose of operator auto() = delete?
What would be the purpose of the following function?
auto f() = delete;
As per the grammar functions, [dcl.fct.def.general]/1, the function-body of a function-definition may be = delete; e.g., it is syntactically valid to define a function as deleted.
C++14 introduced auto return type deduction for functions, and given the allowed grammar for function definitions, as per C++14 the grammar allows explicitly-deleting a function with auto return type.
Whether this corner case is useful or not is not really for the language ponder about, as there is always a cost of introducing corner cases (e.g. "the grammar for definitions of functions shall have a special case for auto type deduction"). Whilst there are limitations on where one may provide explicitly-defaulted function definitions ([dcl.fct.def.default]), the same restrictions do not apply for explicitly-deleted function definitions ([dcl.fct.def.delete]).
which one of them is right here?
A a = b; // error in Clang
Clang is arguable wrong to pick the user-defined conversion function for this initialization. As per [dcl.init.general]/15.6, /15.6.2:
Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. [...]
takes precedence over /15.6.3:
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversions that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in [over.match.copy], and the best one is chosen through overload resolution ([over.match]). [...]
As a user of the language, providing a deleted definition of a function with auto return type could be used to semantically mark that no one should provide any kind of overload of a given function name.
auto f() = delete; // never expose a valid overload for f()
// elsewhere:
int f() { return 42; }
// error: functions that differ only in
// their return type cannot be overloaded
For the special case of user-defined conversion operators, an explicitly-default auto return type user-defined conversion function can be used e.g. in a intended-for-composition base class, similar to Scott Meyers C++03 trick of making a class non-copyable (before C++11 introduced = delete).
struct NoUserDefinedConversionFunctionsAllowed {
operator auto() = delete;
};
struct S : NoUserDefinedConversionFunctionsAllowed {
operator int() { return 1; } // can never be used
};

Related

C++ post-increment: objects vs primitive types

We cannot use pre-increment on rvalues:
int i = 0;
int j = ++i++; // Compile error: lvalue required
If we define a class:
class A
{
public:
A & operator++()
{
return *this;
}
A operator++(int)
{
A temp(*this);
return temp;
}
};
then we can compile:
A i;
A j = ++i++;
What is the different between A object and int data type that
j = ++i++;
compiles with A but not with int?
This happens because when overloaded operators are defined as member functions, they follow some semantics which are more related to calling a member function, not to the behavior of the built-in operator. Note that by default, if we declare a non-static member function like:
class X {
public:
void f();
X g();
};
then we can call it on both lvalue and rvalue class type expressions:
X().f(); // okay, the X object is prvalue
X x;
x.f(); // okay, the X object is lvalue
x.g().f(); // also okay, x.g() is prvalue
When overload resolution for an operator expression selects a member function, the expression is changed to be just a call to that member function, so it follows the same rules:
++A(); // okay, transformed to A().operator++(), called on prvalue
A a;
++a; // okay, transformed to a.operator++(), called on lvalue
++a++; // also technically okay, transformed to a.operator++(0).operator++(),
// a.operator++(0) is a prvalue.
This sort of non-equivalence between built-in operators and overloaded operators also happens with the left subexpression of assignment: the pointless statement std::string() = std::string(); is legal, but the statement int() = int(); is not legal.
But you noted in a comment "I want to design a class that prevents ++a++". There are at least two ways to do that.
First, you could use a non-member operator instead of a member. Most overloaded operators can be implemented as either a member or non-member, where the class type needs to be added as an additional first parameter type of the non-member function. For example, if a has class type, the expression ++a will attempt to find a function as if it were a.operator++() and also a function as if it were operator++(a); and the expression a++ will look for functions for the expressions a.operator++(0) or operator++(a, 0).
(This pattern of trying both ways does not apply to functions named operator=, operator(), operator[], or operator->, because they may only be defined as non-static member functions, never as non-members. Functions named operator new, operator new[], operator delete, or operator delete[], plus user-defined literal functions whose names start like operator "", follow entirely different sets of rules.)
And when the class argument matches a real function parameter, instead of the "implicit object parameter" of a non-static member function, the type of reference used in the parameter, if any, controls as usual whether an argument can be an lvalue, rvalue, or either.
class B {
public:
// Both increment operators are valid only on lvalues.
friend B& operator++(B& b) {
// Some internal increment logic.
return b;
}
friend B operator++(B& b, int) {
B temp(b);
++temp;
return temp;
}
};
void test_B() {
++B(); // Error: Tried operator++(B()), can't pass
// rvalue B() to B& parameter
B b;
++b; // Okay: Transformed to operator++(b), b is lvalue
++b++; // Error: Tried operator++(operator++(b,0)), but
// operator++(b,0) is prvalue and can't pass to B& parameter
}
Another way is to add ref-qualifiers to member functions, which were added to the language in the C++11 version as a specific way of controlling whether a member function's implicit object argument must be an lvalue or rvalue:
class C {
public:
C& operator++() & {
// Some internal increment logic.
return *this;
}
C operator++(int) & {
C temp(*this);
++temp;
return temp;
}
};
Notice the & between the parameter list and the start of the body. This restricts the function to only accept an lvalue of type C (or something that implicitly converts to a C& reference) as the implicit object argument, similarly to how a const in the same spot allows the implicit object argument to have type const C. If you wanted a function to require an lvalue but allow that lvalue to optionally be const, the const comes before the ref-qualifier: void f() const &;
void test_C() {
++C(); // Error: Tried C().operator++(), doesn't allow rvalue C()
// as implicit object parameter
C c;
++c; // Okay: Transformed to c.operator++(), c is lvalue
++c++; // Error: Tried c.operator++(0).operator++(), but
// c.operator++(0) is prvalue, not allowed as implicit object
// parameter of operator++().
}
To get operator= to act more like it does for a scalar type, we can't use a non-member function, because the language only allows member operator= declarations, but the ref-qualifier will similarly work. You're even allowed to use the = default; syntax to have the compiler generate the body, even though the function isn't declared in exactly the same way an implicitly-declared assignment function would have been.
class D {
public:
D() = default;
D(const D&) = default;
D(D&&) = default;
D& operator=(const D&) & = default;
D& operator=(D&&) & = default;
};
void test_D() {
D() = D(); // Error: implicit object argument (left-hand side) must
// be an lvalue
}
It … just is. There are a few constraints that apply only to primitive types and not class types (well, you've found the most obvious one!).
It is largely because operators for built-in types are one thing, whereas for classes they are just member functions in disguise and therefore a completely different beast.
Is this confusing? I don't know; maybe.
Is there a really compelling reason for it? I don't know; possibly not. There's a certain inertia with primitive types: why change something that was in C just because you're introducing classes? What is the benefit of permitting this? On the other hand, would it not be overly strict to ban it for classes, whose implementation of operator++ could do something that, as the language designer, you haven't thought of?

Why is this direct initialization valid? (C++ 17)

Consider the following two classes:
class B
{
public:
B() { }
B(const B& b) = delete; //Move ctor not implicitly declared
};
class A
{
public:
A() { }
operator B()
{
return B();
}
};
I can see why this code compiles fine:
A a;
B b = a;
Following the rules of copy-initialization, the object "a" gets converted to a prvalue of type B and since in C++17 the copy constructor is not needed anymore there's no error:
If T is a class type, and the cv-unqualified version of the type of
other is not T or derived from T, or if T is non-class type, but the
type of other is a class type, user-defined conversion sequences that
can convert from the type of other to T (or to a type derived from T
if T is a class type and a conversion function is available) are
examined and the best one is selected through overload resolution. The
result of the conversion, which is a prvalue temporary (until
C++17)prvalue expression (since C++17) if a converting constructor was
used, is then used to direct-initialize the object. The last step is
usually optimized out and the result of the conversion is constructed
directly in the memory allocated for the target object, but the
appropriate constructor (move or copy) is required to be accessible
even though it's not used. (until C++17)
However why does this direct list-initialization compile too?
A a;
B b{ a };
I couldn't find any wording in the list-initialization stating the compiler should attempt to convert A into B in this case. Only that overload resolution on constructors is considered:
If the previous stage does not produce a match, all constructors of T
participate in overload resolution against the set of arguments that
consists of the elements of the braced-init-list, with the restriction
that only non-narrowing conversions are allowed
However in this case the copy constructor is deleted, so shouldn't it not be selected by overload resolution?
This is CWG 2327. You're correct as far as the standard goes, but some compilers additionally consider conversion functions in this context as well - because it really makes sense to.

What are the rules for template conversion operator type deduction in initializations?

Consider this code:
struct S
{
template <typename T>
operator T() const
{ return {}; }
};
struct R
{
R() = default;
R(const R&) = default;
R(R&&) = default;
R(bool) {}
};
Where on the standard are the rules defined for the following behaviors?
S s;
R r1 = s; // (1) passes: T = R
R r2(s); // (2) ambiguity: T = R or bool?
Why doesn't (1) cause an ambiguity issue (given that R could be initialized by bool as well)? I've recently written an answer for a similar question, but I'm curious to why (1) doesn't behave like (2) in this context, and I don't know where it is described on the standard either.
8.5/15-16:
The initialization that occurs in the = form of a brace-or-equal-initializer or condition, as well as ..., is called copy-initialization.
The initialization that occurs in the forms
T x(a);
T x{a};
as well as ... is called direct-initialization.
So R r1 = s; is copy-initialization, and R r2(s); is direct-initialization. On to paragraph 17:
If the destination type is a (possibly cv-qualified) class type:
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3) and the best one is chosen through overload resolution (13.3).
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3).
So direct-initialization looks at all the constructors of R and ends up being ambiguous, while copy-initialization explicitly tries to convert the expression to R directly and succeeds.

Copy initialization of class type by user defined conversion

I have the following code snippet:
struct T {
T(const T&) = default;
T(const S &);
};
struct S {
operator T();
};
int main() {
S s;
T t = s; // copy-initialization of class type
return 0;
}
My question is why the compiler prefers S::operator T() for the initialization of t rather than reporting an error that the initialization is ambigious. In my opinion the following happens (correct me if i am wrong):
t is copy-initialized with an lvalue of type S
S is not T and S is also not a subclass of T, so S and T are unrelated
because of the fact that the variable t is copy-initialized and the fact that the types S and T are unrelated, the compiler tries to find user-defined-conversion sequences to do the initialization.
overload resolution is responsible for selecting the best user-defined-conversion which can be either a converting constructor of T or the conversion function of S
the implicite conversion sequence for the constructor T::T(const S&) from the argument s is the identity conversion because the lvalue s can be bound directly to this lvalue reference
the implicite conversion sequence for the conversion function S::operator T() from the argument s is also the identity conversion, because the implicit object parameter is S&
Both the constructor and the conversion function return a prvalue of type T which can be used to direct-initialize the variable t. That means that the second standard conversion sequence of both user-defined-conversion sequences is the identity conversion.
This would mean that both user-defined-conversion sequences are equally good. Or is there a special rule which prefers the conversion functions?
I was reading the following rules in the c++11 standard:
The initialization that occurs in the form
T x = a;
as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization.
The semantics of initializers are as follows...If the destination type is a (possibly cv-qualified) class type: If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified
version of the source type is the same class as, or a derived class of, the class of the destination,
constructors are considered....
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3)
User-defined conversion sequence U1 is a better conversion sequence than another user defined conversion sequence U2 if they contain the same user-defined conversion function or constructor and if the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2
Maybe i am making false assumptions. I hope you can help me!
The conversion from S using the conversion operator is better than the conversion to T taking an S const as argument. If you make s an S const, the constructor is preferred: Your identity operation in one case, indeed, is an identity operation, in the other case it isn't. If you make the conversion operator of S a const member, you get an ambiguity. Below is a test program demonstrating all cases:
struct S;
struct T {
T(const T&) = default;
T(const S &);
};
struct S {
S(); // needed to allow creation of a const object
#ifdef AMBIGUOUS
operator T() const;
#else
operator T();
#endif
};
int main() {
#ifdef CONST
S const s;
#else
S s;
#endif
T t = s; // copy-initialization of class type
return 0;
}
I think i found the rule which clarifies this:
13.3.3.2: ... S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.
In the member function S::operator T() the implicit object parameter has type S& which is directly bound to the lvalue s of type S. In the constructor T::T(const S&) the parameter is directly bound to the lvalue s of type S but this reference binding is more cv-qualified than in the operator function, so the operator function is preferred by overload resolution.
Do you agree with this?

Different casting operators used by different compilers

The following C++ program compiles without warnings in all compilers I have tried (gcc 4.6.3, llvm 3.0, icc 13.1.1, SolarisStudio 12.1/12.3):
struct CClass
{
template<class T>
operator T() const { return 1; }
operator int() const { return 2; }
};
int main(void)
{
CClass x;
return static_cast<char>(x);
}
However, all but the SolarisStudio compilers return 2, SolarisStudio (either version) returns 1, which I would consider the most logical result.
Using return x.operator char(); results in all compilers returning 1.
Obviously, since figuring this out, I have been using the latter notation. However, I would like to know which of compilers is correct and why. (One would think that majority rules, but this still doesn't explain the why.)
This question seems to be related to the SO questions here, here, and here, but these "only" give solutions to problems, no explanations (that I was able to apply to my particular problem anyway).
Note that adding an additional overloaded casting operator, say operator float() const { return 3; } results in all compilers except SolarisStudio complaining about ambiguity.
The first (template) overload should be picked.
Paragraph 13.3.3/1 of the C++11 Standard specifies:
[...] a viable function F1 is defined to be a better function than another viable function
F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then
— for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,
— the context is an initialization by user-defined conversion (see 8.5, 13.3.1.5, and 13.3.1.6) and the
standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the
entity being initialized) is a better conversion sequence than the standard conversion sequence from
the return type of F2 to the destination type. [ Example:
struct A {
A();
operator int();
operator double();
} a;
int i = a; // a.operator int() followed by no conversion
// is better than a.operator double() followed by
// a conversion to int
float x = a; // ambiguous: both possibilities require conversions,
// and neither is better than the other
—end example ] or, if not that,
— F1 is a non-template function and F2 is a function template specialization, or, if not that,
[...]
As you can see the, fact that the first conversion operator is a template only becomes relevant when the standard conversion sequence from its return type (char, in this case) to the destination type (char, in this case) is not better than the standard conversion sequence from the return type of the non-template overload (int, in this case) to the destination type (char, in this case).
However, a standard conversion from char to char is an Exact Match, while a standard conversion from int to char is not. Therefore, the third item of § 13.3.3/1 does not apply, and the second item does.
This means that the first (template) overload should be picked.
The first is an exact match, the second requires a conversion. Exact matches have priority over conversions.
Those other questions you linked are mostly unrelated to yours.
Some advice: don't use template conversion operators. Name it convert_to instead.