admin管理员组

文章数量:1027639

C++23中std::optional和std::expected的单子式操作

一、引言

在C++编程中,错误处理和可选值的管理是非常重要的部分。C++17引入了std::optional,它提供了一种新的标准方式来表达可能缺失的值。而C++23在此基础上,不仅对std::optional进行了扩展,还引入了std::expected,并且为它们都提供了受函数式编程启发的新接口,特别是单子式操作(transform、or_else与and_then),这些操作可以简化代码并提高代码的可读性和可维护性。

二、std::optional和std::expected的基本概念

2.1 std::optional

std::optional<T>是一个类模板,它管理一个可选的容纳值,即这个值既可以存在也可以不存在。一种常见的使用情况是作为一个可能失败的函数的返回值。与其他手段,如std::pair<T, bool>相比,std::optional能更好地处理构造开销高昂的对象,并且更加可读,因为它显式表达了意图。例如:

代码语言:cpp代码运行次数:0运行复制
#include <optional>  // 引入std::optional
#include <iostream>
std::optional<double> getValue(bool r) {
    if (r) {
        return 1.52;
    } else {
        return std::nullopt;
    }
}
int main() {
    auto value = getValue(true);
    if (value.has_value()) {
        std::cout << "Value: " << *value << std::endl;
    } else {
        std::cout << "No value" << std::endl;
    }
    return 0;
}

在这个例子中,getValue函数可能返回一个double值,也可能返回std::nullopt表示没有值。在main函数中,我们使用has_value成员函数检查返回值是否存在,如果存在则使用解引用运算符*来获取值。

2.2 std::expected

std::expected<T, E>是C++23标准库中的新成员,旨在提供一种类型安全的方式来表示可能成功或失败的操作结果。它将有效结果或错误封装在单个对象内,本质上表示类型为T的预期值或者类型为E的意外错误。与传统的错误处理技术(如异常或错误码)不同,std::expected避免了异常带来的复杂性和开销,使得编写健壮软件时更加方便。例如:

代码语言:cpp代码运行次数:0运行复制
#include <expected>
#include <iostream>
#include <string>
enum class parse_error {
    invalid_input,
    overflow
};
auto parse_number(std::string_view& str) -> std::expected<double, parse_error> {
    // 解析逻辑
    // 如果解析失败,返回相应的错误
    return std::unexpected(parse_error::invalid_input);
    // 如果解析成功,返回预期的值
    return 3.14;
}
int main() {
    std::string_view input = "3.14";
    auto result = parse_number(input);
    if (result) {
        std::cout << "Parsed number: " << *result << std::endl;
    } else {
        switch (result.error()) {
            case parse_error::invalid_input:
                std::cout << "Invalid input" << std::endl;
                break;
            case parse_error::overflow:
                std::cout << "Overflow error" << std::endl;
                break;
        }
    }
    return 0;
}

在这个例子中,parse_number函数尝试解析一个字符串为double类型的数字。如果解析成功,它返回一个包含解析结果的std::expected对象;如果解析失败,它返回一个包含错误信息的std::expected对象。在main函数中,我们可以通过检查result是否为真来判断操作是否成功,如果成功则获取结果,否则处理相应的错误。

三、std::optional的单子式操作

3.1 transform

transform函数用于对std::optional中的值应用一个函数,并返回一个新的std::optional,其中包含应用函数后的结果。如果std::optional没有值,则返回一个空的std::optional。其函数原型如下:

代码语言:cpp代码运行次数:0运行复制
// 如果*this有值, 则返回F的结果(F返回不一定是optional<U>类型), 否则返回空的optional.
template<class F> constexpr auto transform(F&& f) &;
template<class F> constexpr auto transform(F&& f) const&;
template<class F> constexpr auto transform(F&& f) &&;
template<class F> constexpr auto transform(F&& f) const&&;

示例代码如下:

代码语言:cpp代码运行次数:0运行复制
#include <iostream>
#include <optional>
#include <string>
int main() {
    std::optional<int> opt = 42;
    // 使用 transform 将 int 转换为 std::string
    std::optional<std::string> result = opt.transform([](int value) {
        return std::to_string(value);
    });
    if (result) {
        std::cout << "Transformed value: " << *result << std::endl;
    } else {
        std::cout << "No value" << std::endl;
    }
    return 0;
}

在这个例子中,我们有一个包含int值的std::optional对象opt,通过transform函数将其中的int值转换为std::string类型,并将结果存储在新的std::optional对象result中。最后,我们检查result是否有值并输出相应的结果。

3.2 and_then

and_then函数用于链式调用一系列可能返回std::optional的操作。如果std::optional有值,则将该值传递给提供的函数,并返回该函数的结果;如果std::optional没有值,则直接返回一个空的std::optional。其函数原型如下:

代码语言:cpp代码运行次数:0运行复制
// 如果*this有值, 则返回F的结果(F必须返回optional<U>类型), 否则返回空的optional.
template<class F> constexpr auto and_then(F&& f) &;
template<class F> constexpr auto and_then(F&& f) const&;
template<class F> constexpr auto and_then(F&& f) &&;
template<class F> constexpr auto and_then(F&& f) const&&;

示例代码如下:

代码语言:cpp代码运行次数:0运行复制
#include <iostream>
#include <optional>
#include <string>
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() {
    std::optional<std::string> str_opt = "123";
    auto result = str_opt.and_then(to_int);
    if (result) {
        std::cout << "Converted integer: " << *result << std::endl;
    } else {
        std::cout << "Conversion failed" << std::endl;
    }
    return 0;
}

在这个例子中,我们有一个包含std::stringstd::optional对象str_opt,通过and_then函数将其中的字符串转换为int类型。to_int函数尝试将字符串解析为整数,如果解析成功则返回一个包含整数的std::optional对象,否则返回std::nullopt。最后,我们检查result是否有值并输出相应的结果。

3.3 or_else

or_else函数用于在std::optional没有值的情况下提供一个默认值或执行一个替代操作。如果std::optional有值,则返回该值;如果没有值,则返回提供的函数的结果。其函数原型如下:

代码语言:cpp代码运行次数:0运行复制
// 如果*this有值, 则返回*this, 否则返回F的结果.
template<class F> constexpr auto or_else(F&& f) &;
template<class F> constexpr auto or_else(F&& f) const&;
template<class F> constexpr auto or_else(F&& f) &&;
template<class F> constexpr auto or_else(F&& f) const&&;

示例代码如下:

代码语言:cpp代码运行次数:0运行复制
#include <iostream>
#include <optional>
#include <string>
int main() {
    using maybe_int = std::optional<int>;
    auto valueless = [] {
        std::cout << "Valueless: ";
        return maybe_int{0};
    };
    maybe_int x;
    std::cout << x.or_else(valueless).value() << '\n';
    x = 42;
    std::cout << "Has value: ";
    std::cout << x.or_else(valueless).value() << '\n';
    x.reset();
    std::cout << x.or_else(valueless).value() << '\n';
    return 0;
}

在这个例子中,我们定义了一个valueless函数,它在std::optional没有值时返回一个包含0std::optional对象。然后我们创建了一个std::optional<int>对象x,并多次调用or_else函数。当x没有值时,or_else函数会调用valueless函数并返回其结果;当x有值时,or_else函数直接返回x的值。

四、std::expected的单子式操作

4.1 transform

transform函数用于对std::expected中的值应用一个函数,并返回一个新的std::expected,其中包含应用函数后的结果。如果std::expected包含错误,则直接返回包含相同错误的std::expected对象。示例代码如下:

代码语言:cpp代码运行次数:0运行复制
#include <iostream>
#include <expected>
#include <string>
std::expected<int, std::string> increment(int value) {
    return value + 1;
}
int main() {
    std::expected<int, std::string> exp = 42;
    auto result = exp.transform(increment);
    if (result) {
        std::cout << "Transformed value: " << *result << std::endl;
    } else {
        std::cout << "Error: " << result.error() << std::endl;
    }
    return 0;
}

在这个例子中,我们有一个包含int值的std::expected对象exp,通过transform函数将其中的int值加1,并将结果存储在新的std::expected对象result中。最后,我们检查result是否包含值并输出相应的结果。

4.2 and_then

and_then成员函数用于链式调用一系列可能返回std::expected的操作。它在std::expected对象持有值时被调用,允许无缝地进行操作链,而无需在每个步骤后手动进行错误检查。示例代码如下:

代码语言:cpp代码运行次数:0运行复制
#include <iostream>
#include <expected>
std::expected<int, std::string> incrementIfPositive(int value) {
    if (value > 0) 
        return value + 1;
    return std::unexpected("Value must be positive");
}
std::expected<int, std::string> getInput(int x) {
    if (x % 2 == 0)
        return x;
    return std::unexpected("Value not even!");
}
int main() {
    auto input = getInput(-2);
    auto result = input.and_then(incrementIfPositive);
    if (result)
        std::cout << *result << '\n';
    else
        std::cout << result.error() << '\n';
    return 0;
}

在这个例子中,getInput函数返回一个std::expected对象,如果输入是偶数则包含该偶数,否则包含错误信息。incrementIfPositive函数接受一个整数,如果该整数为正,则返回加1后的结果,否则返回错误信息。通过and_then函数,我们可以将这两个操作链起来,避免了手动的错误检查。

4.3 or_else

or_else函数用于在std::expected包含错误的情况下提供一个替代操作或默认值。如果std::expected包含值,则返回该值;如果包含错误,则调用提供的函数并返回其结果。示例代码如下:

代码语言:cpp代码运行次数:0运行复制
#include <iostream>
#include <expected>
#include <string>
std::expected<int, std::string> divide(int a, int b) {
    if (b == 0) {
        return std::unexpected("Division by zero");
    }
    return a / b;
}
std::expected<int, std::string> fallback() {
    return 0;
}
int main() {
    auto result = divide(10, 0).or_else(fallback);
    if (result) {
        std::cout << "Result: " << *result << std::endl;
    } else {
        std::cout << "Error: " << result.error() << std::endl;
    }
    return 0;
}

在这个例子中,divide函数尝试进行除法运算,如果除数为0则返回错误信息。fallback函数返回一个包含0std::expected对象。通过or_else函数,当divide函数返回错误时,会调用fallback函数并返回其结果。

五、总结

C++23中为std::optionalstd::expected引入的单子式操作(transform、or_else与and_then)为C++编程带来了更强大的功能和更简洁的代码风格。这些操作借鉴了函数式编程的思想,使得我们可以更方便地处理可选值和错误情况,避免了大量的手动错误检查和嵌套的if-else语句,提高了代码的可读性和可维护性。在实际开发中,合理使用这些操作可以让我们的代码更加健壮和优雅。

C++23中std::optional和std::expected的单子式操作

一、引言

在C++编程中,错误处理和可选值的管理是非常重要的部分。C++17引入了std::optional,它提供了一种新的标准方式来表达可能缺失的值。而C++23在此基础上,不仅对std::optional进行了扩展,还引入了std::expected,并且为它们都提供了受函数式编程启发的新接口,特别是单子式操作(transform、or_else与and_then),这些操作可以简化代码并提高代码的可读性和可维护性。

二、std::optional和std::expected的基本概念

2.1 std::optional

std::optional<T>是一个类模板,它管理一个可选的容纳值,即这个值既可以存在也可以不存在。一种常见的使用情况是作为一个可能失败的函数的返回值。与其他手段,如std::pair<T, bool>相比,std::optional能更好地处理构造开销高昂的对象,并且更加可读,因为它显式表达了意图。例如:

代码语言:cpp代码运行次数:0运行复制
#include <optional>  // 引入std::optional
#include <iostream>
std::optional<double> getValue(bool r) {
    if (r) {
        return 1.52;
    } else {
        return std::nullopt;
    }
}
int main() {
    auto value = getValue(true);
    if (value.has_value()) {
        std::cout << "Value: " << *value << std::endl;
    } else {
        std::cout << "No value" << std::endl;
    }
    return 0;
}

在这个例子中,getValue函数可能返回一个double值,也可能返回std::nullopt表示没有值。在main函数中,我们使用has_value成员函数检查返回值是否存在,如果存在则使用解引用运算符*来获取值。

2.2 std::expected

std::expected<T, E>是C++23标准库中的新成员,旨在提供一种类型安全的方式来表示可能成功或失败的操作结果。它将有效结果或错误封装在单个对象内,本质上表示类型为T的预期值或者类型为E的意外错误。与传统的错误处理技术(如异常或错误码)不同,std::expected避免了异常带来的复杂性和开销,使得编写健壮软件时更加方便。例如:

代码语言:cpp代码运行次数:0运行复制
#include <expected>
#include <iostream>
#include <string>
enum class parse_error {
    invalid_input,
    overflow
};
auto parse_number(std::string_view& str) -> std::expected<double, parse_error> {
    // 解析逻辑
    // 如果解析失败,返回相应的错误
    return std::unexpected(parse_error::invalid_input);
    // 如果解析成功,返回预期的值
    return 3.14;
}
int main() {
    std::string_view input = "3.14";
    auto result = parse_number(input);
    if (result) {
        std::cout << "Parsed number: " << *result << std::endl;
    } else {
        switch (result.error()) {
            case parse_error::invalid_input:
                std::cout << "Invalid input" << std::endl;
                break;
            case parse_error::overflow:
                std::cout << "Overflow error" << std::endl;
                break;
        }
    }
    return 0;
}

在这个例子中,parse_number函数尝试解析一个字符串为double类型的数字。如果解析成功,它返回一个包含解析结果的std::expected对象;如果解析失败,它返回一个包含错误信息的std::expected对象。在main函数中,我们可以通过检查result是否为真来判断操作是否成功,如果成功则获取结果,否则处理相应的错误。

三、std::optional的单子式操作

3.1 transform

transform函数用于对std::optional中的值应用一个函数,并返回一个新的std::optional,其中包含应用函数后的结果。如果std::optional没有值,则返回一个空的std::optional。其函数原型如下:

代码语言:cpp代码运行次数:0运行复制
// 如果*this有值, 则返回F的结果(F返回不一定是optional<U>类型), 否则返回空的optional.
template<class F> constexpr auto transform(F&& f) &;
template<class F> constexpr auto transform(F&& f) const&;
template<class F> constexpr auto transform(F&& f) &&;
template<class F> constexpr auto transform(F&& f) const&&;

示例代码如下:

代码语言:cpp代码运行次数:0运行复制
#include <iostream>
#include <optional>
#include <string>
int main() {
    std::optional<int> opt = 42;
    // 使用 transform 将 int 转换为 std::string
    std::optional<std::string> result = opt.transform([](int value) {
        return std::to_string(value);
    });
    if (result) {
        std::cout << "Transformed value: " << *result << std::endl;
    } else {
        std::cout << "No value" << std::endl;
    }
    return 0;
}

在这个例子中,我们有一个包含int值的std::optional对象opt,通过transform函数将其中的int值转换为std::string类型,并将结果存储在新的std::optional对象result中。最后,我们检查result是否有值并输出相应的结果。

3.2 and_then

and_then函数用于链式调用一系列可能返回std::optional的操作。如果std::optional有值,则将该值传递给提供的函数,并返回该函数的结果;如果std::optional没有值,则直接返回一个空的std::optional。其函数原型如下:

代码语言:cpp代码运行次数:0运行复制
// 如果*this有值, 则返回F的结果(F必须返回optional<U>类型), 否则返回空的optional.
template<class F> constexpr auto and_then(F&& f) &;
template<class F> constexpr auto and_then(F&& f) const&;
template<class F> constexpr auto and_then(F&& f) &&;
template<class F> constexpr auto and_then(F&& f) const&&;

示例代码如下:

代码语言:cpp代码运行次数:0运行复制
#include <iostream>
#include <optional>
#include <string>
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() {
    std::optional<std::string> str_opt = "123";
    auto result = str_opt.and_then(to_int);
    if (result) {
        std::cout << "Converted integer: " << *result << std::endl;
    } else {
        std::cout << "Conversion failed" << std::endl;
    }
    return 0;
}

在这个例子中,我们有一个包含std::stringstd::optional对象str_opt,通过and_then函数将其中的字符串转换为int类型。to_int函数尝试将字符串解析为整数,如果解析成功则返回一个包含整数的std::optional对象,否则返回std::nullopt。最后,我们检查result是否有值并输出相应的结果。

3.3 or_else

or_else函数用于在std::optional没有值的情况下提供一个默认值或执行一个替代操作。如果std::optional有值,则返回该值;如果没有值,则返回提供的函数的结果。其函数原型如下:

代码语言:cpp代码运行次数:0运行复制
// 如果*this有值, 则返回*this, 否则返回F的结果.
template<class F> constexpr auto or_else(F&& f) &;
template<class F> constexpr auto or_else(F&& f) const&;
template<class F> constexpr auto or_else(F&& f) &&;
template<class F> constexpr auto or_else(F&& f) const&&;

示例代码如下:

代码语言:cpp代码运行次数:0运行复制
#include <iostream>
#include <optional>
#include <string>
int main() {
    using maybe_int = std::optional<int>;
    auto valueless = [] {
        std::cout << "Valueless: ";
        return maybe_int{0};
    };
    maybe_int x;
    std::cout << x.or_else(valueless).value() << '\n';
    x = 42;
    std::cout << "Has value: ";
    std::cout << x.or_else(valueless).value() << '\n';
    x.reset();
    std::cout << x.or_else(valueless).value() << '\n';
    return 0;
}

在这个例子中,我们定义了一个valueless函数,它在std::optional没有值时返回一个包含0std::optional对象。然后我们创建了一个std::optional<int>对象x,并多次调用or_else函数。当x没有值时,or_else函数会调用valueless函数并返回其结果;当x有值时,or_else函数直接返回x的值。

四、std::expected的单子式操作

4.1 transform

transform函数用于对std::expected中的值应用一个函数,并返回一个新的std::expected,其中包含应用函数后的结果。如果std::expected包含错误,则直接返回包含相同错误的std::expected对象。示例代码如下:

代码语言:cpp代码运行次数:0运行复制
#include <iostream>
#include <expected>
#include <string>
std::expected<int, std::string> increment(int value) {
    return value + 1;
}
int main() {
    std::expected<int, std::string> exp = 42;
    auto result = exp.transform(increment);
    if (result) {
        std::cout << "Transformed value: " << *result << std::endl;
    } else {
        std::cout << "Error: " << result.error() << std::endl;
    }
    return 0;
}

在这个例子中,我们有一个包含int值的std::expected对象exp,通过transform函数将其中的int值加1,并将结果存储在新的std::expected对象result中。最后,我们检查result是否包含值并输出相应的结果。

4.2 and_then

and_then成员函数用于链式调用一系列可能返回std::expected的操作。它在std::expected对象持有值时被调用,允许无缝地进行操作链,而无需在每个步骤后手动进行错误检查。示例代码如下:

代码语言:cpp代码运行次数:0运行复制
#include <iostream>
#include <expected>
std::expected<int, std::string> incrementIfPositive(int value) {
    if (value > 0) 
        return value + 1;
    return std::unexpected("Value must be positive");
}
std::expected<int, std::string> getInput(int x) {
    if (x % 2 == 0)
        return x;
    return std::unexpected("Value not even!");
}
int main() {
    auto input = getInput(-2);
    auto result = input.and_then(incrementIfPositive);
    if (result)
        std::cout << *result << '\n';
    else
        std::cout << result.error() << '\n';
    return 0;
}

在这个例子中,getInput函数返回一个std::expected对象,如果输入是偶数则包含该偶数,否则包含错误信息。incrementIfPositive函数接受一个整数,如果该整数为正,则返回加1后的结果,否则返回错误信息。通过and_then函数,我们可以将这两个操作链起来,避免了手动的错误检查。

4.3 or_else

or_else函数用于在std::expected包含错误的情况下提供一个替代操作或默认值。如果std::expected包含值,则返回该值;如果包含错误,则调用提供的函数并返回其结果。示例代码如下:

代码语言:cpp代码运行次数:0运行复制
#include <iostream>
#include <expected>
#include <string>
std::expected<int, std::string> divide(int a, int b) {
    if (b == 0) {
        return std::unexpected("Division by zero");
    }
    return a / b;
}
std::expected<int, std::string> fallback() {
    return 0;
}
int main() {
    auto result = divide(10, 0).or_else(fallback);
    if (result) {
        std::cout << "Result: " << *result << std::endl;
    } else {
        std::cout << "Error: " << result.error() << std::endl;
    }
    return 0;
}

在这个例子中,divide函数尝试进行除法运算,如果除数为0则返回错误信息。fallback函数返回一个包含0std::expected对象。通过or_else函数,当divide函数返回错误时,会调用fallback函数并返回其结果。

五、总结

C++23中为std::optionalstd::expected引入的单子式操作(transform、or_else与and_then)为C++编程带来了更强大的功能和更简洁的代码风格。这些操作借鉴了函数式编程的思想,使得我们可以更方便地处理可选值和错误情况,避免了大量的手动错误检查和嵌套的if-else语句,提高了代码的可读性和可维护性。在实际开发中,合理使用这些操作可以让我们的代码更加健壮和优雅。

本文标签: C23中stdoptional和stdexpected的单子式操作