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.
Related
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.
#include <iostream>
struct Data{};
struct Test{
Test() = default;
Test(Data){}
};
int main(){
Data d;
Test const& rf = d;
}
Consider the above code, The standard says:
Otherwise:
5.2.2.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.2.2.2 Otherwise, the initializer expression is implicitly converted to a prvalue of type “cv1 T1”. The temporary materialization conversion is applied and the reference is bound to the result.
So, which bullet does the above case obey? The initializer expression is converted to type Test through converting constructor Test::Test(Data) rather than conversion function. However note the emphasized part in 5.2.2.1, It says that the result of the call to the conversion function is then used to direct-initialize the reference. In my example, the called function was converting constructor, hence, the resulting was resulted from converting constructor.
Issue 1:
which bullet covers my example? 5.2.2.1 or 5.2.2.2?
5.2.1.2 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]),
Consider the bullet 5.2.1.2, It has already covered the case which type T2 is a class type, and it can be converted to cv3 T3 through conversion function.
Issue 2:
So, Is it redundant that 5.2.2.1 covers T2 is a class type, and it can be converted to destination type through conversion function, Such case has already covered in 5.2.1.2?
Issue 1
[dcl.init.ref]/5.2.2.1 applies here. It is the paragraph covering user-defined conversions. One of the acceptable conversion mechanisms is 16.3.1.4 [over.match.copy] which can use converting constructors on T1. The value is converted using this constructor and the resulting temporary is bound to the reference.
[dcl.init.ref]/5.2.2.2 applies to cases of implicit conversion not including user-defined conversion, such as widening numeric conversions.
Issue 2
From [dcl.init.ref]:
(5.2.1) If the initializer expression
...
(5.2.1.2) 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 16.3.1.6),
Jumping to 16.3.1.6 [over.match.ref], there is a lot of prose here, but this is the only relevant part:
(1) ... 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. ...
The rest of the section details which conversion functions of S are eligible to be used, but that's unimportant to the situation in the example code. [over.match.ref] considers only conversion operators on the type of the value being used to initialize the reference, which would not be the case here -- Data has no implicit conversion operators. This section makes no reference to converting constructors of T.
Therefore [over.match.ref] and by extension [dcl.init.ref]/5.2.1.2 do not apply to this case.
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.
For example, in the snippet below the user-defined conversion function C::operator A() is implicitly invoked to convert an lvalue of type C into a prvalue of type A, which copy-initializes the variable a in a direct-initialization.
struct A {};
struct C { operator A() { return A(); }; };
int main()
{
C c;
A a(c);
}
I just want to know where this is described in the C++14 Standard. I have a feeling that the answer is in [over.match.copy]/1 bullet point (1.2), but I'm having a problem with the section title Copy-initialization by user-defined conversion.
There are two constructors on A that can be invoked with a single argument:
A(A const&); // copy constructor
A(A&& ); // move constructor
In either case, [dcl.init.ref] explains how we can initialize the reference:
A reference of 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 (but is not a bit-field), 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 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 initializer expression lvalue in the first case and to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object).
We have references of type A or A const being initialized by an expression of type C, which is a class type not reference-related to A. To check if it can be converted to a reference-compatible type cv3 T3, we check [over.match.ref]:
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, [...]
Hence, for the copy constructor, we consider those conversion functions that yield A& and for the move constructor, we consider those conversion functions that yield A or A&&. We don't have the former, but we do have the latter: operator A().
This makes the move constructor of A a viable constructor, but not the copy constructor of A. Since we only have one viable candidate, it is trivially the best viable candidate.
§8.5.3[dcl.init.ref]/5 bullet point (5.2.2.1):
Emphases are mine
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 userdefined
conversion (8.5, 13.3.1.4, 13.3.1.5); 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.
This was an addendum to the standard to address an issue with the discarding of cv-qualifiers through indirect reference binding of a class with user-defined conversions.
The pertinent defect is 1571.
The example given was this:
class A {
public:
operator volatile int &();
};
A a;
const int & ir1a = a.operator volatile int&(); // error!
const int & ir2a = a; // allowed! ir = a.operator volatile int&();
The first reference initialization is invalid, as it discards cv-qualifiers from the initializer expression. However, under the old rules, the second was valid, as only the cv-qualifiers on the converted object were considered, not those of the reference returned by the conversion operator.
I think your confusion arises from the two possible conversions. User-defined conversions are considered to see if they could copy-initialize a non-reference of cv1 T1, then the result of that conversion is used to direct-initialize the reference, for which user-defined conversions are not considered.