How do conversion operators work in C++?

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();

Leave a Comment