How do conversion operators work in C++?

50,576

Solution 1

Some random situations where conversion functions are used and not used follow.

First, note that conversion functions are never used to convert to the same class type or to a base class type.

Conversion during argument passing

Conversion during argument passing will use the rules for copy initialization. These rules just consider any conversion function, disregarding of whether converting to a reference or not.

struct B { };
struct A {
  operator B() { return B(); }
};
void f(B);
int main() { f(A()); } // called!

Argument passing is just one context of copy initialization. Another is the "pure" form using the copy initialization syntax

B b = A(); // called!

Conversion to reference

In the conditional operator, conversion to a reference type is possible, if the type converted to is an lvalue.

struct B { };
struct A {
  operator B&() { static B b; return b; }
};

int main() { B b; 0 ? b : A(); } // called!

Another conversion to reference is when you bind a reference, directly

struct B { };
struct A { 
  operator B&() { static B b; return b; }
};

B &b = A(); // called!

Conversion to function pointers

You may have a conversion function to a function pointer or reference, and when a call is made, then it might be used.

typedef void (*fPtr)(int);

void foo(int a);
struct test {
  operator fPtr() { return foo; }
};

int main() {
  test t; t(10); // called!
}

This thing can actually become quite useful sometimes.

Conversion to non class types

The implicit conversions that happen always and everywhere can use user defined conversions too. You may define a conversion function that returns a boolean value

struct test {
  operator bool() { return true; }
};

int main() {
  test t;
  if(t) { ... }
}

(The conversion to bool in this case can be made safer by the safe-bool idiom, to forbid conversions to other integer types.) The conversions are triggered anywhere where a built-in operator expects a certain type. Conversions may get into the way, though.

struct test {
  void operator[](unsigned int) { }
  operator char *() { static char c; return &c; }
};

int main() {
  test t; t[0]; // ambiguous
}

// (t).operator[] (unsigned int) : member
// operator[](T *, std::ptrdiff_t) : built-in

The call can be ambiguous, because for the member, the second parameter needs a conversion, and for the built-in operator, the first needs a user defined conversion. The other two parameters match perfectly respectively. The call can be non-ambiguous in some cases (ptrdiff_t needs be different from int then).

Conversion function template

Templates allow some nice things, but better be very cautious about them. The following makes a type convertible to any pointer type (member pointers aren't seen as "pointer types").

struct test {
  template<typename T>
  operator T*() { return 0; }
};

void *pv = test();
bool *pb = test();

Solution 2

The "." operator is not overloadable in C++. And whenever you say x.y, no conversion will automatically be be performed on x.

Solution 3

Conversions aren't magic. Just because A has a conversion to B and B has a foo method doesn't mean that a.foo() will call B::foo().

The compiler tries to use a conversion in four situations

  1. You explicitly cast a variable to another type
  2. You pass the variable as an argument to a function that expects a different type in that position (operators count as functions here)
  3. You assign the variable to a variable of a different type
  4. You use the variable copy-construct or initialize a variable of a different type

There are three types of conversions, other than those involved with inheritance

  1. Built-in conversions (e.g. int-to-double)
  2. Implicit construction, where class B defines a constructor taking a single argument of type A, and does not mark it with the "explicit" keyword
  3. User-defined conversion operators, where class A defines an operator B (as in your example)

How the compiler decides which type of conversion to use and when (especially when there are multiple choices) is pretty involved, and I'd do a bad job of trying to condense it into an answer on SO. Section 12.3 of the C++ standard discusses implicit construction and user-defined conversion operators.

(There may be some conversion situations or methods that I haven't thought of, so please comment or edit them if you see something missing)

Solution 4

Implicit conversion (whether by conversion operators or non-explicit constructors) occurs when passing parameters to functions (including overloaded and default operators for classes). In addition to this, there are some implicit conversions performed on arithmetic types (so adding a char and a long results in the addition of two longs, with a long result).

Implicit conversion does not apply to the object on which a member function call is made: for the purposes of implicit conversion, "this" is not a function parameter.

Solution 5

The compiler will attempt one(!) user-defined cast (implicit ctor or cast operator) if you try to use an object (reference) of type T where U is required.

The . operator, however, will always try to access a member of the object (reference) on its left side. That's just the way it's defined. If you want something more fancy, that's what operator->() can be overloaded for.

Share:
50,576

Related videos on Youtube

Khaled Alshaya
Author by

Khaled Alshaya

I am member of the team responsible for the design, implementation and maintenance of the financial systems of the largest energy company in the world! If I could allocate some spare time, I spend it mostly on learning new technologies. C++ was the first programming language I learned and I still love using it for personal projects.

Updated on September 15, 2020

Comments

  • Khaled Alshaya
    Khaled Alshaya almost 4 years

    Consider this simple example:

    template <class Type>
    class smartref {
    public:
        smartref() : data(new Type) { }
        operator Type&(){ return *data; }
    private:
        Type* data;
    };
    
    class person {
    public:
        void think() { std::cout << "I am thinking"; }
    };
    
    int main() {
        smartref<person> p;
        p.think(); // why does not the compiler try substituting Type&?
    }
    

    How do conversion operators work in C++? (i.e) when does the compiler try substituting the type defined after the conversion operator?

  • Pavel Minaev
    Pavel Minaev almost 15 years
    It should be noted however that operator-> can only be defined as a member function, and therefore no conversions (user-defined or otherwise) will be applicable to the expression on the right side of ->.
  • Admin
    Admin almost 15 years
    Actually, you might notice that nowhere does litb's code use the "." operator. And so I don't think it addresses your original question.
  • Johannes Schaub - litb
    Johannes Schaub - litb almost 15 years
    @Neil his original question was "How do conversion operators work in C++? (i.e) when does the compiler try substituting the type defined after the conversion operator?".
  • Admin
    Admin almost 15 years
    From the original code: p.think(); // why does not the compiler try substituting Type&? Which is surely asking why p (the thing before the dot) is not converted.
  • Johannes Schaub - litb
    Johannes Schaub - litb almost 15 years
    @AraK, as Neil says, there is no conversion applied when you do "x.y" - by design. Of course, allowing a conversion like that would have problems, which include the question whether to access smartref or person's members. But my answer did not focus on this "x.y" problem - i focused on showing other cases when the compiler uses conversion functions, because that's how i understood your main question.
  • Khaled Alshaya
    Khaled Alshaya almost 15 years
    @Niel, thanks for clarifying my code. I choose litb answer because it addresses my general question.
  • Steve Jessop
    Steve Jessop almost 15 years
    Might be worth noting under "non class types" that conversion to bool is fraught with danger (I mean, more so than most conversions), because it gives you a surprising conversion to any arithmetic type for free. If all you want is to make your object usable as a condition expression, provide a conversion to void*, like ios does. It's safer because there's nowhere to chain from void*.
  • sbi
    sbi almost 15 years
    Yes, I know. But what he wanted (smart reference) is a sibling to the smart pointer which cannot be done in C++, since the . operator cannot be overloaded. That's why I pointed out operator->. I guess I wasn't very clear, though.
  • Johannes Schaub - litb
    Johannes Schaub - litb almost 15 years
    @onebyone, oh how could i forget to mention that! When i wrote it, i though "oh, but append later that it's dangerous" but i silly forgot that. Thanks for mentioning!
  • Johannes Schaub - litb
    Johannes Schaub - litb almost 15 years
    @Neil, also note that the "x.y" case was just a "simple example". It seemed to me that it wasn't his original problem that caused him to ask. So i thought i wouldn't just repeat what others already did - but rather focus on the actual question.
  • Admin
    Admin almost 15 years
    @litb Unfortunately, the title of SO questions rarely matches the body text. But if someone posts some code (as they should, IMHO) I find it best to focus my answer on the code. Yours is certainly a very good answer (as usual) to the title question.
  • Johannes Schaub - litb
    Johannes Schaub - litb almost 15 years
    @Neil, i'm glad we agree that both answers are good in their own rights. Thanks for the praise - sure i like your answers too :) Have fun
  • Alex Chamberlain
    Alex Chamberlain over 11 years
    This could be improved with C++ casts.
  • underscore_d
    underscore_d over 3 years
    This is an answer with no explanation and doesn't actually address the question, which is how conversion operators work and whether they can be used to invoke member functions of the converted type. Inheriting from a class is far from being the same as converting to it, and OT.
  • Aaron Franke
    Aaron Franke almost 3 years
    How do I create a conversion operator from an enum class type to a boolean type?