Replies: 2 comments
-
Hey, sorry taking a few days to get to this. Thank you for the feedback! Are you able to describe the type of situation you're currently working in where this would be needed? Is there any way to perform indirection to make the error type lightweight? Getting a reference to the underlying operator has been requested before -- I'm not sure if you saw this already (#5). In most practical cases I've worked with, errors can be represented by easy enum types, which make this simple. In the more complex cases, such as distributing things like stack-traces, contextual information, etc, this could be done by throwing it into a structure pointed to by a For example: struct large_error_context {
public:
large_error_context(); // default constructs: assumes no error
large_error_context(...); // creates with error
private:
struct info { // some struct of large data
stack_trace trace;
source_location location;
other_large_data data;
};
std::unique_ptr<info> m_info;
};
Can you point to some (other) specific lines where this creates an issue? Your example of |
Beta Was this translation helpful? Give feedback.
-
I just realized that in my post the dummy implementations are returning
No problem :)
Sure. A bunch of my code is littered with the following pattern: enum class Error { /* ... */ };
struct T {
// fields omitted.
};
struct U {
T t;
// other fields omitted.
};
result<T, Error> f();
result<U, Error> g() {
auto r = f();
if (r.has_error()) {
// propagate error.
return cpp::fail(std::move(r).error());
// calling error() does a check even though I've already done it.
}
T& t = *r; // I can call operator* because I already checked before.
// do things with t.
} It would be nice if I could replace return cpp::fail(std::move(r).error()); with something like return cpp::fail(std::move(r).error_unchecked()); Another issue I found with struct Error {
void* context; // doesn't need to be void, just some pointer to borrowed memory.
Error() = delete; // Prevent the construction of errors with nullptr.
Error(void* context) noexcept : context(context) {}
};
// Assume the existence of T, U and f() from the previous example.
result<T, Error> g() {
auto r = f();
if (r.has_error()) {
// propagate error.
return cpp::fail(std::move(r).error());
// will not compile because Error is not default constructible, even
// though it shouldn't be required in this context.
}
}
Yes, I had read that post.
I would also find it surprising behavior if it did that. // result&& -> error&&
template <typename T, typename E>
auto constexpr result<T, E>::assume_error() && noexcept -> typename std::add_rvalue_reference_t<E> {
return std::move(m_storage.storage.m_error);
}
// result const& -> error const&
template <typename T, typename E>
auto constexpr result<T, E>::assume_error() const& noexcept -> typename std::add_lvalue_reference_t<typename std::add_const_t<E>>
{
return m_storage.storage.m_error;
} It would solve the unnecessary check in the first example, and the default constructible requirement from the second example.
You're right, my bad. I've since been playing around with those explosive types and figured it wasn't an issue. |
Beta Was this translation helpful? Give feedback.
-
Would it be possible to implement a member function that returns the result's underlying error, similar to
.error()
, but without without the runtime check? It would be analogous tooperator*()
.Additionally, this member function wouldn't need the error type to be default constructible, since it would never need to construct one. This would in turn allow non-default constructible errors to be used in conjunction with result.
Dummy implementation:
Finally, I noticed that the result's member functions that move the underlying value or error are unconditionally
noexcept
. However, this isn't always the case. The following is valid code:This could be solved by using the
noexcept
operator, e.g.:Beta Was this translation helpful? Give feedback.
All reactions