I work in places that abjure exceptions in most of the code. I have spent years writing both error-code and exception based error handling and building libraries for both environments. With some simple building blocks error-codes and exceptions can be joined such that each library can be written agnostic to exceptions and allow each caller of the library to choose whether to throw or pass error codes. These also implement certain exception features; such as requiring explicit checking of error codes.

One building block is unique_error.

unique_error serves much the same function as the std::system_error but with additional features and eschewing some things that blocked my adoption of std::system_error. unique_error does not require that all the error values of a type be redefined. It uses traits discovered by ADL to specify the behavior. It terminates the program if an error is set and then not checked. unique_error is also similar to unique_ptr in that it treats an error like a resource.

Here is the complete definition of unique_winerror:


namespace unique_winerror_def
{
struct tag {};

// found by ADL via the tag&& parameter
inline bool unique_error_ok(DWORD winerror, tag &&)
{
return winerror == ERROR_SUCCESS;
}

// found by ADL via the tag&& parameter
inline DWORD unique_error_default(tag &&)
{
return ERROR_SUCCESS;
}
}

//
// implementations of these must be included
// in the final module
//
void unique_error_report_initiated(
DWORD value,
unique_winerror_def::tag &&);
void unique_error_report_reset(
DWORD value,
unique_winerror_def::tag &&);

typedef unique_error<unique_winerror_def::tag>
unique_winerror;

inline unique_winerror make_winerror_if(BOOL is_last_error)
{
unique_winerror result;
if (is_last_error) {
return std::move(result.reset(GetLastError()));
}
return std::move(result);
}

inline
unique_winerror::static_error winerror_cast(DWORD raw)
{
return unique_winerror::cast(raw);
}

Using unique_winerror

CreateEvent example

Starting from a previous post we can now improve the code further:

namespace Win32
{
template<typename Resource, Resource InvalidValue>
std::pair<unique_winerror, Resource>
WinerrorAndResource(Resource resource)
{
return std::make_pair(
make_winerror_if(Resource == InvalidValue),
Resource);
}

namespace Event
{
inline auto EventAndWinerror(
HANDLE apiResult) -> decltype(
WinerrorAndResource<HANDLE, nullptr>(
apiResult))
{
return WinerrorAndResource<HANDLE, nullptr>(
apiResult);
}
}
}

// now you can write this
namespace we = Win32::Event;
unique_winerror winerror;
HANDLE event = nullptr;

std::tie(
winerror,
event) = we::EventAndWinerror(
CreateEvent(
nullptr
make(we::Reset::Manual),
make(we::Value::Reset),
nullptr));
if (!winerror)
{
return;
}

LoadString example

The complexities of LoadString for arbitrary sized strings is encapsulated here:

inline
unique_winerror
LoadStringRaw(
HINSTANCE instance,
UINT id,
const range<WCHAR*>& space,
range<WCHAR*>* spaceUsed,
size_t* spaceRequested)
{
if (spaceUsed) {
*spaceUsed = space;
spaceUsed->advance_end(-space.size());
}

if (spaceRequested) {
*spaceRequested = space.size();
}

SetLastError(ERROR_SUCCESS);

int loadResult = LoadStringW(
instance,
id,
space.begin(),
space.size());

auto winerror = make_winerror_if(TRUE);

if (loadResult >= 0) {
if ((loadResult + 1) < space.size()) {
if (winerror.ok()) {
*spaceUsed = space;
spaceUsed->advance_end(
loadResult - space.size());
}
} else {
*spaceRequested = std::max<size_t>(
3,
static_cast<size_t>(space.size() * 1.5));

if (winerror.ok()) {
winerror = winerror_cast(ERROR_MORE_DATA);
}
}
} else {
if (winerror.ok()) {
winerror = winerror_cast(ERROR_UNIDENTIFIED_ERROR);
}
}

return winerror;
}

inline
std::wstring
LoadStdString(
HINSTANCE instance,
UINT id,
size_t sizeLimit = 2048,
size_t initialSize = 80)
{
unique_winerror winerror;
std::wstring result;
RANGE_NAMESPACE::range<WCHAR*> spaceUsed;
size_t spaceRequested = initialSize;

while (spaceRequested < sizeLimit) {
result.resize(spaceRequested);
winerror = LoadStringRaw(
instance,
id,
make_range_raw(result),
&spaceUsed,
&spaceRequested);
if (winerror != winerror_cast(ERROR_MORE_DATA)) {
winerror.throw_if();
break;
}
winerror.suppress();
continue;
}

return std::move(result);
}

std::wstring title;

title = LoadStdString(
hInstance,
IDS_APP_TITLE);

Implementation

Maintained here.

template<typename ErrorTag>
class unique_error
{
public:
typedef unique_error
this_type;

typedef ErrorTag
tag;

typedef decltype(unique_error_default(tag()))
type;

class static_error
{
private:
type value;
};

static static_error cast(type raw);
static this_type make(type raw);
static this_type make(const static_error& other);

~unique_error();

unique_error();
unique_error(static_error other);
unique_error(unique_error && other);
unique_error(const unique_error& other);

void swap(unique_error& other);
unique_error& operator=(unique_error other);

operator
typename unspecified_bool<this_type>::type() const;

this_type& reset();
this_type& reset(type raw);
type release();

type get() const;
bool try_ok() const;
bool ok() const;

const this_type& suppress() const;
this_type& suppress();

void throw_if(const std::string& message) const;
void throw_if(const char* message = NULL) const;

private:
type value;
mutable Disposition::type disposition;
};