1

According to Herb Sutter's C++ Coding Standards: 101 Rules, Guidelines, and Best Practices programmer should avoid c-style casting:

C-style casts have different (and often dangerous) semantics depending on context, all disguised behind a single syntax. Replacing C-style casts with C++-style casts helps guard against unexpected errors

I am trying to pass pointer p_ctrl to WinAPI callback function, for which I want to use DWORD_PTR parameter of callback function (below example is working, but contains C-style casts where commented):

WndCtrls* WndCtrls::Button ( WndCtrls* const p_ctrl, HWND hwnd, RECT const &rc )
{
    p_ctrl->ctrl = CreateWindowEx (
        0,
        L"BUTTON",
        p_ctrl->w_classNameButton.c_str (),
        WS_VISIBLE | WS_CHILD | BS_OWNERDRAW,
        rc.left,
        rc.top,
        rc.right,
        rc.bottom,
        hwnd,
        0,
        (HINSTANCE)GetWindowLongPtr ( hwnd, GWL_HINSTANCE ), // Problematic C-style cast for which I already know workaround
        p_ctrl
    );
    SetWindowSubclass ( p_ctrl->ctrl, WndCtrls::CtrlProc, 0, (DWORD_PTR)p_ctrl ) ) // C-style cast

    return p_ctrl;
}

LRESULT CALLBACK WndCtrls::CtrlProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
    WndCtrls* const p_ctrl = (WndCtrls*)dwRefData; // Problematic C-style cast
    switch ( message )
    {
        ...
    }
    return DefSubclassProc ( hwnd, message, wParam, lParam );
}

I already tried dynamic cast, but that gives me errors. reinterpret_cast should not be used at all (according to Sutter).

Please is there a way to do those casts using c++ provided functions?

Xzsh4s575sf75
  • 83
  • 1
  • 11
  • 2
    `reinterpret_cast(p_ctrl)`, `reinterpret_cast(dwRefData)` – Igor Tandetnik Nov 12 '16 at 22:43
  • `WndCtrls* const p_ctrl = (WndCtrls*)dwRefData;` - this is absolute correct. no any problems here – RbMm Nov 12 '16 at 22:44
  • 2
    `reinterpret_cast` isn't deemed "never to be used", it's deemed "use only when necessary and when you understand what you're doing". – Jonathan Potter Nov 12 '16 at 23:54
  • @RbMm: That's a C-style cast with all the issues Herb Sutter addressed. For one, you never know what cast really is performed. Is it a (safe) `static_cast` or a fatally inappropriate `const_cast`? It depends on context that may or may not be visible in full. – IInspectable Nov 13 '16 at 12:52
  • @IInspectable - `static_cast` or `const_cast` here unrelated. need exactly `reinterpret_cast(p)` or `(T*)p`. and i on 100% agree with andlabs answer. in windows very frequently need exactly `reinterpret_cast` and advice from book is bad – RbMm Nov 13 '16 at 12:59
  • @RbMm: Which part of the book is wrong then, in your opinion? It argues, that C-style casts are dangerous, because the semantics depend on context, whereas the semantics of C++-style casts do not. That makes `reinterpret_cast` safer than `(WndCtrls*)` (as the latter depends on context, and could perform a fatal `const_cast` in addition to the `reinterpret_cast`). And that's precisely what the book explains. – IInspectable Nov 13 '16 at 13:07
  • @IInspectable - you want say that `reinterpret_cast(p)` can have different effect that ((T*)p) ? – RbMm Nov 13 '16 at 13:10
  • @RbMm: I said that, **twice** already. And I was very explicit about it (a **`const_cast`** in addition to a `reinterpret_cast`). Whether that is permissible or a fatal bug depends on context outside your control (or at least not visible at the cast site). – IInspectable Nov 13 '16 at 13:13
  • @IInspectable - in this case can you give an example where reinterpret_cast != C-style cast ? with const_cast we cannot convert ULONG_PTR <-> PVOID (error C2440: 'const_cast': cannot convert from 'NT::PVOID' to 'ULONG_PTR') – RbMm Nov 13 '16 at 13:16
  • @IInspectable - context here absolute clear and concrete. in many windows API used 2 params - callback function pointer and param (p) to this callback declared some times as PVOID and some times as DWORD_PTR. and we need convert this param to (MyClass*) say. how you do this ? i know only `((MyClass*)p)` or `reinterpret_cast(p)` variant. or look for WindowProc declaration( WPARAM and LPARAM - how thay used ) – RbMm Nov 13 '16 at 13:17
  • @RbMm: This is getting tedious, and I don't know how I can be any more explicit. Maybe you simply don't know C++ well enough, or haven't seen projects complex enough, to understand that context can change underneath your code. Maybe revisit this question in two years' time and see, if it makes sense to you by then. Today, I don't believe I could make you see the potential issue, I'm afraid. – IInspectable Nov 13 '16 at 13:21
  • @IInspectable - in my understand context here absolute concrete - convert `DWORD_PTR dwRefData` (or from analog api) to `WndCtrls*` and please give example where reinterpret_cast != C-style cast – RbMm Nov 13 '16 at 13:24
  • You're missing the part where IInspectable says `const_cast`. You can't use `reinterpret_cast` to cast away `const`, but you can with a C-style cast. Try it! I did it on OS X with `$ echo 'int i(const int *j) { return *reinterpret_cast(j); }' > const.cpp; clang++ const.cpp -o /dev/null` and it gives me `const.cpp:1:31: error: reinterpret_cast from 'const int *' to 'int *' casts away qualifiers`. I'm sure MSVC gives you a similar error (but I can't see what it is right now). Also not sure if this applies to any other qualifiers... – andlabs Nov 13 '16 at 16:58
  • @andlabs: MSVC issues error [C2440](https://msdn.microsoft.com/en-us/library/sy5tsf8z.aspx) for this conversion (`error C2440: 'reinterpret_cast': cannot convert from 'const int *' to 'int *'` `note: Conversion loses qualifiers`). This is true for all cv-qualifiers, i.e. `volatile` as well as `const`. – IInspectable Nov 13 '16 at 17:37

1 Answers1

5

Sutter's advice is just that: advice. They aren't hard and fast rules; they're just suggestions that encourage you to write safer, more robust code.

This is one of those cases where the advice doesn't work.

In many places, the Windows API needs to be able to pass data that may or may not be a pointer. As such, it uses a pointer-sized integer, using a C-style cast (remember that the Windows API is primarily C-based) to turn the integer into a pointer. This is safe because the documentation requires it to be: if you give Windows a garbage pointer value, you're breaking that function's rules!

The standard C/C++ names for the pointer-sized integers are intptr_t (signed) and uintptr_t (unsigned). However, Windows predates C99 and C++11 (when these were introduced), so it uses its own names: LONG_PTR (signed) and DWORD_PTR and ULONG_PTR (unsigned). In addition, WPARAM, LPARAM, and LRESULT are also pointer-sized, since window messages often need to deal with pointers.

So go ahead and use that C-style cast or that reinterpret_cast<>, whichever one you prefer. The other casts won't work because you need to interpret an integer as a pointer, which the other casts won't let you do.

You may need these anyway because there are other places that, because the Windows API needs to not only be in C but also be usable from other languages, subclassing is replaced by having an object of the struct to derive from as the first element of the dervied struct. This is most apparent in the WM_NOTIFY message, where all possible notification structs do this with NMHDR. Just keep this in mind.

andlabs
  • 11,290
  • 1
  • 31
  • 52
  • *"the Windows API is primarily C-based"* - Minor nitpick here: The entirety of the Windows API is based on C, and accessible from any programming language that can consume a C interface. Some of those interfaces are (**in addition**) accessible through C++, most notably COM interfaces, or GDI+. – IInspectable Nov 13 '16 at 13:00