15

After extensive reading of ISO/IEC 14882, Programming language – C++ I'm still unsure why const is needed for implicit conversion to a user-defined type with a single argument constructor like the following

#include <iostream>

class X {
public:
   X( int value ) {
      printf("constructor initialized with %i",value);
   }
}

void implicit_conversion_func( const X& value ) {
   //produces "constructor initialized with 99"
}

int main (int argc, char * const argv[]) {
   implicit_conversion_func(99);
}



Starting with section 4 line 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). Certain language constructs require that an expression be converted to a Boolean value. An expression e appearing in such a context is said to be contextually converted to bool and is well-formed if and only if the declaration bool t(e); is well-formed, for some invented temporary variable t (8.5). The effect of either implicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The result is an lvalue if T is an lvalue reference type (8.3.2), and an rvalue otherwise. The expression e is used as an lvalue if and only if the initialization uses it as an lvalue.

Following that I found the section on initializers related to user-defined types in 8.5 line 6

If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.

Finally I ended up at 12.3 line 2 about user-defined conversions which states

User-defined conversions are applied only where they are unambiguous (10.2, 12.3.2).

Needless to say, 10.2 and 12.3.2 didn't answer my question.

  1. Can someone shed some light on what effect const has on implicit conversions?
  2. Does the use of const make the conversion "unambiguous" per 12.3 line 2?
  3. Does const somehow affect lvalue vs. rvalue talked about in section 4?
curiousguy
  • 8,038
  • 2
  • 40
  • 58
Brandon Cook
  • 1,362
  • 11
  • 11

2 Answers2

15

It doesn't really have much to do with the conversion being implicit. Moreover, it doesn't really have much to do with conversions. It is really about rvalues vs. lvalues.

When you convert 99 to type X, the result is an rvalue. In C++ results of conversions are always rvalues (unless you convert to reference type). It is illegal in C++ to attach non-const references to rvalues.

For example, this code will not compile

X& r = X(99); // ERROR

because it attempts to attach a non-const reference to an rvalue. On the other hand, this code is fine

const X& cr = X(99); // OK

because it is perfectly OK to attach a const reference to an rvalue.

The same thing happens in your code as well. The fact that it involves an implicit conversion is kinda beside the point. You can replace implicit conversion with an explicit one

implicit_conversion_func(X(99));

and end up with the same situation: with const it compiles, without const it doesn't.

Again, the only role the conversion (explicit or implicit) plays here is that it helps us to produce an rvalue. In general, you can produce an rvalue in some other way and run into the same issue

int &ir = 3 + 2; // ERROR
const int &cir = 3 + 2; // OK
AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • "_When you convert 99 to type X, the result is an rvalue._" No. **A rvalue is an expression.** Converting `99` to `X` results in a temporary object. – curiousguy Dec 23 '11 at 06:22
  • @curiousguy: While the concept of "rvalue" is firmly attached to expression, the term "rvalue" can be used to refer to the expression itself as well as to the result it yields. The language standard actually uses the concept of "rvalue result" a lot more often than of "rvalue expression". (Obviously, both are actually intended to refer to the same thing). Moreover, the language specification states clearly and explicitly in 5.2.3 that the result of functional-style case is an rvalue. Period. The fact that it also happens to be "a temporary object" is hardly relevant here. – AnT stands with Russia Dec 24 '11 at 20:27
  • Could you please explain what "rvalue" means? – curiousguy Dec 25 '11 at 22:09
  • "_unless you convert to reference type_" do you mean: if you cast to a reference type? – curiousguy Dec 25 '11 at 22:14
  • @curious Converting `99` to type `X` is an rvalue in the sense of clause 4, paragraph 3: "The result is an lvalue if T is an lvalue reference type or an rvalue reference to function type (8.3.2), an xvalue if T is an rvalue reference to object type, and a prvalue otherwise.". It is true that an rvalue is an expression. But expressions are not always source-code. An expression can be synthesized by the language semantics. – Johannes Schaub - litb Dec 27 '11 at 23:38
  • For example: `int a[2]; int *p = a;`. Here, `a` is both an lvalue and an rvalue, depending on whether you include the implicit array-to-pointer conversion into consideration. The result of that conversion is an rvalue expression. Deeper down to the operand of that conversion, the `a` is an lvalue expression. – Johannes Schaub - litb Dec 27 '11 at 23:39
  • @JohannesSchaub-litb "_An expression can be synthesized by the language semantics._" according to what? "_depending on whether you include the implicit array-to-pointer conversion into consideration_" I don't feel well about that. The standard should say something about this, and it doesn't. – curiousguy Feb 18 '12 at 04:43
  • @AndreyT "_While the concept of "rvalue" is firmly attached to expression, the term "rvalue" can be used to refer to the expression itself as well as to the result it yields._" Yes indeed. The more I try to think about it to clear things up, the more I feel confused. – curiousguy Feb 18 '12 at 05:01
  • "_Here, a is both an lvalue and an rvalue,_" Here, `1` has both type `int`, `long`, `double`, etc. Here, `""` has both type `const char*`, `std::string`, etc. – curiousguy Feb 18 '12 at 05:56
  • @curious correct about `1` being a double, if you use it in a way like `double d = 1;`. You cannot lexically express the conversion, so we say `1` and it looks like nonsense. But we could say `integral_floating_conversion_to_double(1)` to represent the conversion lexically, and then it looks sensible. – Johannes Schaub - litb Feb 18 '12 at 11:35
  • @JohannesSchaub-litb "_then it looks sensible_" and pretty and not verbose at all. ;) – curiousguy Feb 18 '12 at 17:37
0

Per section 5.2.2 paragraph 5, when an argument to a function is of const reference type, a temporary variable is automatically introduced if needed. In your example, the rvalue result of X(99) has to be put into a temporary variable so that that variable can be passed by const reference to implicit_conversion_func.

Jeffrey Hantin
  • 35,734
  • 7
  • 75
  • 94
  • "_the rvalue 99 has to be put into a temporary variable so that that variable can be passed by (const) reference to the constructor of X_" No. The parameter of the constructor is passed by value; there is no constant reference to `int`, and no temporary with the value 99. "_The semantics of creating a non-const temporary there would in any case be rather confusing_" What about `std::string() = "Hello, world"`? – curiousguy Dec 23 '11 at 06:28
  • @curiousguy Good point -- I think I misread the OP. The temporary is a `const X` initialized by constructing with `99` as argument, not a `const int`. The gist of it remains, though: a `const` temporary is created to satisfy the `const &` argument. – Jeffrey Hantin Dec 27 '11 at 23:22