This question is related to std::expected and reference return type
I try to get my head around std::expected
(respectively https://github.com/TartanLlama/expected) as an alternative error handling methodology, and I like the concept. However, references seem to be forbidden for the expected type. In my project, some functions should return values, some others references, and I want a common error handling strategy which works for both.
My idea/expectation would be that an std::expected
for references should work kind of like a pointer upon success, e.g. like this (ill formed, but what I "would like"):
std::expected<Foo&,string> ret = someFunction(); // undefined behavior
if(ret)
ret->bar();
else
doErrorHandling(ret.error());
In std::expected and reference return type, it was proposed to use a std::reference_wrapper
as expected type in such cases. However, it seems that this would mean my example above would need some (ugly) indirection:
std::expected<std::reference_wrapper<Foo>,string> ret = someFunction();
if(ret)
{
Foo& temp{*ret}; // ugly indirection to trigger implicit conversion
temp.bar();
}
else
doErrorHandling(ret.error());
Is there a way to avoid this extra step? I know, it seems small, but, for me, it somewhat contradicts the whole point of std::expected
...
For a little less typing, you might consider something like
gsl::not_null<Foo *>
in place ofstd::reference_wrapper<Foo>
(note the addition of the pointer to the templated type in gsl::not_null vs std::reference_wrapper). Then you could do something like(*ret)->bar()
or(**ret).bar()
. There is more information about the comparison of those two wrapper types in another question.Note that most compilers implement references as pointers anyway, so the "extra" indirection still happens "under the hood", it's just done for you by the compiler. Thus, this should be primarily a question of typing and clarity rather than performance. In particular, since
reference_wrapper<Foo>
andgsl::not_null<Foo *>
should both be trivially copyable and trivially destructible, you shouldn't be prevented from passing them in registers the way that you might with unique_ptr.If you're unable or disinclined to use gsl specifically, you could write a similar wrapper class yourself:
T *
" is never null. To support polymorphism you probably want to allow constructing a "not_null<T>
" with a "U &
" wherestd::is_convertible<U *, T *>::value
is true. Copy constructor and destructor should be trivial.Example: