Beginner question.
In Win32API, the second parameter of ::GetClientRect
is LPRECT
, but It receives both CRect
and CRect*
.
I tried to see how LPRECT
is defined, but it seems just an ordinary pointer to struct:
typedef struct tagRECT
{
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;
And CRect
inherits tagRECT
:
class CRect :public tagRECT {...}
Using MFC, CWnd::GetClientRect
works the same way:
CRect rect;
GetClientRect(&rect); // works
GetClientRect(rect); // works too.
How is this possible? Please let me know if I am missing some basic concepts of C .
CodePudding user response:
During overload resolution1 the compiler builds a set of candidate functions that match the arguments. If none of the functions' signatures exactly match the arguments the compiler will then try to apply implicit conversions, at most one per argument.
That's what makes both calls possible, though the implicit conversion is different in either case:
GetClientRect(&rect);
In this function call expression, &rect
has type CRect*
, with CRect
publicly deriving from RECT
, so an implicit pointer conversion from CRect*
to RECT*
(aka LPRECT
) is done.
GetClientRect(rect);
Here, on the other hand, rect
has type CRect
. C doesn't provide any built-in conversions from CRect
to RECT*
, so user-defined conversions are considered. Specifically, there's a conversion operator CRect::operator LPRECT
that produces a RECT*
pointer given a CRect
object.
It is of crucial importance to appreciate, that both function call expressions do different things. In the second case the compiler literally injects a call to operator LPRECT()
prior to calling GetClientRect()
. There's nothing in the language that mandates that the conversion operator must behave any given way, and it's down to library authors to provide a sane implementation.
In this case, operator LPRECT()
behaves as one would expect, and both function calls have the same observable behavior. Now that you have two ways to do the same thing, guidance is required to make a judicious choice:
While there are no strict rules on which of the options to use, it's down to opinion. I would recommend GetClientRect(&rect)
for a few reasons:
- Easier to understand for readers of the code
- Pointer arguments are frequently used as
[out]
parameters where implementations write their results - Less ambiguous:
GetClientRect(rect)
looks like pass-by-value, or maybe the object is bound to a reference. In reality, neither one it is, and the (invisible) conversion operator is called instead
- Pointer arguments are frequently used as
- No more expensive than the version that uses the conversion operator
1 Overload resolution is a core language feature. The set of rules driving this part of the language, however, make it anything but basic. It's something you'll eventually have to learn, but it's a very steep learning curve. The cppreference.com link gives you a glimpse at the complexity involved.