提问人:NeitherNor 提问时间:2/21/2023 最后编辑:Nicol BolasNeitherNor 更新时间:8/26/2023 访问量:272
std::expected、引用和 std::reference_wrapper
std::expected, references, and std::reference_wrapper
问:
此问题与 std::expected 和引用返回类型有关
我试着(分别 https://github.com/TartanLlama/expected)将我的头脑作为一种替代的错误处理方法,我喜欢这个概念。但是,对于预期的类型,引用似乎是被禁止的。在我的项目中,一些函数应该返回值,一些其他函数应该返回引用,我想要一个适用于两者的通用错误处理策略。std::expected
我的想法/期望是,对于引用应该像成功的指针一样工作,例如像这样(格式不正确,但我“想要”):std::expected
std::expected<Foo&,string> ret = someFunction(); // undefined behavior
if(ret)
ret->bar();
else
doErrorHandling(ret.error());
在 std::expected 和 reference 返回类型中,建议在这种情况下使用 as expected 类型。然而,这似乎意味着我上面的例子需要一些(丑陋的)间接:std::reference_wrapper
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());
有没有办法避免这个额外的步骤?我知道,它看起来很小,但是,对我来说,它有点与......std::expected
答:
为了减少键入次数,您可以考虑使用 gsl::not_null<Foo *>
之类的东西来代替(请注意,在 gsl::not_null 与 std::reference_wrapper 中添加了指向模板化类型的指针)。然后你可以做类似 or .在另一个问题中提供了有关这两种包装器类型比较的更多信息。std::reference_wrapper<Foo>
(*ret)->bar()
(**ret).bar()
请注意,大多数编译器无论如何都会将引用实现为指针,因此“额外”间接仍然发生在“后台”,它只是由编译器为您完成。因此,这应该主要是打字和清晰度的问题,而不是性能问题。特别是,由于 和 都应该是可简单复制的和可简单破坏的,因此不应阻止您像使用 unique_ptr 那样在寄存器中传递它们。reference_wrapper<Foo>
gsl::not_null<Foo *>
如果你不能或不愿意专门使用 gsl,你可以自己编写一个类似的包装类:
- 使用构造函数强制执行私有成员原始指针 “” 从不为 null 的不变性。为了支持多态性,您可能希望允许构造一个带有 “” 的 “”,其中为 true。复制构造函数和析构函数应该是微不足道的。
T *
not_null<T>
U &
std::is_convertible<U *, T *>::value
- 添加一些方法(可能至少是内联取消引用运算符,但由您决定)来取消引用私有成员指针
- (可选)将隐式或显式转换运算符添加到引用和/或原始指针
例:
#include <type_traits>
// for std::addressof, unless you don't want to support
// classes with overloaded operator&
#include <memory>
namespace stack_overflow_answer {
template<typename T>
class non_null
{
T * raw;
public:
using pointer = T *;
using element_type = T;
using reference = T &;
// defining our own constructor
// already deletes the default constructor,
// but this makes it obvious that we did so on purpose
non_null() = delete;
// std::addressof is only constexpr since c++17,
// so for older compilers, either get rid of constexpr
// or give up supporting classes that overload operator&
// and replace std::addressof with &
constexpr non_null(reference t_ref) noexcept
: raw(std::addressof(t_ref))
{}
// construct from other types if pointer converts
template<
typename U,
typename Enable = typename std::enable_if<
std::is_convertible<U *, T *>::value
&& !std::is_same<U *, T *>::value
>::type
> constexpr non_null(U & convertible_ref) noexcept
: raw(std::addressof(convertible_ref))
{}
non_null(non_null const &) = default;
non_null & operator=(non_null const &) = default;
// also allow assignment from non-null convertible pointers
template<
typename U,
typename Enable = typename std::enable_if<
std::is_convertible<U *, T *>::value
&& !std::is_same<U *, T *>::value
>::type
> constexpr non_null & operator=(non_null<U> const & convertible) noexcept
{
// other than constexpr std::addressof,
// the rest of this should all work in c++11,
// so we'll meet c++11's restrictions on constexpr
// by using the comma operator to do the assignment
return ((raw = static_cast<U *>(convertible)), *this);
}
constexpr reference operator*() const noexcept
{return *raw;}
constexpr pointer operator->() const noexcept
{return raw;}
// I have chosen implicit conversions in this example
// but you could make them explicit if you prefer
constexpr operator reference () const noexcept
{ return *raw;}
constexpr operator pointer () const noexcept
{return raw;}
};
// you could verify with whatever type you want
static_assert(
std::is_trivially_copyable<non_null<int> >::value
&& std::is_trivially_destructible<non_null<int> >::value,
"inappropriate overhead"
);
}
评论
gsl::
评论
ret->get().bar();
static_cast<Foo&>(*ret).bar();