I found out the rules for determining the candidate conversion functions, for direct reference binding, are not described clearly by the standard at least for me.
The rules are found in [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, the candidate
functions are selected as follows:
(1.1) Let R be a set of types including
(1.1.1) “lvalue reference to cv2 T2” (when initializing an lvalue reference or an rvalue reference to function) and
(1.1.2) “cv2 T2” and “rvalue reference to cv2 T2” (when initializing an rvalue reference or an lvalue reference to function) for any T2.
The permissible types for non-explicit conversion functions are the
members of R where “cv1 T” is reference-compatible ([dcl.init.ref])
with “cv2 T2”. For direct-initialization, the permissible types for
explicit conversion functions are the members of R where T2 can be
converted to type T with a (possibly trivial) qualification conversion
([conv.qual]); otherwise there are none.
As far as I understood, I can say that when initializing an "lvalue reference to cv1 T" the conversion function is a candidate if and only if it returns "lvalue reference to cv2 T2", where “cv1 T” is reference-compatible with “cv2 T2”.
And when initializing an "rvalue reference to cv1 T", the conversion function is a candidate if and only if it returns "rvalue reference to cv2 T2" or "cv T2", where “cv1 T” is reference-compatible with “cv2 T2”. Right?
Assuming my understanding is correct, here is a question. Why do we even need a conversion function to convert from cv2 T2 to cv1 T even though “cv1 T” is reference-compatible with “cv2 T2”?
Can you clearly explain the bullet (1.1) with some examples?
Why do we even need a conversion function to convert from cv2 T2 to cv1 T even though “cv1 T” is reference-compatible with “cv2 T2”?
Because only after the conversion is done(using the conversion function), the result is of type cv2 T2. In other words, the result of using/calling the conversion function is cv2 T2 and not whatever expression you have before using the conversion function. This is the whole point of using a conversion function so that we can something that is reference compatible with cv1 T.
Lets look at some examples:
Example 1
struct C {
operator int&();
};
const int& ref= C();
In the above example, the type cv1 T = const int is not reference-compatible with the class-type C. But due to the presence of the conversion function C::operator int&(), a given C can be converted to an lvalue of type cv2 T2 = int. And we know that const int is reference compatible with int. This means that the reference ref can be bound to the result of the conversion function according to your quoted clause.
Example 2
This example 2 does not uses a conversion function but i've added this just to clarify the explanation given at the beginning of my answer.
double val = 4.55;
const int &ref = val;
In the above example, the type cv1 T = const int is not reference-compatible with the type double. And at the end, the reference ref is bound to a materialized temporary(prvalue) of type cv2 T2 = int. This is possible because const int is reference compatible with the type of the materialized temporary int.
The important thing to note here is that a temporary is materialized because originally cv1 T = const int was not reference compatible with double. But after the materialization, cv1 T = const int is reference compatible with the result cv2 T2 = int.
Related
The following quotes are needed in the question:
[dcl.init.ref]/5:
5- A reference to type “cv1 T1” is initialized by an expression of
type “cv2 T2” as follows:
(5.1) [..]
(5.2) [..]
(5.3) Otherwise, if the initializer expression
(5.3.1) is an rvalue (but not a bit-field) or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
(5.3.2) [..]
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).
(emphasis mine)
[expr.type]/2:
If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.
Consider the following example:
const int&& r1 = 0;
Taking cv1 T1 as const int, and cv2 T2 as int
It's clear that bullet [dcl.init.ref]/(5.3.1) is applied here. The initializer expression is an rvalue (prvalue); and cv1 T1 (const int) is reference-compatible with cv2 T2 (int). And since that the converted initializer is prvalue, its type T4 (int) is adjusted to cv1 T4 (const int). Then, temporary materialization is applied.
But, per [expr.type]/2, before applying temporary materialization conversion, cv1 T4 (const int) becomes int again. Then, by applying temporary materialization, we've got an xvalue denoting an object of type int. Then the reference is bound to the resulting glvalue.
Here's my first question. The reference r1 is reference to const int and the resulting glvalue is an xvalue denoting an object of type int. So how r1, which is of type const int&&, is now binding to an xvalue of type int? Is this valid binding? Is any missing wording? Am I misunderstood/missed something?
Consider another last example:
const int&& r2 = static_cast<int&&>(0);
The same wording as above applies: The initializer expression is an rvalue (xvalue) and cv1 T1 (const int) is reference-compatible with cv2 T2 (int). And since that the converted initializer is an xvalue not prvalue, [conv.qual] or even [conv.rval] is not applied (i.e, the condition "If the converted initializer is a prvalue, ..") isn't satisfied.
I know that [conv.rval] isn't needed here since the initializer expression is already an xvalue, but [conv.qual] is required.
And that's my last question. The reference r2 is reference to const int and the resulting glvalue is an xvalue denoting an object of type int. So how r2, which is of type const int&&, is now binding to an xvalue of type int? Is this valid binding? Is any missing wording? Am I misunderstood/missed something?
Here's my first question. The reference r1 is reference to const int and the resulting glvalue is an xvalue denoting an object of type int. So how r1, which is of type const int&&, is now binding to an xvalue of type int? Is this valid binding?
Yes, that's what happens. I fail to see the issue here.
I know that [conv.rval] isn't needed here since the initializer expression is already an xvalue, but [conv.qual] is required.
No it isn't. Again, I fail to see the issue.
There is, in fact, no rule that says that a reference of type T&&, can only refer to an object whose type is exactly T. A const int&& can refer to an int object. The concept of reference-compatibility was invented in order to describe what types of objects a reference can refer to.
int main(int argc, char const *argv[])
{
int *i;
const int * &j = i;
}
This code gives the error error: non-const lvalue reference to type 'const int *' cannot bind to a value of unrelated type 'int *
But it's not clear to me why this is not allowed. I defined j to be a reference to a pointer to a constant int. So if I set it equal to i, aren't I saying that j is a reference of i and we are not allowed to modify the int pointed to by i through j?
So if I set it equal to i, aren't I saying that j is a reference of i
That's what you're trying to say. But the type of i is incompatible, so that's something that cannot be said.
and we are not allowed to modify the int pointed to by i through j?
If you had a pointer to const, then you couldn't modify the pointed int. And you could refer to such pointer with j. But you haven't created such pointer.
A pointer to non-const is convertible to pointer to const however. But result of such conversion is an rvalue, so your reference to non-const cannot be bound to it. If you used a reference to const, it would extend the lifetime of the temporary result of the implicit conversion:
const int * const &j = i;
But the reference just adds unncecessary cofusion. Better give the converted pointer a name, rather than refer to a temprary object:
const int* j = i;
Why is the type of i not compatible?
Because the language rules say so.
Is this just the rule?
This is the rule (from latest standard draft):
Given types “cv1 T1” and “cv2 T2”, “cv1 T1” is reference-related to “cv2 T2” if T1 is similar ([conv.qual]) to T2, or T1 is a base class of T2.
“cv1 T1” is reference-compatible with “cv2 T2” if a prvalue of type “pointer to cv2 T2” can be converted to the type “pointer to cv1 T1” via a standard conversion sequence ([conv]).
In all cases where the reference-compatible relationship of two types is used to establish the validity of a reference binding and the standard conversion sequence would be ill-formed, a program that necessitates such a binding is ill-formed.
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 (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”92 (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).
Otherwise, if the reference is an lvalue reference to a type that is not const-qualified or is volatile-qualified, the program is ill-formed.
Given this example:
int g_i = 10;
struct S {
operator int&(){ return g_i; }
};
int main() {
S s;
int& iref1 = s; // implicit conversion
int& iref2 = {s}; // clang++ error, g++ compiles fine:
// `s` is converted
// to a temporary int and binds with
// lvalue reference
int&& iref3 = {s}; // clang++ compiles, g++ error:
// cannot bind rvalue reference
// to lvalue
}
The errors are as described in the comments.
gcc 8.2.1 and clang 7.0.1 were used and disagree about what is happening in this example. Could someone clarify this?
In list initialization :
Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization); if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.
Otherwise, if T is a reference type, a prvalue of the type referenced by T is generated. The prvalue initializes its result object by copy-list-initialization or direct-list-initialization, depending on the kind of initialization for the reference. The prvalue is then used to direct-initialize the reference. [ Note: As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type. — end note ]
In reference initialization:
Given types “cv1 T1” and “cv2 T2”, “cv1 T1” is reference-related to “cv2 T2” if T1 is the same type as T2, or T1 is a base class of T2. “cv1 T1” is reference-compatible with “cv2 T2” if
- T1 is reference-related to T2, or
- T2 is “noexcept function” and T1 is “function”, where the function types are otherwise the same,
...and later on there's some (personally ambiguous) language on user-defined conversions:
For example:
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 ... value result of the conversion
...
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”
...
then the value of 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”
...
Otherwise:
- 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 ... 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.
...
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.
These rules are quite nuanced and I cannot fully grasp each situation.
To me, it seems like a prvalue should be getting generated (I agree with clang), but the language on reference initialization, and interaction with list initialization is very fuzzy.
Let's read the standard in the correct order, so that we know which sections apply to the situation at hand.
[dcl.init]/17 says:
The semantics of initializers are as follows... If the initializer is a (non-parenthesized) braced-init-list or is = braced-init-list, the object or reference is list-initialized (11.6.4) ...
So we go to [dcl.init.list] (11.6.4). Paragraph 3 says:
List-initialization of an object or reference of type T is defined as follows: (... cases that don't apply are elided from this quotation...) Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E ... otherwise, if T is a reference type, a prvalue of the type referenced by T is generated. The prvalue
initializes its result object by copy-list-initialization or direct-list-initialization, depending on the kind of initialization for the reference. The prvalue is then used to direct-initialize the reference. [ Note: As
usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a
non-const type. —end note ]
According to [dcl.init.ref]/4:
Given types “cv1 T1” and “cv2 T2”, “cv1 T1” is reference-related to “cv2 T2” if T1 is the same type as T2, or T1 is a base class of T2.
Therefore, in your code, the referenced type int is not reference-related to the type in the initializer list, namely S. Thus, by [dcl.init.list]/3, a prvalue of type int is generated, and it takes the form int{s}. And as the note says, in the case of iref2, the program is ill-formed because it tries to bind a non-const lvalue reference to a prvalue. In the case of iref3, the program should compile since iref3 is being bound to the prvalue result int{s}.
Consider this code:
int **p = 0;
class S {
public:
explicit operator int**&() {
return p;
}
};
int main() {
S a;
int *const*&b (a); // error in both g++-7 and clang-5.0 with "-std=c++17"
return 0;
}
You will agree
a qualification conversion from int** to int*const* is possible, and
int *const*&b (a) is a direct-initialization.
First, we refer to 11.6.3, paragraph 5 [dcl.init.ref] from n4700.
A reference to type “cv1 T1 (= int*const*)” is initialized by an expression of type “cv2 T2 (= S)” 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 (16.3.1.6) and choosing the best one through overload resolution (16.3)),
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...
Here, we expect T3 to be int*const*. As noted above, whether it's a possible conversion is determined as per 16.3.1.6, paragraph 1 [over.match.ref].
... 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:
... For direct-initialization, those explicit conversion functions that
are not hidden within S and yield type “lvalue reference to cv2 T2” or “cv2 T2” or “rvalue reference to cv2 T2”, respectively, where T2 is the same type as T or can be converted to type T with a qualification conversion are also candidate functions.
Here, S::operator int**& yields "lvalue reference to T2 (= int**)", and it can be converted to T (= int*const*) by a qualification conversion. Here, we can say that the conversion is possible, but the program is not accepted in both g++-7 and clang-5.0. Why is that?
The reference initialization rule we're looking for is [dcl.init.ref]:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
We have cv1 T1 as int* const* and cv2 T2 as S. We then go through the next sections carefully:
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).
Our reference is an lvalue reference. The initializer expression is an lvalue but the two types are not reference-compatible, so the first bullet does not apply.
The initializer expression does have non-reference-related class type, but it cannot be converted to a reference-compatible type. The reference-compatible part is important. int** is not reference-compatible with int* const*, and while the former can be converted to the latter, the result would not be an lvalue - which is also required.
So, this section doesn't apply, and we move on.
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.
Our reference meets neither of those criteria, so the initialization is ill-formed.
A simpler version of this failure would be:
int* pi;
int const*& r = pi; // error
We can't go through a qualification conversion when we have an lvalue reference to non-const type.
I've encountered a problem with implicit conversion in C++. The following is a minimal example:
struct A {
virtual void f()=0; // abstract
};
struct Ad : A {
virtual void f() {} // not abstract
};
struct B {
operator Ad () const { return Ad(); }
};
void test(A const &lhs) {}
int main()
{
B b;
test(b);
}
What I would like the compiler to do is: convert b to a variable of type Ad (using the conversion defined in B) and pass the result to test. However, the above code does not compile in GCC (with C++11 enabled), the result being Cannot allocate an object of abstract type 'A'.
Some things to note:
Clang compiles this.
If you make A non-abstract by changing f()=0; to f() {}, the code works just fine.
The compiler does find the conversion operator (as indicated by 2), but it doesn't do what I'd like it to do.
(All quotes from N4140, the C++14 FD)
TL;DR: The code is well-formed, this is (or was) a GCC bug.
The rules for reference initialization are covered in [dcl.init.ref]/5. I'll first show you the bullet that doesn't cover it - if you want to skip that go straight to the third quote.
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.
If the initializer expression
is an xvalue (but not a bit-field), class prvalue, array prvalue 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 xvalue,
class prvalue, or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see 13.3.1.6),
then the
reference is bound to the value of the initializer expression in the
first case and to the result of the conversion in the second case (or,
in either case, to an appropriate base class subobject).
And reference-compability is defined in [dcl.init.ref]/41.
Now consider the linked 13.3.1.6:
Under the conditions specified in 8.5.3, a reference can be bound
directly to a glvalue or class prvalue that is 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 “cv1 T” is the underlying 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” [..],
where “cv1 T” is reference-compatible (8.5.3) with “cv2 T2”,
are candidate functions. For direct-initialization, [..].
As you can see, your conversion function isn't a candidate after this paragraph. Thus the next bullet in [dcl.init]/5 is applicable:
Otherwise:
If T1 is a class type, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1
T1” by user-defined conversion (8.5, 13.3.1.4); 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. The program is
ill-formed if the direct-initialization does not result in a direct
binding or if it involves a user-defined conversion.
Note that the phrase "the program is
ill-formed if the corresponding non-reference copy-initialization
would be ill-formed" may imply that as
B b;
A a = b;
is ill-formed, the program is ill-formed. I believe this to be a defect or vagueness in the wording though, and not the reason that GCC does not accept the code. Assuredly the wording solely aims at the initialization itself, not the fact that a most-derived object of type T1 (aka A) can be created in the first place.
Finally 13.3.1.4 accepts our conversion function:
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. [..]. 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.
Now the last question is whether in
A const& ref(Ad());
ref is bound directly. And it is.
Thus the original parameter reference binds directly, and no most-derived object of type A must be created.
What GCC presumably thinks is that a temporary of type A must be initialized and the reference be bound to that temporary. Or it pedantically follows the above defected wording, which is very unlikely.
1)
Given types “cv1 T1” and “cv2 T2,” “cv1 T1” is reference-related to
“cv2 T2” if T1 is the same type as T2, or T1 is a base class of T2.
“cv1 T1” is reference-compatible with “cv2 T2” if T1 is
reference-related to T2 and cv1 is the same cv-qualification as, or
greater cv-qualification than, cv2.