C++23中的新功能之expected和optional

一、std::optional

在前面用过c++17新推出的这个std::optional功能,它可以处理接口返回空值的情况,非常方便实用。但终究存在一个习惯的问题,大家会问,只是处理一个空值,多写一个这玩意儿意义不大,还是用方法吧。这种情况一定是大概率的发生。正如语言发展一样,一定要简单,而且功能还要强大。
标准委员会的大佬们一定也是这么想的,所以既然出现了可选项,是不是可以在这个可选项上做文章,吸引更多的开发者使用它呢?先看一个例子:


std::optional<int> GetData(int id)
{...}
return GetData(id).value_or(-3);

这样在处理一些异常情况时,开发者可以自己设定返回默认的值。这种形式,一般对开发者来说,是喜闻乐见的,少写个else(甚至可能更多),而且还容易理解。只要头脑中这种想法开了头,那么便无法挡住了。在C++23中提供了三个函数:add_then,tranform以及or_else。它们基本都符合上面所说的容易理解应用还增强了功能。看下面的例子:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
std::optional<int> to_int(std::string_view sv)
{
    int r {};
    auto [ptr, ec] { std::from_chars(sv.data(), sv.data() + sv.size(), r) };
    if (ec == std::errc())
        return r;
    else
        return std::nullopt;
}
 
int main()
{
    using namespace std::literals;
 
    const std::vector<std::optional<std::string>> v
    {
        "1234", "15 foo", "bar", "42", "5000000000", " 5", std::nullopt, "-43"
    };
 
    for (auto&& x : v | std::views::transform(
        [](auto&& o)
        {
            // debug print the content of input optional
            std::cout << std::left << std::setw(13)
                      << std::quoted(o.value_or("nullopt")) << " -> ";
 
            return o
                // if optional is nullopt convert it to optional with "" string
                .or_else([]{ return std::optional{""s}; })
                // flatmap from strings to ints (making empty optionals where it fails)
                .and_then(to_int)
                // map int to int + 1
                .transform([](int n) { return n + 1; })
                // convert back to strings
                .transform([](int n) { return std::to_string(n); })
                // replace all empty optionals that were left by
                // and_then and ignored by transforms with "NaN"
                .value_or("NaN"s);
        }))
        std::cout << x << '\n';
}

看一下运行结果:

"1234"        -> 1235
"15 foo"      -> 16
"bar"         -> NaN
"42"          -> 43
"5000000000"  -> NaN
" 5"          -> NaN
"nullopt"     -> NaN
"-43"         -> -42

看到这个代码会不会想到在Java等其它语言中的函数式编程(链式编程),add_then它们三个就可以搞定。既可以用transform来处理正常的,也可以用or_else处理异常的,一瓜可以多吃,以后C++编程也可以 拖一长列,不过这玩意儿太长了,还真是不好理解。
人的本性或许就是懒的,有人说是懒人改变了这个世界,不是没有道理。

二、std::expected

前面的value_or其实可以当成一种std::expected的特例,先看一下它的定义:

template< class T, class E >
class expected;

std::expected增加了一个形参E,它用来处理不符合常规的,也就是异常的值。std::expected相当于std::variant和std::optional的结合体。在C++中,一般是不推荐抛出异常的,但错误会常常发生,这时,std::expected期望着正常的状态,但在出现非期望的状态时,也可以通过一些方式来展现这些异常。
看一个例子:

#include 
#include 
#include 
#include 
#include 
 
enum class parse_error
{
    invalid_input,
    overflow
};
 
auto parse_number(std::string_view& str) -> std::expected<double, parse_error>
{
    const char* begin = str.data();
    char* end;
    double retval = std::strtod(begin, &end);
 
    if (begin == end)
        return std::unexpected(parse_error::invalid_input);
    else if (std::isinf(retval))
        return std::unexpected(parse_error::overflow);
 
    str.remove_prefix(end - begin);
    return retval;
}
 
int main()
{
    auto process = [](std::string_view str)
    {
        std::cout << "str: " << std::quoted(str) << ", ";
        if (const auto num = parse_number(str); num.has_value())
        {
            std::cout << "value: " << *num << '\n';
            // If num did not have a value, dereferencing num
            // would cause an undefined behavior, and
            // num.value() would throw std::bad_expected_access.
            // num.value_or(123) uses specified default value 123.
        }
        else if (num.error() == parse_error::invalid_input)
        {
            std::cout << "error: invalid input\n";
        }
        else if (num.error() == parse_error::overflow)
        {
            std::cout << "error: overflow\n";
        }
        else
        {
            std::cout << "unexpected!\n"; // or invoke std::unreachable();
        }
    };
 
    for (auto src: { "42", "42abc", "meow", "inf" })
        process(src);
}

运行结果:

str: "42", value: 42
str: "42abc", value: 42
str: "meow", error: invalid input
str: "inf", error: overflow

而且它也有和std::optional一样的三个函数:add_then,tranform以及or_else,用法也基本相同。这个用在有多处判断返回结果处理异常的情况,就很有优势。

三、总结

库的强大,其实就意味着这种语言向简单化在发展。为什么Java等语言编写起来相对简单,主要原因就是除有强大的库,还有无数的可以看作库的框架。C++在这方面几乎没法和这些高级语言相比,但总体上C++也在朝着这个方向不断的前进,这就是好事。
或许再过十年,当开发者们回头看C++代码时,可能发现,这是我初学时的C++么?

你可能感兴趣的:(C++11,C++,c++23)