你如何使用Alexandrescu的预期<T>与void函数?

Ale*_*lex 35 c++ error-handling runtime-error c++11

所以我遇到了这个(恕我直言)非常好的想法,使用返回值和异常的复合结构 - Expected<T>.它克服了传统错误处理方法(例外,错误代码)的许多缺点.

安德烈Alexandrescu的的谈话(系统误差在C++处理)它的幻灯片.

异常和错误代码具有基本相同的使用方案,其中函数返回某些内容而不返回内容.Expected<T>另一方面,似乎只针对返回值的函数.

所以,我的问题是:

  • 有没有人Expected<T>在实践中尝试过?
  • 你如何将这个成语应用于什么都不返回的函数(即void函数)?

更新:

我想我应该澄清一下我的问题.在Expected<void>专业化是有道理的,但我更感兴趣的是它如何被使用-在一致的使用成语.实现本身是次要的(并且很容易).

例如,Alexandrescu给出了这个例子(有点编辑):

string s = readline();
auto x = parseInt(s).get(); // throw on error
auto y = parseInt(s); // won’t throw
if (!y.valid()) {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

这段代码以一种自然流动的方式"干净".我们需要价值 - 我们得到它.但是,expected<void>有人必须捕获返回的变量并对其执行某些操作(如.throwIfError()或类似的东西),这不是那么优雅.显然,.get()无效是没有道理的.

那么,如果你有另一个函数,比如说toUpper(s),哪个函数就地修改了字符串并没有返回值,你的代码会是什么样子?

Mat*_* M. 13

尽管对于那些专注于C-ish语言的人来说它可能看起来很新,但对于那些喜欢支持sum-types的语言的人来说,它并非如此.

例如,在Haskell中你有:

data Maybe a = Nothing | Just a

data Either a b = Left a | Right b
Run Code Online (Sandbox Code Playgroud)

|读取与第一个元素(Nothing,Just,Left,Right)只是一个"标签".基本上总和类型只是歧视性的工会.

在这里,你会有Expected<T>类似的东西:Either T Exception具有Expected<void>类似的专业化Maybe Exception.


ipc*_*ipc 12

有没有人尝试过预期; 在实践中?

这很自然,我在看到这个演讲之前就已经习惯了.

你如何将这个成语应用于什么都不返回的函数(即void函数)?

幻灯片中显示的表单有一些微妙的含义:

  • 该例外与该值绑定.
  • 可以随意处理异常.
  • 如果由于某些原因忽略该值,则会禁止该异常.

如果你有expected<void>,这就不成立,因为没有人对这个void值感兴趣,所以总是会忽略异常.我会强迫这个,因为我会强制阅读expected<T>Alexandrescus类,断言和明确的suppress成员函数.出于好的原因,不允许从析构函数中重新抛出异常,因此必须使用断言来完成.

template <typename T> struct expected;

#ifdef NDEBUG // no asserts
template <> class expected<void> {
  std::exception_ptr spam;
public:
  template <typename E>
  expected(E const& e) : spam(std::make_exception_ptr(e)) {}
  expected(expected&& o) : spam(std::move(o.spam)) {}
  expected() : spam() {}

  bool valid() const { return !spam; }
  void get() const { if (!valid()) std::rethrow_exception(spam); }
  void suppress() {}
};
#else // with asserts, check if return value is checked
      // if all assertions do succeed, the other code is also correct
      // note: do NOT write "assert(expected.valid());"
template <> class expected<void> {
  std::exception_ptr spam;
  mutable std::atomic_bool read; // threadsafe
public:
  template <typename E>
  expected(E const& e) : spam(std::make_exception_ptr(e)), read(false) {}
  expected(expected&& o) : spam(std::move(o.spam)), read(o.read.load()) {}
  expected() : spam(), read(false) {}

  bool valid() const { read=true; return !spam; }
  void get() const { if (!valid()) std::rethrow_exception(spam); }
  void suppress() { read=true; }

  ~expected() { assert(read); }
};
#endif

expected<void> calculate(int i)
{
  if (!i) return std::invalid_argument("i must be non-null");
  return {};
}

int main()
{
  calculate(0).suppress(); // suppressing must be explicit
  if (!calculate(1).valid())
    return 1;
  calculate(5); // assert fails
}
Run Code Online (Sandbox Code Playgroud)


Lor*_*tté 5

就像Matthieu M.所说,这对于C++来说是一个相对较新的东西,但对于许多函数式语言来说并不是什么新东西.

我想在这里加上我的2美分:在我看来,部分困难和差异可以在"程序与功能"方法中找到.我想使用Scala(因为我熟悉Scala和C++,我觉得它有一个更接近的工具(选项Expected<T>)来说明这种区别.

在Scala中你有Option [T],它是Some(t)或None.特别是,也可以选择[单位],这在道德上等同于Expected<void>.

在Scala中,使用模式非常相似,并围绕2个函数构建:isDefined()和get().但它也有"map()"功能.

我喜欢将"map"视为"isDefined + get"的功能等价物:

if (opt.isDefined)
   opt.get.doSomething
Run Code Online (Sandbox Code Playgroud)

val res = opt.map(t => t.doSomething)
Run Code Online (Sandbox Code Playgroud)

将选项"传播"到结果中

我认为,在这里,使用和撰写选项的这种功能风格,就是你的问题的答案:

那么,如果你有另一个函数,你的代码会是什么样子,比如toUpper(s),它会就地修改字符串并且没有返回值?

就个人而言,我不会修改字符串,或者至少我不会返回任何内容.我认为这Expected<T>是一个"功能"概念,需要一个功能模式才能运行良好:toUpper需要返回一个新字符串,或者在修改后返回自己:

auto s = toUpper(s);
s.get(); ...
Run Code Online (Sandbox Code Playgroud)

或者,使用类似Scala的地图

val finalS = toUpper(s).map(upperS => upperS.someOtherManipulation)
Run Code Online (Sandbox Code Playgroud)

如果您不想遵循功能路由,可以使用isDefined/valid并以更加程序化的方式编写代码:

auto s = toUpper(s);
if (s.valid())
    ....
Run Code Online (Sandbox Code Playgroud)

如果你遵循这条路线(也许是因为你需要),有一个"空虚与单位"的要点:历史上,虚空不被认为是一种类型,但"没有类型"(void foo()被认为是一样的Pascal程序).单位(在函数语言中使用)更多地被视为一种意味着"计算"的类型.所以返回一个Option [Unit]确实更有意义,被视为"可选择做某事的计算".而且Expected<void>,void假定了一个类似的含义:一个计算,当它按预期工作时(没有特殊情况),只是结束(什么都不返回).至少,IMO!

因此,使用预期或选项[单位]可以被视为可能产生结果的计算,或者可能不是.链接它们将证明它很难:

auto c1 = doSomething(s); //do something on s, either succeed or fail
if (c1.valid()) {
   auto c2 = doSomethingElse(s); //do something on s, either succeed or fail
   if (c2.valid()) { 
        ...
Run Code Online (Sandbox Code Playgroud)

不是很干净.

Scala中的地图使它更清洁一点

doSomething(s) //do something on s, either succeed or fail
   .map(_ => doSomethingElse(s) //do something on s, either succeed or fail
   .map(_ => ...)
Run Code Online (Sandbox Code Playgroud)

哪个更好,但仍远非理想.在这里,Maybe monad显然赢了......但那是另一个故事......

  • `预期<T>`有点不同,如果我说得对,`Option [T]`就像C#`Nullable`,或者C++`boost :: optional`(提出`std :: optional`).但是'预期'是针对可选值的例外情况.它试图获得最好的异常和返回代码. (2认同)