Today I saw that when trying to dereference an argument in a function call, the implicit conversion happens before the actual dereference operation (I believe only if the original type does not support dereference).
This observation can be seen here:
struct A{};
struct C
{
C(const A&)
{
}
};
C operator*(const C&);
double f(C);
template <typename T>
struct t
{
static const bool value = sizeof(f(**static_cast<T*>(NULL))) == sizeof(double);
};
int main(int argc, const char * argv[]) {
t<A>::value;
}
Does the C++ standard explicitly mention this behaviour? Thank you.
Let's take a look at this expression:
f(**static_cast<A*>(NULL))
From inner-most to outer-most, static_cast<A*>(NULL)
is an A*
(though prefer nullptr
to NULL
, and also prefer using std::declval<A*>()
to get "something of type A*
" rather than the old casting of null pointer approach).
Next, *(a prvalue of type A*)
gives you an lvalue of type A
. That's just what pointer dereference means and it isn't overloadable. It's nice when things are easy to reason about.
Next, *(an lvalue of type A)
. In order to figure out what that means, we transform the call to function-notation, and our set of candidates are:
A::operator*()
operator*(a)
, doing unqualified lookup on operator*()
in the context of the expression.The first bullet doesn't find anything, there is no A::operator*()
. The second bullet, unqualified lookup on operator*()
will find the function C operator*(const C&);
because it is in scope. It is a viable candidate, because A
is convertible to C
via C(A const&)
. The third bullet has no viable candidate.
As we only have one viable candidate, it's trivially the best viable candidate - so *(lvalue of type A)
gives us a prvalue of type C
.
To specifically answer your question:
the implicit conversion happens before the actual dereference operation
Yes, the conversion has to happen in order to resolve the dereference operation. Although it's not actually a "dereference", it's just a unary operator*()
.
Lastly, f(prvalue of type C)
invokes the one f
we have that gives us double
.
Note that in modern C++, I'd suggest writing the check as something closer to:
template <typename T>
struct t
: std::is_same<
decltype(f(*std::declval<T>())), // <== the type we get when we call f
// on a dereferenced T
double>
{ };