概述
std::tuple - 解决多个返回值的良药(C++11)
std::variant-解决多类型单一返回值的神奇类型(C++17)
std::optional - 解决空/有效语义C++(17)
std::expected - 解决正确错误语义(C++23)
一、传统返回值处理的困境 在传统的C++编程中,函数返回值处理存在以下问题:
1. 多个返回值处理复杂 1 2 3 4 5 6 7 8 9 10 bool parseCoordinates (const std::string& input, int & x, int & y) { if () { return false ; } x = ; y = ; return true ; }
2. 无法处理多类型单一返回值处理 1 2 3 4 5 6 7 auto func () { if (){ return 1 ; }else if (){ return "hello" ; } }
3. 空值处理不安全 1 2 3 4 std::string* findUser (int id) { return nullptr ; }
4. 错误处理-语义不明确 1 2 3 4 5 6 7 int divide (int a, int b) { if (b == 0 ) { return -1 ; } return a / b; }
因此现代C++的以下特性来帮助更好更优雅的处理函数返回值
std::tuple - 解决多个返回值的良药(C++11)
std::variant-解决多类型单一返回值的神奇类型(C++17)
std::optional - 解决空/有效语义C++(17)
std::expected - 解决正确错误语义(C++23)
二、std::tuple - 解决多返回值的良药 1 概述 std::tuple
是C++11引入的一个容器,用于存储固定数量的不同类型的元素。
2 .特点
类型安全 :编译时确定元素类型,避免运行时类型错误
固定大小 :元素数量在编译时确定,不能动态改变
异构存储 :可以存储不同类型的元素
值语义 :支持拷贝和移动操作
零开销抽象 :编译器优化后几乎没有性能损失
3. 常用API通用使用模板 创建 1 2 3 4 5 6 7 8 std::tuple<int , std::string, double > t1 (42 , "hello" , 3.14 ) ;auto t2 = std::make_tuple (42 , "hello" , 3.14 );std::tuple<int , std::string, double > t3{42 , "hello" , 3.14 };
查询 1 2 3 4 5 constexpr size_t size = std::tuple_size_v<decltype (t1)>;using first_type = std::tuple_element_t <0 , decltype (t1)>;
访问 1 2 3 4 5 6 7 8 9 int value = std::get <0 >(t1);std::string str = std::get <1 >(t1); int value2 = std::get <int >(t1);auto [i, s, d] = t1;
操作 1 2 3 4 5 6 7 8 9 10 std::get <0 >(t1) = 100 ; std::get <1 >(t1) = "world" ; bool equal = (t1 == t2);bool less = (t1 < t2);std::swap (t1, t2);
4. 具体使用示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <tuple> #include <vector> int main () { std::tuple t1{"hello" , 42 , std::vector<int >{1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }}; std::cout << std::get <0 >(t1) << "\n" ; std::get <0 >(t1) = "world" ; std::cout << std::get <0 >(t1) << "\n" ; std::cout << std::tuple_size_v<decltype (t1)> << "\n" ; std::tuple t2 = std::make_tuple ( "hello" , 42 , std::vector<int >{1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }); auto [e1, e2, e3] = t2; std::cout << e1 << "\n" ; std::cout << e2 << "\n" ; for (auto &i : e3) { std::cout << i << " " ; } return 0 ; }
使用场景 :
函数参数展开
多返回值的统一处理
泛型编程中的参数传递
5. std::apply 函数详解 std::apply
(C++17)是处理tuple数据的核心函数,它将tuple的元素作为参数传递给可调用对象。
功能 :将tuple中的元素展开作为函数参数
语法 :
1 2 3 template <class F, class Tuple>constexpr decltype (auto ) apply (F&& f, Tuple&& t) ;
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <tuple> #include <iostream> #include <functional> #include <string> auto add = [](int a, int b, int c) { return a + b + c; };auto tuple_data = std::make_tuple (1 , 2 , 3 );auto result = std::apply (add, tuple_data); std::tuple<int , std::string, double > getData () { return {42 , "hello" , 3.14 }; } void processData (int num, const std::string& str, double val) { std::cout << "Number: " << num << ", String: " << str << ", Value: " << val << std::endl; } int main () { auto data = getData (); std::apply (processData, data); return 0 ; }
二. std::variant-解决多类型单一返回值的神奇类型 2.1 概述 std::variant
是C++17引入的类型安全的联合体(union),它可以在运行时存储其模板参数中指定的任意一种类型的值。与传统的union不同,std::variant
知道当前存储的是哪种类型。
2.2 特点
类型安全 :运行时类型检查,避免未定义行为
异常安全 :构造失败时保持有效状态
值语义 :支持拷贝、移动和比较操作
空间效率 :只占用最大类型的空间加上少量元数据
访问者模式支持 :通过std::visit
实现优雅的类型分发
2.3 常用API通用使用模板 创建 1 2 3 4 5 6 7 std::variant<int , std::string, double > v1 (42 ) ;std::variant<int , std::string, double > v2 (std::string("hello" )) ;std::variant<int , std::string, double > v3; v3. emplace <std::string>("world" );
查询 1 2 3 4 5 6 7 8 size_t index = v1. index ();bool is_int = std::holds_alternative <int >(v1);bool is_valid = !v1. valueless_by_exception ();
访问 1 2 3 4 5 6 7 8 9 10 11 12 13 int value = std::get <int >(v1);int value2 = std::get <0 >(v1); if (auto ptr = std::get_if <int >(&v1)) { int value = *ptr; } std::visit ([](auto && arg) { std::cout << arg << std::endl; }, v1);
操作 1 2 3 4 5 6 7 8 9 v1 = std::string ("new value" ); v1 = 3.14 ; bool equal = (v1 == v2);std::swap (v1, v2);
2.4 std::visit详解 什么是std::visit std::visit
是访问std::variant
内容的推荐方式。它接受一个”访问者”(可调用对象)和一个或多个variant
对象,根据variant
当前存储的类型自动调用相应的处理逻辑。
为什么需要std::visit 由于std::variant
可以存储多种不同类型,我们需要一种安全的方式来处理所有可能的类型。std::visit
确保:
类型安全 :编译器保证所有可能的类型都被处理
性能优化 :编译时生成最优的分发代码
代码简洁 :避免手动的类型检查和转换
std::visit的基本用法 1. 使用lambda表达式
1 2 3 4 5 6 std::variant<int , std::string, double > v = 42 ; std::visit ([](auto value) { std::cout << "值是: " << value << std::endl; }, v);
2. 使用函数对象(重载操作符)
1 2 3 4 5 6 7 8 9 10 11 12 13 struct Printer { void operator () (int i) { std::cout << "整数: " << i << std::endl; } void operator () (const std::string& s) { std::cout << "字符串: " << s << std::endl; } void operator () (double d) { std::cout << "浮点数: " << d << std::endl; } }; std::visit (Printer{}, v);
什么是overload技巧 当我们需要为不同类型提供不同的处理逻辑时,可以使用”overload”技巧。这是一个辅助工具,让我们可以组合多个lambda表达式:
1 2 3 4 5 6 7 8 9 10 template <class ... Ts> struct overload : Ts... { using Ts::operator () ... ; };template <class ... Ts> overload (Ts...) -> overload<Ts...> ;std::visit (overload { [](int i) { std::cout << "整数: " << i << std::endl; }, [](const std::string& s) { std::cout << "字符串: " << s << std::endl; }, [](double d) { std::cout << "浮点数: " << d << std::endl; } }, v);
代码分解 1 2 3 4 template <class ... Ts> struct overload : Ts... { using Ts::operator () ... ; };
1. 模板结构体定义
template<class… Ts> :这是一个可变参数模板, Ts… 表示可以接受任意数量的类型参数
struct overload : Ts… :这个结构体通过 多重继承 继承了所有传入的类型 Ts…
using Ts::operator()… :这是 C++17 的 using 声明包展开 ,它将所有基类的 operator() 函数都引入到当前作用域中
1 2 template <class ... Ts> overload (Ts...) -> overload<Ts...> ;
2. 推导指引(Deduction Guide)
这是 C++17 引入的 类模板参数推导指引
它告诉编译器:当你看到 overload(参数…) 这样的构造时,应该推导出 overload<参数类型…>
这样就可以不用显式指定模板参数,让编译器自动推导
工作原理 当你传入多个 lambda 表达式时:
1 2 3 4 5 auto visitor = overload { [](int i) { std::cout << "整数: " << i << std::endl; }, [](const std::string& s) { std::cout << "字符串: " << s << std::endl; }, [](double d) { std::cout << "浮点数: " << d << std::endl; } };
编译器会:
推导出每个 lambda 的类型(比如 Lambda1 , Lambda2 , Lambda3 )
创建 overload<Lambda1, Lambda2, Lambda3>
这个类继承了所有 lambda 类型
通过 using Ts::operator()… 将所有 lambda 的 operator() 都引入
最终得到一个拥有多个重载 operator() 的对象
std::visit返回值 1 2 3 4 5 6 7 8 std::string result = std::visit (overload { [](int i) { return "数字: " + std::to_string (i); }, [](const std::string& s) { return "文本: " + s; }, [](double d) { return "小数: " + std::to_string (d); } }, v); std::cout << result << std::endl;
2.5 具体使用示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #include <iostream> #include <variant> #include <vector> template <typename ... Ts> struct overload : Ts... { using Ts::operator () ... ; }; template <typename ... Ts> overload (Ts...) -> overload<Ts...>;int f1 () { return 1 ; }bool f2 () { return false ; }std::vector<int > f3 () { return {1 , 2 , 3 }; }std::string f4 () { return "hello" ; }struct Visit { void operator () (int i) { std::cout << "int: " << i << std::endl; } void operator () (bool b) { std::cout << "bool: " << b << std::endl; } void operator () (const std::string &s) { std::cout << "string: " << s << std::endl; } void operator () (const std::vector<int > &v) { std::cout << "vector: " ; for (auto &i : v) { std::cout << i << " " ; } } }; struct Visit_Return { std::string operator () (int i) { return "处理了整数: " + std::to_string (i); } std::string operator () (bool b) { return "处理了布尔值: " ; } std::string operator () (const std::string &s) { return "处理了字符串: " + s; } std::string operator () (const std::vector<int > &v) { std::string result = "处理了向量: " ; for (auto &i : v) { result += std::to_string (i) + " " ; } return result; } }; int main () { std::variant<int , std::string> v; v = 42 ; std::cout << std::get <int >(v) << std::endl; v.emplace <std::string>("hello" ); std::cout << std::get <std::string>(v) << std::endl; std::variant<int , bool , std::string, std::vector<int >> v2; v2 = f1 (); std::visit (Visit{}, v2); auto res = std::visit (Visit_Return{}, v2); std::cout << res << std::endl; std::visit (overload{[](int i) { std::cout << "int: " << i << std::endl; }, [](bool b) { std::cout << "bool: " << b << std::endl; }, [](const std::string &s) { std::cout << "string: " << s << std::endl; }, [](const std::vector<int > &v) { std::cout << "vector: " ; for (auto &i : v) { std::cout << i << " " ; } }}, v2); return 0 ; }
四、std::optional - 解决空/有效语义 1. 概述 std::optional
(C++17)是一个模板类,用于表示可能存在也可能不存在的值。它提供了类型安全的空值处理机制,避免了传统指针的空指针解引用问题。
2. 特点
类型安全 :编译时检查,避免空指针解引用
明确语义 :清楚表达值可能不存在的情况(std::nullopt)
零开销抽象 :性能接近原生类型
链式操作 :支持函数式编程风格(C++23)
异常安全 :提供安全的值访问方式
3. 常用API通用模板 构造 1 2 3 4 5 6 std::optional<int > opt1; std::optional<std::string> opt8 = std::make_optional <std::string>("hello" );
基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 std::optional<int > opt = 42 ; bool has_val1 = opt.has_value (); bool has_val3 = opt ? true : false ; int val1 = opt.value (); int val2 = *opt; int val3 = opt.value_or (0 ); int * ptr = &(*opt); const int * const_ptr = &opt.value (); opt = 100 ; opt = std::nullopt ; opt.reset (); opt.emplace (200 ); opt.emplace (); std::optional<int > other = 300 ; opt.swap (other); std::swap (opt, other);
数据处理-基于链式调用风格(C++23) transform() 操作 - 返回有效值的时候调用
1 2 template <class F>constexpr auto transform (F&& f) && -> std::optional<std::invoke_result_t <F, T&&>> ;
参数 :F&& f
- 可调用对象,接受T类型参数,返回任意类型U
返回值 :用户返回optional内部类型,transform自动包装成一个optional
功能 :对包含的值应用变换函数,如果optional为空则保持空状态
调用案例 :auto doubled = opt.transform([](int x) { return x * 2; });
说明 :将整数值乘以2,如果opt为空则doubled也为空
and_then() 操作 - 返回有效值的时候调用 ,可以返回无效值
1 constexpr auto and_then (F&& f) && -> std::invoke_result_t <F, T&&> ;
参数 :F&& f
- 可调用对象,接受T类型参数,返回std::optional<U>
类型
返回值 :函数返回的std::optional<U>
类型
功能 :对包含的值应用返回optional的函数,实现条件性链式操作
调用案例 :auto result = opt.and_then([](int x) -> std::optional<int> { return x > 0 ? std::optional<int>{x} : std::nullopt; });
说明 :只有当值大于0时才保留,否则返回空optional
or_else() 操作 - 返回无效值的时候调用
1 2 template <class F>constexpr std::optional or_else (F&& f) && ;
参数 :F&& f
- 可调用对象,无参数,返回std::optional<T>
类型
返回值 :函数返回的std::optional<T>
类型
功能 :当optional为空时提供替代值,有值时直接返回原值
调用案例 :auto fallback = empty_opt.or_else([]() { return std::make_optional(42); });
说明 :当empty_opt为空时,返回包含42的optional
4. 简单使用代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <iostream> #include <optional> std::optional<int > f (int x) { if (x > 0 ) { return x; } return std::nullopt ; } int main () { auto res1 = f (10 ); if (res1. has_value ()) { std::cout << res1. value () << std::endl; } else { std::cout << "res1 is nullopt" << std::endl; } auto res2 = f (-1 ).value_or (-100 ); std::cout << res2 << std::endl; auto res3 = f (-1 ).or_else ([]() -> std::optional<int > { std::cout << " res 3 is nullopt" ; return std::nullopt ; }); auto res4 = f (10 ).transform ([](int x) { return x + 1 ; }); if (res4. has_value ()) { std::cout << res4. value () << std::endl; } else { std::cout << "res3 is nullopt" << std::endl; } auto res5 = f (10 ).and_then ([](int x) -> std::optional<int > { if (x > 10 ) return std::optional <int >(x); return std::nullopt ; }); if (res5. has_value ()) { std::cout << res4. value () << std::endl; } else { std::cout << "res4 is nullopt" << std::endl; } auto res6 = f (11 ) .transform ([](int x) { return x + 1 ; }) .and_then ([](int x) -> std::optional<int > { if (x > 10 ) { return std::optional <int >(x); } return std::nullopt ; }) .or_else ([]() -> std::optional<int > { std::cout << "res5 is nullopt" << std::endl; return std::nullopt ; }); std::cout << "res6: " << res6. value () << std::endl; return 0 ; }
五、std::expected - 解决正确错误语义 1. 概述 std::expected
(C++23)是一个模板类,用于表示可能成功(包含期望值)或失败(包含错误信息)的操作结果。模板参数为std::expected<T, E>
,其中T是成功值类型,E是错误类型。
2. 特点
明确的错误处理 :强制处理错误情况,编译时保证
类型安全 :错误和成功值都有明确的类型
性能优化 :避免异常的性能开销,零开销抽象
函数式编程支持 :支持链式操作和组合
丰富的错误信息 :可以携带详细的错误上下文
3. 常用API通用模板 构造 1 2 3 4 5 6 7 8 9 10 11 12 13 #include <expected> #include <string> std::expected<int , std::string> exp2{42 }; std::expected<int , std::string> exp3 = std::unexpected ("Error message" ); std::expected<int , std::string> exp4{std::unexpect, "Error message" }; std::expected<std::string, int > exp5{std::in_place, "success" }; std::expected<std::string, int > exp6{std::unexpect, 404 };
基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 std::expected<int , std::string> exp = 42 ; bool has_value = exp.has_value (); bool is_success = static_cast <bool >(exp); int val1 = exp.value (); int val2 = *exp; int val3 = exp.value_or (0 ); if (!exp) { const std::string& error = exp.error (); } exp = 100 ; exp = std::unexpected ("New error" ); exp.emplace (200 );
数据处理 transform() 操作
1 2 3 4 template <class F>constexpr auto transform (F&& f) const & -> std::expected<std::invoke_result_t <F, const T&>, E> ;template <class F>constexpr auto transform (F&& f) && -> std::expected<std::invoke_result_t <F, T&&>, E> ;
参数 :F&& f
- 可调用对象,接受T类型参数,返回任意类型U
返回值 :std::expected<U, E>
,其中U是变换函数的返回类型
功能 :对成功值应用变换函数,错误值保持不变
调用案例 :auto doubled = exp.transform([](int x) { return x * 2; });
说明 :成功时将值乘以2,失败时错误信息不变
and_then() 操作
1 2 3 4 template <class F>constexpr auto and_then (F&& f) const & -> std::invoke_result_t <F, const T&> ;template <class F>constexpr auto and_then (F&& f) && -> std::invoke_result_t <F, T&&> ;
参数 :F&& f
- 可调用对象,接受T类型参数,返回std::expected<U, E>
类型
返回值 :函数返回的std::expected<U, E>
类型
功能 :对成功值应用返回expected的函数,可以引入新的错误
调用案例 :auto result = exp.and_then([](int x) -> std::expected<int, std::string> { return x > 0 ? x : std::unexpected("Invalid"); });
说明 :只有当值大于0时才成功,否则返回错误
or_else() 操作
1 2 3 4 template <class F>constexpr auto or_else (F&& f) const & -> std::invoke_result_t <F, const E&> ;template <class F>constexpr auto or_else (F&& f) && -> std::invoke_result_t <F, E&&> ;
参数 :F&& f
- 可调用对象,接受E类型参数,返回std::expected<T, F>
类型
返回值 :函数返回的std::expected<T, F>
类型
功能 :错误时提供恢复逻辑,成功值直接传播
调用案例 :auto recovered = error_exp.or_else([](const std::string& err) -> std::expected<int, std::string> { return 42; });
说明 :当包含错误时,返回默认值42作为恢复
transform_error() 操作
1 2 3 4 template <class F>constexpr auto transform_error (F&& f) const & -> std::expected<T, std::invoke_result_t <F, const E&>> ;template <class F>constexpr auto transform_error (F&& f) && -> std::expected<T, std::invoke_result_t <F, E&&>> ;
参数 :F&& f
- 可调用对象,接受E类型参数,返回任意类型F
返回值 :std::expected<T, F>
,其中F是错误变换函数的返回类型
功能 :对错误值应用变换函数,成功值保持不变
调用案例 :auto formatted = exp.transform_error([](const std::string& err) { return "Error: " + err; });
说明 :为错误信息添加前缀,成功值不受影响
4. 简单使用代码 #include <expected>
#include <iostream>
enum class MYERROR { ERR1 = 1, ERR2 = 2, ERR3 = 3 };
std::expected<int, MYERROR> f(int x) {
if (x > 10)
return x;
else if (x > 0 && x <= 10)
return std::unexpected(MYERROR::ERR1);
else if (x == 0)
return std::unexpected(MYERROR::ERR2);
else
return std::unexpected(MYERROR::ERR3);
}
int main() {
auto res1 = f(0);
if (res1.has_value()) {
std::cout << res1.value() << std::endl;
} else {
std::cout << "error: " << static_cast<int>(res1.error()) << std::endl;
}
auto res2 = f(5).value_or(-100);
std::cout << res2 << std::endl;
auto res3 =
f(120)
.and_then([](int x) -> std::expected<int, MYERROR> { return x + 1; })
.value_or(0);
std::cout << res3 << std::endl;
auto res5 = f(1).or_else([](MYERROR err) -> std::expected<int, MYERROR> {
std::cout << "error: " << static_cast<int>(err) << std::endl;
return {};
});
return 0;
}```