Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
fredemmott opened this issue Jul 23, 2024 · 4 comments
Labels
feature-request New feature or request

Comments

@fredemmott
Copy link

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

@dunhor dunhor added the feature-request New feature or request label Jul 25, 2024
@citelao
Copy link
Contributor

citelao commented Aug 23, 2024

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
Copy link

computerquip-work commented Aug 30, 2024

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
Copy link
Contributor

citelao commented Sep 2, 2024

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…

@ijprest
Copy link

ijprest commented Dec 31, 2024

I used this pattern extensively at a previous workplace... it's very natural for replacing 'out' parameters and combining with an error code, and would be a natural fit for using HRESULTs in new / 'modern' C++ code.

I'd envision a helper typedef, something like this:

namespace wil {
template <typename T = void>
using hresult = std::expected<T, HRESULT>;
}

Then, used in an API like so:

wil::hresult<> foo();       // returns 'void' on success; HRESULT on failure
wil::hresult<thing> bar();  // returns a thing on success; HRESULT on failure

Calling functions and checking errors:

// Checking for success is very natural!
if(auto ret = bar()) {
  // Success: Use value in 'ret'... kinda like a smart-pointer
  ret->some_function_on_thing(); // operator-> digs into the return value
  auto my_thing = *ret; // deref to get at the value; alternatively `ret.value()`
} else {
  // Failure: Use error code in `ret.error()`
  HRESULT hr = ret.error();
}

Returning a 'void' on success and HRESULT on failure (i.e., returning wil::hresult<>) is roughly equivalent to just returning a straight HRESULT... but checking for success/failure is more natural... success is true and failure is false! No SUCCEEDED/FAILED macros required. (I think some versions of C++ will also allow you to elide the empty angle brackets <>... not sure if that's C++20 or later.)

Want some result_macros.h-style macros?

#define RETURN_IF_UNEXPECTED(ret) \
do {\
   const auto __errRet = (ret); \
   if(!ret) { \
     return std::unexpected{ret.error()}; \
   } \
}

auto ret = bar();
RETURN_IF_UNEXPECTED(ret);
// Success if we made it here; use value in 'ret'
ret->some_function_on_thing();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants