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();
C++ conversion operators
In these two lines you are telling the compiler to construct an A object with the default constructor, then call its nonexistent operator () (int) and return its return value:
A c ;
return c(this->i+b.i);
Use either
A c(i + b.i);
return c;
or
return A(i + b.i);
On a side note, an example for an implicit conversion operator, for your class:
operator int () const
{
return i;
}
But their use smells like bad design, and can cause bad stuff to happen, like implicit conversion to bool or a pointer. Use something else instead, like an int toInt () const
member function.
C++ type conversion operator
You forgot the const
on the double
conversion operator:
operator double() const { // <---------------------------
cout << "operator double() called" << endl;
return this->c;
}
};
As in your example a
is not const
, the double conversion is the best match. If you fix that you get the expected output.
Live example
...some opinion based PS:
I didnt find what the core guidelines say about conversion operators, but if I had to make up a guideline for conversion operators it would be: Avoid them. If you use them, make them explicit
. The surprising effects of implicit conversion outweigh the benefits by far.
Just as an example, consider std::bitset
. Instead of offering conversion operators it has to_string
, to_ulong
and to_ullong
. It is better to have your code explicit. A a; double d = a;
is a little bit mysterious. I would have to look at the class definition to get an idea of what is really going on. On the other hand A a; double d = a.as_double();
can do the exact same thing, but is way more expressive.
Overloaded function and multiple conversion operators ambiguity in C++, compilers disagree
GCC and Clang are correct. The implicit conversion sequences (user-defined conversion sequences) are indistinguishable.
[over.ics.rank]/3:
(emphasis mine)
Two implicit conversion sequences of the same form are
indistinguishable conversion sequences unless one of the following
rules applies:...
(3.3) User-defined conversion sequence U1 is a better conversion sequence
than another user-defined conversion sequence U2 if they contain the
same user-defined conversion function or constructor or they
initialize the same class in an aggregate initialization and in either
case the second standard conversion sequence of U1 is better than the
second standard conversion sequence of U2.
The user-defined conversion sequences involves two different user-defined conversion functions (operator double()
and operator long long int()
), so compilers can't select one; the 2nd standard conversion sequence won't be considered.
C++ conversion operator overload
int z = a;
Looks innocuous, right?
Well, the above line calls the implicit bool-conversion-operator, because that's the only way you left it to get from Person
to int
, and that only needs one user-defined conversion (the maximum allowed).
This is the reason for the safe-bool-idiom before C++11, and the major reason for the general advice to mark conversion operators and single-argument ctors explicit
unless they are not transformative nor lossy.
If you want to see it for yourself, fix your code and trace invocation of your operator bool
.
Why can't I overload C++ conversion operators outside a class, as a non-member function?
Besides having non-explicit conversion operators e.g.
operator bool()
in a class, you can also have non-explicit constructors taking a single argument, in the class you are converting to, as a way of introducing a user-defined conversion. (Not mentioned in question)As to why you cannot introduce user-defined conversions between two types
A
andB
without modifying their definitions... well this would create chaos.If you can do this, then you can do it in a header file, and since introducing new user-defined conversions can change the meaning of code, it would mean that "old" code using only
A
andB
could totally change what it is doing depending on if your header is then included before it, or something like this.It's already hard enough to figure out exactly what user-defined conversion sequences are taking place when things are going wrong, even with the restriction that the conversions have to be declared by one of the two types. If you literally have to search every single unrelated header file in full potentially to find these conversion function definitions it dramatically worsens the maintenance problem, and there doesn't appear to be any benefit to allowing this. I mean can you give a non-contrived example where this language feature would help you make the implementation of something much simpler or easier to read?
In general, I think programmers like the idea that to figure out what a line
a = b;
does, they just have to read the definition of the type ofa
and the type ofb
and go from there... it's potentially ugly and painful if you start allowing these "gotcha" conversions that are harder to know about.I guess you could say the same thing with regards to
operator <<
being used for streaming... but with user-defined conversions its more serious since it can potentially affect any line of code where an object of that type is being passed as a parameter.
Also, I don't think you should necessarily expect to find a well-thought out reason, not everything that is feasible for compilers to implement is permitted by the standard. Committee tends to be conservative and seek consensus, so "no one really cared about feature X enough to fight for it" is probably as good an explanation as you will find about why feature X is not available.
Why is initialization of a constant dependent type in a template parameter list disallowed by the standard?
Answer to that question suggests a common reason for a feature not being available:
- Legacy: the feature was left out in the first place and now we've built a lot without it that it's almost forgotten (see partial function template specialization).
Related Topics
In Stl Maps, Is It Better to Use Map::Insert Than []
What Is the 'Override' Keyword in C++ Used For
What's the Difference Between Size_T and Int in C++
How to Initialise Memory With New Operator in C++
Advantages of Using Std::Make_Unique Over New Operator
Mixing Cout and Wcout in Same Program
Difference in Floating Point Arithmetics Between X86 and X64
Throw Keyword in Function'S Signature
How to Remove an Item from a Stl Vector With a Certain Value
Static_Assert Fails Compilation Even Though Template Function Is Called Nowhere
Foreach Macro on Macros Arguments
How to Start a Cuda App in Visual Studio 2010
How to Read a Value from the Windows Registry
How to Concatenate Two Strings in C++