【问题标题】:standard template for returning a value with a boolean result flag用于返回带有布尔结果标志的值的标准模板
【发布时间】:2019-04-16 10:26:41
【问题描述】:

当我开始利用 C++17 结构化绑定和 if 运算符 init 语句进行更优雅的函数结果报告和检查时,我开始按照 C++ 核心指南 F21 执行以下操作:

std::pair<bool, int>Foo()
{
    return {true, 42}; //true means that function complete with no error and that 42 is a good value
}

void main(void)
{
    if (auto [Result, Value] = Foo(); Result)
    {
        //Do something with the return value here
    }
}

然后,当然,我认为为此类返回类型提供一个可重用的模板会很好,这样没有人必须复制该对的 bool 部分:

template <typename T> using validated = std::pair<bool,T>;

validated<int> Foo()
{
    return {true, 42};
}

void main(void)
{
    if (auto [Result, Value] = Foo(); Result)
    {
        //Do something with the return value here
    }
}

这对我很有用,但现在我想知道是否有某种标准等效于这个模板,这样我就不必重新发明轮子并自己定义它。似乎任意类型值加上有效性标志将是一个有用的构造,但我在标准库中找不到任何东西。我错过了什么吗?

【问题讨论】:

    标签: c++ c++17 c++-standard-library


    【解决方案1】:

    std::optional 正是您要问的。它甚至在描述中:

    可选的常见用例是可能失败的函数的返回值。与其他方法(例如std::pair&lt;T,bool&gt;)相比, optional 可以很好地处理构建成本高的对象并且更具可读性,因为其意图是明确表达的。

    示例中的if 看起来更简单:

    #include <optional>
    #include <iostream>
    
    std::optional<int> Foo(bool fail)
    {
        if (!fail) return {42};
        return {};
    }
    
    void process(bool fail) {
        if (auto val = Foo(fail)) {
            std::cout << val.value() << '\n';
        } else {
            std::cout << "No value!\n";
        }    
    }
    
    int main() {
        std::optional<int> oi;
        process(true);
        process(false);
    }
    

    如果您真的希望明确使用Value,那么您可以随时通过成功分支上的引用解压缩它,即auto Value = val.value()

    您需要注意一些警告。 2 从我的头顶:

    1. 性能:Why is the construction of std::optional<int> more expensive than a std::pair<int, bool>? 尽管对于给定的示例up-to-date clang with -O3 looks pretty convicing

      注意:为简洁起见,为 process 添加了 static - 以防止生成用于外部链接的版本。

    2. 如果对象是默认构造的,它将返回false。这可能会让一些人感到惊讶,optional 的默认构造并不默认构造底层价值。

    编辑: 在 cmets 之后,我决定明确声明 没有类似 pair&lt;T,bool&gt; 的类型别名或与标准库兼容的类似内容。证明某些东西不存在并不容易,但如果有这样的类型,标准库肯定会在insert 的声明中使用它,它不会;因此,我强烈暗示它周围没有任何语义包装。

    【讨论】:

    • 我认为 OP 对与现有 API 一起使用的类型别名感兴趣,例如 std::map::insertstd::optional 显然在这里不起作用。
    • std::optional 是一个很好的发现,谢谢。但是,我同意上面的评论,与标准库本身中现有的返回类型兼容是可取的,我最初没有考虑过。此外,查看 std::optional 的源代码,对于手头的简单任务来说似乎相当重。所以是的,标准库中已经定义的 std::pair 的类型别名将是最好的。有什么建议吗?
    • @RegusPregus 您认为它很重的方式是什么?在没有价值的情况下,它实际上具有更少的开销。 pair&lt;bool, T&gt; 必须构造一个相当不利的对象。恕我直言,标准库出于历史原因使用 pair&lt;bool, T&gt;,即在 c++17 之前没有 optional,但这是现在的惯用方式。
    • @KonradRudolph 是的,但我认为核心指南示例更多的是解决这一事实,而不是鼓励使用 pair&lt;bool, T&gt;。老实说,我不认为pairoptional 有任何优势,除非某个特定的实现对你的性能有影响。一个有趣的实现是this one,它将 no-value 编码为保留类型的保留值。我会考虑将其用于内置类型以节省空间,但这不是标准。
    • @RegusPregus 不要害怕:std::optional&lt;T&gt; 可以非常轻巧。尽管这个答案中的警告是完全正确的,但它的实现通常可以像你的 std::pair 代码一样高效——取决于库 QoI。
    【解决方案2】:

    您可能对提议的std::expected 感兴趣。

    它的界面非常接近std::optional。的主要优点 expected&lt;T, E&gt; 超过 optional&lt;T&gt; 是传输错误的能力:

    enum class errc {err1, err2, err3};
    
    std::expected<int, errc> Foo()
    {
      if (/* error condition 1 */)  return std::unexpected(errc::err1);
    
      // ... checking other error conditions
    
      return 42;  // no error condition (42 is a good value)
                  // implicit conversion from `int` to `expected<int, errc>`
                  // avoid boilerplate code
    }
    
    int main()
    {
      auto q = Foo();
    
      if (q)
      {
        // Do something with the return value here
      }
    }
    

    你也可以看看:


    作为旁注main() must return int

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-15
      • 2019-04-03
      • 2013-04-09
      • 1970-01-01
      • 1970-01-01
      • 2014-04-12
      • 2015-06-18
      • 2019-06-10
      相关资源
      最近更新 更多