microsoft / wil

Windows Implementation Library
MIT License
2.57k stars 234 forks source link

Feature request: when built with `/std:c++latest` or future `/std:c++23`, support `std::expected<>` for `wil::reg::try_*` #452

Open fredemmott opened 1 month ago

fredemmott commented 1 month ago

Something like std::expected<std::wstring, HRESULT> = wil::reg::try_get(...) would be a very handy API :)

citelao commented 2 weeks ago

Ooh, that's super cool! I didn't know about that API. @keith-horton, you might like this---it'd let us have safer nothrow variants!

Something like this, right?

auto value = wil::reg::try_get_dword(...);
if (value.has_value())
{
  std::cout << "value: " << *value << '\n';
}
else if (value.error() == E_FILE_NOT_FOUND)
{
  // ...
}
// ...

Edit: for clarity, I'm not sure I have the bandwidth to add this, and I'm not sure Keith does either. But I think it's a good suggestion :)

computerquip-work commented 1 week ago

At some point, I used this with Boost Outcome's result type, similar to:

template <typename T>
using hr_result = result<T, HRESULT>

// ...

class thing
{
private:
    thing() nothrow;

public:
    thing(const thing &) = delete;
    thing(thing &&) = default;
    thing &operator(const thing &) = delete;
    thing &operator(thing &&) = default;

    static hr_result<thing> create();
}

This pattern allows me to avoid exceptions without the need to have out parameters for results. Without a result type like this, it actually wouldn't be possible to make the default constructor private and two-phase construction ends up being a requirement or an HRESULT out parameter is required.

Then I would have macros specifically designed to check the errors, log them if they failed, and return on failure. Back when I did this, I did it with clang-cl so it ended up looking pretty dang decent:

hr_result<DWORD> do_something()
{    
    return TRYX(do_a_thing()) + TRYX(do_another_thing());
}

which ends up pretty close to how Rust looks with ? syntax, just more verbose.

The MSVC alternative would be:

hr_result<DWORD> do_something()
{    
    DWORD a{};
    DWORD b{};

    TRY(a, do_a_thing());
    TRY(b, do_another_thing());

    return a + b;
}

or

hr_result<DWORD> do_something()
{
    TRY(DWORD a, do_a_thing());
    TRY(DWORD b, do_another_thing());

    return a + b;
}

In future projects, I stopped doing this because the logging of errors on the Windows platform is convoluted to say the least and I needed boilerplate to handle that every time, boilerplate that WIL already handles. I also had to recreate/drag the various check macros around specifically meant for Windows result types that had to wrap around boost outcomes result macros. I really like this pattern but the setup was very inconvenient, especially for small codebases.

Just some food for thought.

citelao commented 1 week ago

Is this something that could be done generically across all of WIL at once? Eg a helper of some sort.

Not sure how you could automatically “forward” outparams though…