LValue ref qualified member function being called on an RValue object - c++

I'm trying to figure out why the following snippet calls the LValue cast operator overload:
#include <iostream>
class Foo
{
public:
Foo(int i = 0) : i(i) {}
operator const int& () const &
{
std::cout << "lvalue\n";
return i;
}
operator int () const &&
{
std::cout << "rvalue\n";
return i;
}
int i = 0;
};
Foo Fool()
{
return Foo(5);
}
int main()
{
const int& i = Fool();
const int j = Fool();
return 0;
}
The current outputs are:
lvalue
rvalue
But from my understanding Fool() returns an rvalue and since const& can bind to rvalues there is no need to construct an lvalue Foo.
Can anyone explain why lvalue is being constructed? I believe this is a dangling lvalue.

Okay, so the thing to note here is that overload resolution only ever considers one conversion function for i. They don't both participate, and so the reference qualifier cannot be used to differentiate them. For the case of binding a reference
[over.match.ref]
Under the conditions specified in [dcl.init.ref], a reference can be
bound directly to the result of applying a conversion function to an
initializer expression. Overload resolution is used to select the
conversion function to be invoked. Assuming that “reference to cv1 T”
is the type of the reference being initialized, and “cv S” is the type
of the initializer expression, with S a class type, the candidate
functions are selected as follows:
The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S
and yield type “lvalue reference to cv2 T2” (when initializing an
lvalue reference or an rvalue reference to function) or “cv2 T2” or
“rvalue reference to cv2 T2” (when initializing an rvalue reference or
an lvalue reference to function), where “cv1 T” is
reference-compatible with “cv2 T2”, are candidate functions. For
direct-initialization, those explicit conversion functions that are
not hidden within S and yield type “lvalue reference to cv2 T2” (when
initializing an lvalue reference or an rvalue reference to function)
or “rvalue reference to cv2 T2” (when initializing an rvalue reference
or an lvalue reference to function), where T2 is the same type as T or
can be converted to type T with a qualification conversion, are also
candidate functions.
According to the text in bold, when initializing i, our only candidate is operator int const&. So overload resolution can either pass here, or fail entirely. But it cannot select operator int, since that one is not even under consideration. It succeeds because a const qualified lvalue reference can bind to the object argument.
On the other hand, for initializing a value
[over.match.conv]
Under the conditions specified in [dcl.init], as part of an
initialization of an object of non-class type, a conversion function
can be invoked to convert an initializer expression of class type to
the type of the object being initialized. Overload resolution is used
to select the conversion function to be invoked. Assuming that “cv1 T”
is the type of the object being initialized, and “cv S” is the type of
the initializer expression, with S a class type, the candidate
functions are selected as follows:
The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S
and yield type T or a type that can be converted to type T via a
standard conversion sequence are candidate functions. For
direct-initialization, those explicit conversion functions that are
not hidden within S and yield type T or a type that can be converted
to type T with a qualification conversion are also candidate
functions. Conversion functions that return a cv-qualified type are
considered to yield the cv-unqualified version of that type for this
process of selecting candidate functions. A call to a conversion
function returning “reference to X” is a glvalue of type X, and such a
conversion function is therefore considered to yield X for this
process of selecting candidate functions.
So when initializing j both conversion functions participate as overloads, and here the reference qualifier makes a difference.
You do get a dangling reference here, and it seems to be due to a dark corner in the language. The bullet in the first quoted paragraph could probably be refined to consider the binding of const lvlaue references better. Since those may bind to temporaries as well, your second conversion operator could ideally be a candidate under better rules.

Related

Understand the bullet (5.4.1) in [dcl.init.ref] clause

Given the following example,
struct S
{
operator const double&();
}
const int& ref = S();
First per [dcl.init.ref]/5:
A reference to type “cv1 T1” is initialized by an expression of type
“cv2 T2” as follows:
Taking "cv1 T1" as const int and "cv2 T2" as S.
Skipping all discarded bullets until we reach to [dcl.init.ref]/(5.4) which says:
(5.4) Otherwise:
(5.4.1) If T1 or T2 is a class type and T1 is not reference-related to T2, user-defined conversions are considered using the rules for
copy-initialization of an object of type “cv1 T1” by user-defined
conversion ([dcl.init], [over.match.copy], [over.match.conv]); the program is ill-formed if the corresponding non-reference copy-initialization would be ill-formed. The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference. For this direct-initialization, user-defined conversions are not considered.
(5.4.2) [..]
I think all we're agree on that this bullet (5.4.1) is one satisfied because T2 is a class type, and T1 is not reference-related to T2. But my problem at this point is that I can't understand the rest of the wording, and how it's related to each other.
Given the first sentence: "If T1 or T2 is a class type and T1 is not reference-related to T2, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion" What's the intention of the bold part? And the entity being initialized is "lvalue reference to cv1 T1" not an object of type "cv1 T1". So why the word "object" is mentioned here? Also, what're those rules of "copy-initialization"?
Given the second sentence: "The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference.". Does this mean that we have to go back again to the beginning of [dcl.init.ref]/5? and why?
The last thing I need to understand is that the reference being initialized is of type const int&, and the called conversion function returns const double&; so how ref can bind to const double which is of an unrelated type?
If T1 or T2 is a class type and T1 is not reference-related to T2, user-defined conversions are considered using the rules for copy-initialization of an object of type "cv1 T1" by user-defined conversion
As it says, you use the rules for copy-initialization of an object (not reference) of type cv1 T1. In other words, you pretend that the thing you have to initialize is an object, not reference, and you figure out how to perform such an initialization (treated as a copy-initialization, not a direct-initialization). The rules for copy-initialization of objects are found in [dcl.init.general]/16. In this case, you will reach p16.7:
Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated ([over.match.conv]), and the best one is chosen through overload resolution ([over.match]). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized.
If the conversion cannot be done or is ambiguous, the initialization is ill-formed.
[over.match.conv] explains how to determine the candidate conversion functions. Clearly, in this case there is only one candidate. The conversion sequence required to initialize an object of type const int from S() would be calling the conversion function S::operator const double&, followed by an lvalue-to-rvalue conversion, and finally a floating-integral conversion.
After [over.match] has been applied to choose the best conversion function, we go back to [dcl.init.ref]/5.4.1:
The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference.
Now, here's the subtlety: we look at the hypothetical conversion sequence that would have occurred if we were initializing an object of type const int, but we do not actually initialize an object of type const int. Instead, we only call the conversion function, S::operator const double&, and we ignore the rest of the hypothetical conversion sequence. With the result of the conversion function, which is an lvalue of type const double, we direct-initialize our reference from this value.
To determine how to perform this direct-initialization, we have to go back to the beginning of [dcl.init.ref]/5, but in order to avoid potential recursion, it is specified that user-defined conversions will not be considered during this second round. In this case [dcl.init.ref]/5.4.2 will be reached in the second round:
Otherwise, the initializer expression is implicitly converted to a prvalue of type "T1". The temporary materialization conversion is applied, considering the type of the prvalue to be "cv1 T1", and the reference is bound to the result.
This is where the temporary materialization conversion that you were wondering about is specified.

Is this an issue in the template argument deduction process | Conversion function template [duplicate]

This question already exists:
Deducing template argument of a conversion function template [duplicate]
Closed 6 months ago.
Consider the following example:
struct S
{
template <class T>
operator const T()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
return T();
}
} s;
int&& res = s;
Per [temp.deduct.conv]/1: (emphasis mine)
Template argument deduction is done by comparing the return type of
the conversion function template (call it P) with the type that is
required as the result of the conversion (call it A) [..]
and [temp.deduct.conv]/4: (emphasis mine)
If A is a cv-qualified type, the top-level cv-qualifiers of A's type
are ignored for type deduction. If A is a reference type, the type
referred to by A is used for type deduction.
and [temp.deduct.conv]/5 (emphasis mine)
In general, the deduction process attempts to find template argument
values that will make the deduced A identical to A. However, there are
four cases that allow a difference:
(5.1) If the original A is a
reference type, A can be more cv-qualified than the deduced A (i.e.,
the type referred to by the reference).
[..]
I'm expecting either of the following is occurring:
The types of P and A will be: P = const T, A = int&& Now, since A is a reference type, no adjustments occurred in P. But the type A would be adjusted per [temp.deduct.conv]/4. So the final types of P and A is P = const T, A = int. Now we've ended up with a deduction failure, so alternative deductions defined in [temp.deduct.conv]/5 are considered. The bullet (5.1) is what's needed in this case. Now A (int) is not more cv-qualified than deduced A (int). So the program is ill-formed because the initialization is invalid.
The reference initialization is invalid duto binding a reference of type int&& to a value of type const Twill drop qualifiers. So the program is ill-formed because the initialization is invalid..
Unfortunately, nothing of what I expected happened. The Live Demo clearly shows that the deduced T for the above example is int.
My question: What am I missing/conflating here?
N4861, to which you link, is not the newest working draft. I'm going to assume that you linked to it because it's the closest draft to C++20. The quotations that I'm going to provide below are from C++20 itself, just in case there are any differences.
According to [temp.deduct.conv]/1, we need to consult [dcl.init], [over.match.conv], and [over.match.ref] to determine what is A.
And in fact, [dcl.init] is really where you should have started looking in the first place, since the overload resolution is triggered by the declaration int&& res = s;.
[dcl.init.ref]/5.3 and 5.4 govern the initialization of rvalue references. 5.3 is tried first:
Otherwise, if the initializer expression
is an rvalue (but not a bit-field) or function lvalue and "cv1 T1" is reference-compatible with "cv2 T2", or
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an rvalue or function lvalue of type "cv3 T3", where "cv1 T1" is reference-compatible with "cv3 T3*" (see [over.match.ref]),
then the value of the initializer expression in the first case and the result of the conversion in the second case is called the converted initializer. If the converted initializer is a prvalue, its type T4 is adjusted to type "cv1 T4" ([conv.qual]) and the temporary materialization conversion ([conv.rval]) is applied. In any case, the reference is bound to the resulting glvalue (or to an appropriate base class subobject).
So we need to consult [over.match.ref] to see if such a cv3 T3 exists (in which case the conversion will be performed, and res will be bound to the result of that conversion). (Note that [over.match.conv] will not be reached in this case; it would be reached if we got to [dcl.init.ref]/5.4, but we will not, because [dcl.init.ref]/5.3 will succeed.)
[over.match.ref] says:
Under the conditions specified in [dcl.init.ref], a reference can be bound directly to the result of applying a conversion function to an initializer expression. Overload resolution is used to select the conversion function to be invoked. Assuming that "reference to cv1 T" is the type of the reference being initialized, and "cv S" is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:
The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S and yield type "lvalue reference to cv2 T2" (when initializing an lvalue
reference or an rvalue reference to function) or "cv2 T2" or "rvalue reference to cv2 T2" (when initializing an rvalue reference or an lvalue reference to function), where "cv1 T" is reference-compatible ([dcl.init.ref]) with "cv2 T2", are candidate functions. For direct-initialization, those explicit conversion functions that are not hidden within S and yield type "lvalue reference to cv2 T2" (when initializing an lvalue reference or an rvalue reference to function) or "rvalue reference to cv2 T2" (when initializing an rvalue reference or an lvalue reference to function), where T2 is the same type as T or can be converted to
type T with a qualification conversion ([conv.qual]), are also candidate functions.
The argument list has one argument, which is the initializer expression.
The only type that int is reference-compatible with is int itself, and we are initializing an rvalue reference, so according to this section, we look for conversion functions of S with type int and int&&.
Only at this point, we are ready to perform template argument deduction under [temp.deduct.conv]/1. We will need to do this twice: once with A = int, and once with A = int&&. The results are then merged, and if there is more than one candidate in the resulting set, then we need to use overload resolution to select the one that gets called.
With A = int, [temp.deduct.conv]/3.3 tells us that the top-level cv-qualifiers of P are ignored. Clearly, this results in T being deduced as int; the candidate is S::operator const int().
With A = int&&, in this case the top-level cv-qualifiers of P are not ignored; P remains const T. [temp.deduct.conv]/4 tells us that A is transformed into the type it refers to, namely int. We do the deduction using the transformed types (P = const T, A = int). This fails; there's no T such that const T is int. So there are no candidates generated in this step.
The result is that S::operator const int() is called, producing a prvalue of type const int, but cv-qualification of prvalues of scalar type is discarded prior to any further analysis. So it is materialized into a temporary object of type int, and res is bound to that temporary.

Possible bug in clang or gcc because of deleted templated lvalue conversion operator?

This is a follow-up of this question.
I played with this code and used clang trunk and gcc trunk:
struct A
{
};
struct T
{
A a;
operator A() { return a; }
template <typename T> operator const T&() = delete;
};
struct C
{
A a;
};
int main()
{
C c;
T t;
c.a = t;
}
Demo
Clang has nothing to complain, but gcc has:
<source>: In function 'int main()':
<source>:23:11: error: use of deleted function 'T::operator const T&() [with T = A]'
23 | c.a = t;
| ^
<source>:10:27: note: declared here
10 | template <typename T> operator const T&() = delete;
|
^~~~~~~~
So, which compiler is right and which is wrong?
I would like to see gcc is wrong, but how can I overcome the error?
Both Clang and GCC are wrong, but at least GCC stops compilation so it's marginally better.
A is a class type, and as such assigning to it passes through an operator= overload. You did not provide one, so the compiler provides two to the effect of
A& operator=(A const&) = default;
A& operator=(A&&) = default;
That reference argument is what needs to get initialized from t in the expression c.a = t. And for either operator=, the reference can be bound unambiguously.
[dcl.init.ref]
5 A reference to type “cv1 T1” is initialized by an expression of
type “cv2 T2” as follows:
If the reference is an lvalue reference and the initializer expression
[...]
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an lvalue of type
“cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (this
conversion is selected by enumerating the applicable conversion
functions ([over.match.ref]) and choosing the best one through
overload resolution),
then the reference is bound ... to the lvalue result of the
conversion in the second case (or, in either case, to the appropriate
base class subobject of the object).
Otherwise, if the initializer expression
[...]
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an rvalue or function
lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with
“cv3 T3” (see [over.match.ref]),
then ... the result of the conversion in the second case is called
the converted initializer. If the converted initializer is a prvalue,
its type T4 is adjusted to type “cv1 T4” ([conv.qual]) and the
temporary materialization conversion ([conv.rval]) is applied. In any
case, the reference is bound to the resulting glvalue (or to an
appropriate base class subobject).
Where on the subject of building that candidate set for these two cases, the standard says
[over.match.ref]
1 Under the conditions specified in [dcl.init.ref], a reference can
be bound directly to the result of applying a conversion function to
an initializer expression. Overload resolution is used to select the
conversion function to be invoked. Assuming that “reference to cv1 T”
is the type of the reference being initialized, and “cv S” is the type
of the initializer expression, with S a class type, the candidate
functions are selected as follows:
The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S
and yield type “lvalue reference to cv2 T2” (when initializing an
lvalue reference or an rvalue reference to function) or “cv2 T2” or
“rvalue reference to cv2 T2” (when initializing an rvalue reference or
an lvalue reference to function), where “cv1 T” is
reference-compatible with “cv2 T2”, are candidate functions. For
direct-initialization, those explicit conversion functions that are
not hidden within S and yield type “lvalue reference to cv2 T2” (when
initializing an lvalue reference or an rvalue reference to function)
or “rvalue reference to cv2 T2” (when initializing an rvalue reference
or an lvalue reference to function), where T2 is the same type as T or
can be converted to type T with a qualification conversion, are also
candidate functions.
That bullet needs a bit of work to parse correctly, but it basically describes one of two disjoint cases that may apply here:
Initialization of an lvalue reference to T
The candidate functions are those that yield "lvalue reference to cv2 T2".
Initialization of an rvalue reference to T
The candidate functions are those that yield "cv2 T2" or "rvalue reference to cv2 T2".
For operator=(A const&) we are in case #1, and have a synthesized operator A const&() as the only candidate. For operator=(A&&) its case #2, and the non-template operator A() is the only candidate. Either way, we have an unambiguous implicit conversion sequence with a user-defined conversion that binds the reference parameter of either operator=.
But now neither operator= is a better viable function than the other according to the rules in [over.match.best]. And neither conversion is better according to the partial ordering in [over.ics.rank].
This means that the the program should be declared ill-formed on account of an ambiguous call to operator=. However, GCC and Clang both err (not MSVC though)1. Clang favors the A&& overload, while GCC goes for A const& and issues a diagnostic for the use of a deleted conversion function. But this isn't due to any standard mandated behavior. Ideally, they should both report the call to operator= is ambiguous.
1 - A comparison of different compiler behaviors, with a reduced example.

Best viable conversion function

In the following program, which (if any) conversion function should be selected and why?
int r;
struct B {};
struct D : B {};
struct S {
D d;
operator D&(){r=1; return d;} // #1
operator B&(){r=2; return d;} // #2
};
int main() {
S s;
B& b = s;
return r;
}
Both gcc and clang select select the conversion function #2. But why?
The standard says:
(1) Under the conditions specified in [dcl.init.ref], a reference can be bound directly to the result of applying a conversion function to an initializer expression.
Overload resolution is used to select the conversion function to be invoked.
Assuming that “reference to cv1 T” is the type of the reference being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:
(1.1) - The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S and yield type “lvalue reference to cv2 T2” (when initializing an lvalue reference or an rvalue reference to function) or “cv2 T2” or “rvalue reference to cv2 T2” (when initializing an rvalue reference or an lvalue reference to function), where “cv1 T” is reference-compatible with “cv2 T2”, are candidate functions. For direct-initialization, those explicit conversion functions that are not hidden within S and yield type “lvalue reference to cv2 T2” (when initializing an lvalue reference or an rvalue reference to function) or “rvalue reference to cv2 T2” (when initializing an rvalue reference or an lvalue reference to function), where T2 is the same type as T or can be converted to type T with a qualification conversion, are also candidate functions.
(2) The argument list has one argument, which is the initializer expression.
[ Note: This argument will be compared against the implicit object parameter of the conversion functions.
— end note
 ]
Here we have two candidate functions #1 and #2. Both are viable - if one of them is deleted, the program still compiles.
Both conversion functions take only the implicit argument and have the same cv- and ref-qualification on it. So none should be the best viable, and the program should not compile. Why does it compile?
Well, as you know, overload resolution occurs in three stages: (1) enumerate candidate functions; (2) determine which candidate functions are viable; (3) select the best viable function.
According to [over.match.best]/1:
... 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(F1), 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 11.6, 16.3.1.5, and 16.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 ... ] or, if not that,
[ ... further tie-breaker rules ... ]
The implicit conversion required from s to the implicit object parameter of either #1 or #2 is the identity conversion, so ICS1(#1) and ICS2(#1) are indistinguishable and the second bullet point is relevant here. In the case of #1, a derived-to-base conversion is required to convert from the return type of the conversion function, namely D&, to the required type, namely B&. In the case of #2, the standard conversion sequence is the identity conversion (B& to B&), which is better. Therefore, function #2 is chosen as better than #1 in this context.

Why can a member function be called on a temporary but a global function cannot?

In the below code I call step as a member function and as a global function on a temporary value. The member function is allowed, and works, whereas the global function is disallowed due to invalid initialisation of non-const reference of type ‘kludge&’ from an rvalue of type ‘kludge’.
I'm trying to understand, from a language perspective, why one behaviour is allowed and the other is not. Technically both calls and functions seem like they'd be compiled identically, or at least could be.
#include <iostream>
struct kludge {
int a;
kludge() {
a = 1;
}
kludge & step() {
a++;
std::cout << a << ",";
return *this;
}
};
kludge get() {
kludge t;
return t;
}
kludge & step( kludge & t ) {
t.a++;
std::cout << t.a << ",";
return t;
}
int main() {
get().step();
step( get() );
}
You cannot bind rvalues to non-const lvalue references1. That applies to step(get()) as the parameter of step, which is a non-const lvalue reference, cannot be bound to the prvalue (pure rvalue) get().
However, member functions can per se be called on object arguments of every value category, be it lvalue or rvalue - [over.match.funcs]/4 and /5:
For non-static member functions, the type of the implicit object
parameter is
“lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
[..]
For non-static member functions declared without a ref-qualifier, an
additional rule applies:
even if the implicit object parameter is
not const-qualified, an rvalue can be bound to the parameter as long
as in all other respects the argument can be converted to the type of
the implicit object parameter. [ Note: The fact that such an
argument is an rvalue does not affect the ranking of implicit
conversion sequences (13.3.3.2). — end note ]
But if you use so-called ref-qualifiers, you can restrict the value categories that are valid for a particular member function. That is, if you write:
kludge & step() & { /* .. */ }
The call get().step() will be ill-formed too.
1)
This is a well-known fact, but here is [dcl.init.ref]/5, heavily shortened:
A reference to type “cv1 T1” is initialized by an expression of
type “cv2 T2” as follows:
If the reference is an lvalue reference and the initializer expression
is an lvalue [..]
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an lvalue of type “cv3 T3,”
[..]
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the
reference shall be an rvalue reference.
Temporary cannot bind to non-const reference
step( get() );
// ~~~~~ Creates a temporary object (r-value)
// But step( ) excepts a non-const reference