Home > Software engineering >  How can both CRect and CRect* be input arguments to GetClientRect?
How can both CRect and CRect* be input arguments to GetClientRect?

Time:09-16

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
  • 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.

  • Related