Can a default function return auto? - c++

Consider the following class:
class example
{
public:
auto & operator =(const example &) = default;
auto & operator =(example &&) = default;
};
Are those declarations considered legal?

auto & operator =(const example &) = default;
auto & operator =(example &&) = default;
Are those declarations considered legal?
No.
[dcl.spec.auto] ... If the declared return type of the function contains a placeholder type, the return type of the function is deduced from non-discarded return statements, if any, in the body of the function ([stmt.if]).
A defaulted function definition does not have a body, which conflicts the quoted rule. There is nothing that the return type could be deduced from and there is no rule that states what that type would be in this case.
operator<=> has an exceptional rule specifying what the return type will be when auto is used as shown in Caleth's answer, but operator= does not have such rule. I see no reason such rule couldn't be introduced to allow auto in defaulted assignment operators.

In C++20, Yes1
Let R be the declared return type of a defaulted three-way
comparison operator function, and let xi be the elements of the
expanded list of subobjects for an object x of type C`.
If R is auto, then let cvi Ri be the type of the expression xi <=> xi. The operator function is defined as deleted if that
expression is not usable or if Ri is not a comparison category type
([cmp.categories.pre]) for any i. The return type is deduced as the
common comparison type (see below) of R0, R1, …, Rn−1.
[class.spaceship/2]
Before C++20, No
A function definition of the form: attribute-specifier-seq opt
decl-specifier-seq opt declarator virt-specifier-seq opt = default ; is called an explicitly-defaulted definition. A function that is
explicitly defaulted shall
be a special member function,
have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy
constructor or copy assignment operator, the parameter type may be
“reference to non-const T”, where T is the name of the member
function's class) as if it had been implicitly declared, and
[dcl.fct.def.default] (emphasis added)
But only <=>. A defaulted == must return bool, and assignment has similar restrictions as previous standards.
if F is an assignment operator, and the return type of T1 differs
from the return type of T2 or T1's parameter type is not a
reference, the program is ill-formed;
[dcl.fct.def.default]

not a language lawyer answer
From my experience compilers don't accept auto return type for defaulted special member functions, so I assume they are indeed not allowed by the standard.
The only exception I know of is the C++20 default three-way comparison operator:
#include <compare>
struct X
{
auto operator<=>(const X&) const = default;
};

Related

Default constructor expression and lvalues

My C++ colleagues and I ran into a curious construct:
struct A { int i; };
void foo(A const& a);
int main() {
foo(A() = A{2}); // Legal
}
The A() = A{2} expression completely befuddled us as it appears to be assigning A{2} to a temporary, default-constructed object. But see it in compiler explorer (https://gcc.godbolt.org/z/2LsfSk). It appears to be a legal statement (supported by GCC 9 and Clang 9), as are the following statements:
struct A { int i; };
int main() {
A() = A{2};
auto a = A() = A{3};
}
So it appears, then, that in some contexts A() is an lvalue. Or is something else going on here? Would appreciate some explanation and, preferably, a reference to the C++17 standard.
Update: #Brian found that this is a duplicate of assigning to rvalue: why does this compile?. But would really appreciate if someone could find the appropriate reference in the C++ standard.
A{} is always an rvalue per [expr.type.conv]
1 A simple-type-specifier or typename-specifier followed by a parenthesized optional expression-list or by a braced-init-list (the initializer) constructs a value of the specified type given the initializer.
If the type is a placeholder for a deduced class type, it is replaced by the return type of the function selected by overload resolution for class template deduction for the remainder of this subclause.
2 If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression.
Otherwise, if the type is cv void and the initializer is () or {} (after pack expansion, if any), the expression is a prvalue of the specified type that performs no initialization.
Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer.
If the initializer is a parenthesized optional expression-list, the specified type shall not be an array type.
emphasis mine
The reason these works is here is nothing in the standard to stop it from working.
For built in types like int there is [expr.ass]/1
The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand; their result is an lvalue referring to the left operand.
So this stops you from doing int{} = 42;. This section doesn't apply to classes, though. If we look in [class.copy.assign] there is nothing that says that an lvalue is required, but the first paragraph does state
A user-declared copy assignment operator X​::​operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X&, or const volatile X&
Which means
A{} = A{2};
is actually
A{}.operator=(A{2})
Which is legal to do on an rvalue class object since the default operator = for your class has no ref-qualifier to stop it from being called on rvalues. If you add
A& operator=(const A& a) & { i = a.i; }
to A instead of using the default assignment operator then
A{} = A{2};
would no longer compile since the operator= will only work on lvalues now.

Declaring defaulted assignment operator as constexpr: which compiler is right?

Consider
struct A1 {
constexpr A1& operator=(const A1&) = default;
~A1() {}
};
struct A2 {
constexpr A2& operator=(const A2&) = default;
~A2() = default;
};
struct A3 {
~A3() = default;
constexpr A3& operator=(const A3&) = default;
};
GCC and MSVC accept all three structs. Clang rejects A1 and A2 (but accepts A3), with the following error message:
<source>:2:5: error: defaulted definition of copy assignment operator is not constexpr
constexpr A1& operator=(const A1&) = default;
^
<source>:6:5: error: defaulted definition of copy assignment operator is not constexpr
constexpr A2& operator=(const A2&) = default;
^
2 errors generated.
(live demo)
Which compiler is correct, and why?
I think all three compilers are wrong.
[dcl.fct.def.default]/3 says:
An explicitly-defaulted function that is not defined as deleted may be declared constexpr or consteval only if it would have been implicitly declared as constexpr. If a function is explicitly defaulted on its first declaration, it is implicitly considered to be constexpr if the implicit declaration would be.
When is the copy assignment operator implicitly declared constexpr? [class.copy.assign]/10:
The implicitly-defined copy/move assignment operator is constexpr if
X is a literal type, and
[...]
Where a literal type is, from [basic.types]/10:
A type is a literal type if it is:
[...]
a possibly cv-qualified class type that has all of the following properties:
it has a trivial destructor,
[...]
A1 doesn't have a trivial destructor, so its implicit copy assignment operator isn't constexpr. Hence that copy assignment operator is ill-formed (gcc and msvc bug to accept).
The other two are fine, and it's a clang bug to reject A2.
Note the last bit of [dcl.fct.def.default] that I quoted. You don't actually have to add constexpr if you're explicitly defaulting. It would be implicitly constexpr where that is possible.
The C++17 standard states:
15.8.2 Copy/move assignment operator [class.copy.assign]
...
10 A copy/move assignment operator for a class X that is defaulted and not defined as deleted is implicitly defined when it is odr-used (6.2) (e.g., when it is selected by overload resolution to assign to an object of its class type) or when it is explicitly defaulted after its first declaration. The implicitly-defined copy/move assignment operator is constexpr if
(10.1) — X is a literal type, and
(10.2) — the assignment operator selected to copy/move each direct base class subobject is a constexpr function, and
(10.3) — for each non-static data member of X that is of class type (or array thereof), the assignment operator selected to copy/move that member is a constexpr function.
The copy-assignment operator satisfies the above requirements in two of the cases. In the first case, we have a non-literal type because of the non-trivial destructor.
So I believe Clang is wrong to reject the code in the second case.
There is a bug filed with Clang titled: Defaulted destructor prevents using constexpr on defaulted copy/move-operator which shows the same symptoms as the code in the OP.
The comments from the bug report state:
When defaulted destructor is commented out (i.e. not user declared), then errors cease to exist.
and
The problem also goes away if you declare the destructor before the copy assignment operator.
This is true of the code in the question as well.
As #YSC points out, another relevant quote here is:[dcl.fct.def.default]/3 which states:
An explicitly-defaulted function that is not defined as deleted may be declared constexpr or consteval only if it would have been implicitly declared as constexpr. If a function is explicitly defaulted on its first declaration, it is implicitly considered to be constexpr if the implicit declaration would be.

Template assignment operator doesn't replace the default assignment operator

In C++ Templates The Complete Guide in section 5.3 Member Templates it's written:
Note that a template assignment operator doesn't replace the default
assignment operator. For assignments of stacks of the same type, the
default assignment operator is still called.
Is this correct, because when I ran below code:
#include<iostream>
using namespace std;
template<typename T>
class Pair
{
public:
T pair1,pair2;
Pair(T i,T j):pair1(i),pair2(j){}
template<typename T1>Pair<T>& operator=(Pair<T1>&);
};
template<typename T>
template<typename T1>
Pair<T>& Pair<T>::operator=(Pair<T1>& temp)
{
this->pair1 =temp.pair1*10;//At this point
this->pair2=temp.pair2;
return *this;
}
int main()
{
Pair<int>P1(10,20);
Pair<int>P2(1,2);
P2=P1;
cout<<P2.pair1<<' '<<P2.pair2<<endl;
return 1;
}
I got answer 100 20.
It didn't give the default assignment answer.
Is that a typing mistake in C++ Templates the Complete Guide?
C++ Templates: The Complete Guide By David Vandevoorde, Nicolai M.
Josuttis
Publisher : Addison Wesley
Pub Date : November 12, 2002 Pages : 552
The copy assignment operator is indeed implicitly declared and considered by overload resolution.
A user-declared copy assignment operator X::operator= is a
non-static non-template member function of class X [..]. If the class definition does not explicitly
declare a copy assignment operator, one is declared implicitly. [..]
The implicitly-declared copy assignment operator for a class X will
have the form
X& X::operator=(const X&)
if
each direct base class B of X has a copy assignment operator whose parameter is of type const B&, const volatile B& or B, and
for all the non-static data members of X that are of a class type M (or array thereof), each such class type has a copy assignment
operator whose parameter is of type const M&, const volatile M& or
M.
Otherwise, [..]
As you can see the implicitly-declared copy assignment operator for Pair<int> has one parameter of type Pair<int> const& - note the const in particular! Overload resolution favours non-const references over const ones if both can be bound to the argument, [over.ics.rank]/3:
Two implicit conversion sequences of the same form are
indistinguishable conversion sequences unless one of the following
rules applies:
—
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence
S2 if
[..]
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.
The specialization of the template lacks a const in the reference parameter, thus it's a better match and is selected.
The default assignment operator accepts the argument as a const reference: http://en.cppreference.com/w/cpp/language/as_operator.
You have defined a version without const, and your version is better in the context of overload resolution (no conversion required).
Try with the following change:
int main()
{
Pair<int>P1(10,20);
Pair<int>P2(1,2);
const Pair<int>& x = P1;
P2=x;
cout<<P2.pair1<<' '<<P2.pair2<<endl;
return 1;
}
to see the expected result.

Can I add an implicit conversion from a volatile T to a T?

This code
struct T {
int m_x;
T(int x) : m_x(x) {}
operator T() {
return T(0);
}
};
int main() {
volatile T v(2);
T nv(1);
nv = v; // nv.m_x = 0
}
Gives:
prog.cpp: In function ‘int main()’:
prog.cpp:14:10: error: no match for ‘operator=’ in ‘nv = v’
prog.cpp:14:10: note: candidates are:
prog.cpp:1:8: note: T& T::operator=(const T&)
prog.cpp:1:8: note: no known conversion for argument 1 from ‘volatile T’ to ‘const T&’
prog.cpp:1:8: note: T& T::operator=(T&&)
prog.cpp:1:8: note: no known conversion for argument 1 from ‘volatile T’ to ‘T&&’
What typecast overload do I need to define for this to work?
The short answer:
Yes you can but the compiler won't do the job for you.
You cannot have an compiler-provided conversion from volatile T to T but a user-defined implicit conversion using a volatile-qualified constructor.
It is also impossible to declare such a conversion by using explicitly-defaulted versions of the special member functions (see long answer for reference).
You'll have to provide a user-defined way of conversion to enable such assignments. You can either
use a non-explicit copy constructor with a cv-qualified argument for implicit user-defined conversion or
a copy assignment operator taking a v-qualified argument.
Example:
X (X const volatile & xo);
X& operator= (X const volatile & xo);
The long answer with standard quotes 'n stuff or
why doesn't the compiler do this for me?
Way 1: User-provided constructor from volatile T
Standard, ISO 14882:2011, 4/3
An expression e can be implicitly converted to a type T if and only if the declaration T t=e; is well-formed, for some invented temporary variable t (8.5).
Since the declaration T t = e;, where in this case e is typed volatile T, requires such a copy-initialization to be valid, you'll need a copy constructor from a volatile T.
I already answered (Why am I not provided with a default copy constructor from a volatile?).
Therefore you'll need to provide a user-defined way of copy-initialization of T from volatile T.
X (X const volatile & xo);
Note:
This is a declaration, you'll also have to provide a definition.
The constructor must not be explicit.
Providing an user-defined copy constructor taking a volatile argument will result in the abscence of an implicitly generated default assignment operator.
This will make your assignment work.
Way 2: User-provided copy assignment operator from volatile T
Another way to make the assignment of your example code work is a copy assignment operator.
Unfortunatelly, the standard also does say that a compiler will not provide implicit copy assignment operators for the conversion of volatile to non volatile objects.
Standard, ISO 14882:2011, 12.8/18
If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy assignment operator is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy constructor or a user-declared destructor. The implicitly declared copy assignment operator for a class X will have the form
X& X::operator=(const X&)
if
each direct base class B of X has a copy assignment operator whose parameter is of type const B&, const volatile B& or B, and
for all the non-static data members of X that are of a class type M (or array thereof), each such class type has a copy assignment operator whose parameter is of type const M&, const volatile M& or M. 122
Otherwise, the implicitly-declared copy assignment operator will have the form
X& X::operator=(X&)
Note 122 on 12.8/18
the reference parameter of the implicitly-declared copy assignment operator cannot bind to a volatile lvalue; see C.1.9.
In the other answer I quoted C.1.9 where it says:
The implicitly-declared copy constructor and implicitly-declared copy assignment operator cannot make a copy of a volatile lvalue. [ ... ]
The result is that we'll have to provide a suitable copy assignment operator if we want to have one.
X& operator= (X const volatile & xo);
Also note that you cannot declare an assignment/constructor from volatile explicitly-defaulted.
C++11 Standard 8.4.2/1
A function that is explicitly defaulted shall
be a special member function,
have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be “reference to non-const T”, where T is the name of the member function’s class) as if it had been implicitly declared, and
not have default arguments.
The following Note was removed from the final C++11 standard but was present in the Draft N3242. It still holds.
[ Note: This implies that parameter types, return type, and cv-qualifiers must match the hypothetical implicit declaration. —end note ]
Since the hypothetical implicit declaration is non-volatile you cannot have the defaulted.
Here's how you get a copy constructor and copy assignment that allows a volatile source:
struct X {
X(const X& o) : members(o.members) {}
X(const volatile X& o) : members(o.members) {}
X& operator=(const X& o) {v=o.v; return *this;}
X& operator=(const volatile X& o) {v=o.v; return *this;}
};
Note though that this has some consequences. The type is no longer POD or even trivially copyable, for one. Which might defeat the whole point of making it volatile.
You can implement the assignment operator =:
T& operator=(const volatile T &rhs) {
m_x = rhs.m_x;
return *this;
}

Ambiguous assignment operator

I have two classes, one of which, say, represents a string, and the other can be converted to a string:
class A {
public:
A() {}
A(const A&) {}
A(const char*) {}
A& operator=(const A&) { return *this; }
A& operator=(const char*) { return *this; }
char* c;
};
class B {
public:
operator const A&() const {
return a;
}
operator const char*() const {
return a.c;
}
A a;
};
Now, if I do
B x;
A y = x;
It triggers copy constructor, which compiles fine. But if I do
A y;
y = x;
It complains about ambiguous assignment, and can't choose between =(A&) and =(char*). Why the difference?
There is a difference between initialization and assignment.
In initialization, that is:
A y = x;
The actual call depends on the type of x. If it is the same type of y, then it will be like:
A y(x);
If not, as in your example, it will be like:
A y(static_cast<const A&>(x));
And that compiles fine, because there is no ambiguity any more.
In the assignment there is no such special case, so no automatic resolution of the ambiguity.
It is worth noting that:
A y(x);
is also ambiguous in your code.
There is §13.3.1.4/(1.2), only appertaining to (copy-)initialization of objects of class type, that specifies how candidate conversion functions for your first case are found:
Under the conditions specified in 8.5, as part of a
copy-initialization of an object of class type, a user-defined
conversion can be invoked to convert an initializer expression to the
type of the object being initialized. Overload resolution is used to
select the user-defined conversion to be invoked. […] Assuming that
“cv1 T” is the type of the object being initialized, with T a class
type, the candidate functions are selected as follows:
The converting constructors (12.3.1) of T are candidate
functions.
When the type of the initializer expression is a class type
“cv S”, the non-explicit conversion functions of S and its base
classes are considered. When initializing a temporary to be bound to
the first parameter of a constructor where the parameter is of type
“reference to possibly cv-qualified T” and the constructor is called
with a single argument in the context of direct-initialization of an
object of type “cv2 T”, explicit conversion functions are also
considered. Those that are not hidden within S and yield a type
whose cv-unqualified version is the same type as T or is a derived
class thereof are candidate functions. […] Conversion functions that return “reference to X” return lvalues or xvalues,
depending on the type of reference, of type X and are therefore considered to yield X for this process of selecting candidate functions.
I.e. operator const char* is, though being considered, not included in the candidate set, since const char* is clearly not similar to A in any respect. However, in your second snippet, operator= is called as an ordinary member function, which is why this restriction doesn't apply anymore; Once both conversion functions are in the candidate set, overload resolution will clearly result in an ambiguity.
Note that for direct-initialization, the above rule doesn't apply either.
B x;
A y(x);
Is ill-formed.
A more general form of this result is that there can never be two user-defined conversions in one conversion sequence during overload resolution. Consider §13.3.3.1/4:
However, if the target is
the first parameter of a constructor or […]
and the constructor […] is a candidate
by
13.3.1.3, when the argument is the temporary in the second step of a class copy-initialization, or
13.3.1.4, 13.3.1.5, or 13.3.1.6 (in all cases),
user-defined conversion sequences are not considered. [Note: These
rules prevent more than one user-defined conversion from being applied
during overload resolution, thereby avoiding infinite recursion. — end
note ]