std::expected、引用和 std::reference_wrapper

std::expected, references, and std::reference_wrapper

提问人:NeitherNor 提问时间:2/21/2023 最后编辑:Nicol BolasNeitherNor 更新时间:8/26/2023 访问量:272

问:

此问题与 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

C++ 异常 C++23 引用包装器 std-expected

评论

1赞 Richard Critten 2/21/2023
“......如果程序使用引用类型实例化预期值,则该程序格式不正确...“ en.cppreference.com/w/cpp/utility/expected
0赞 Richard Critten 2/21/2023
从提案文件:P0323R12 - 3.27。预期参考“......我们需要一个未来的建议......“ 在 P0323R12 - 3.26 中。对 T 和 E 的要求要求“......它们必须是完整的对象类型“,并且引用不是对象。
0赞 NeitherNor 2/21/2023
@RichardCritten:我知道我不能用引用类型(注意“未定义的行为”)实例化预期,而且“我们需要一个未来的提案”。问题是,是否有比所述更好的解决方法?
0赞 sklott 2/21/2023
你不需要使用临时变量,你可以做类似或ret->get().bar();static_cast<Foo&>(*ret).bar();

答:

0赞 Urausgeruhtkin 8/23/2023 #1

为了减少键入次数,您可以考虑使用 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,你可以自己编写一个类似的包装类:

  1. 使用构造函数强制执行私有成员原始指针 “” 从不为 null 的不变性。为了支持多态性,您可能希望允许构造一个带有 “” 的 “”,其中为 true。复制构造函数和析构函数应该是微不足道的。T *not_null<T>U &std::is_convertible<U *, T *>::value
  2. 添加一些方法(可能至少是内联取消引用运算符,但由您决定)来取消引用私有成员指针
  3. (可选)将隐式或显式转换运算符添加到引用和/或原始指针

例:

#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"
     );
}

评论

0赞 Adrian Mole 8/25/2023
请注意,该库不是 C++ 标准的一部分,可能并非对所有人或所有平台/编译器都可用。gsl::
0赞 Urausgeruhtkin 8/26/2023
@AdrianMole没问题,在现有的“不愿意”中添加了“无法”作为按照步骤创建自己的原因,然后添加了一个示例实现。