0

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?

mada
  • 1,646
  • 1
  • 15

1 Answers1

1

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.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • `As far as I understood, I can say that when initializing an "lvalue reference to cv1 T" ...` Is my understanding correct? ` – mada Aug 18 '22 at 17:46
  • @Morise Yes exactly. – Jason Aug 18 '22 at 17:47
  • Also if you can, I need an explanation for that section: "_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..._" – mada Aug 18 '22 at 17:48
  • @Morise It means that say if there were two conversion functions available like for [example](https://godbolt.org/z/Y5csEKe1r): `struct C {operator int&(); operator double&();};` then the set `R` would only contain the type corresponding to the first conversion operator `C::operator int&()` and not corresponding to the second conversion function `C::operator double&()`. – Jason Aug 18 '22 at 17:58
  • "_then the set R would only contain the type corresponding to the first conversion operator C::operator int&() and not corresponding to the second conversion function C::operator double&()_" That's because the referred-to type of the reference being initialized `cv1 T = const int` is reference-compatible with the result of applying the conversion function `cv2 T2 = int`, and it is not reference-compatible with `cv2 T2 = double` Correct reason? – mada Aug 19 '22 at 04:07
  • And I have a last question - In this initialization "const int& ref= C();" Does `ref` is bind _directly_ to the result of applying conversion function, or does it binds to a temporary? I think it binds directly because the initializer expression results in an lvalue reference binds directly to an lvalue, therefore, `ref` binds also directly to that lvalue. Right? – mada Aug 19 '22 at 04:36
  • @Morise Yes, `ref` binds to the `lvalue` and not to a temporary in the example 1 given in my answer. And yes, your previous comment is also correct. That is, you've understood correctly. – Jason Aug 19 '22 at 04:42