闭关之 C++ 函数式编程笔记(四):monad 和 模板元编程
目录
- 第十章 monad
- 注意
- 10.1 仿函数并不是以前的仿函数
- 10.1.1 处理可选值
- 10.2 monad: 更强大的仿函数
- 10.3 基本的例子
- 10.4 range 与 monad 的嵌套使用
- 10.5 错误处理
- 10.5.1 std::optional<T> 作为 monad
- 10.5.2 expected<T, E> 作为 monad
- 10.5.3 try monad
- 10.6 monad 状态处理
- 10.7 并发和延续 monad
- 10.7.1 future 作为 monad
- 10.7.2 future 的实现
- 10.8 monad 组合
- 总结
- 第十一章 模板元编程
- 11.1 编译时操作类型
- 11.1.1 推断类型调试
- 11.1.2 编译时的模式匹配
- 11.1.3 提供类型的元信息
- 11.2 编译时检查类型的属性
- 11.3 构造柯里化函数
- 11.3.1 调用所有可用的
- 11.4 DSL 构建块
- 总结
- 关键 API
- 思想
第十章 monad
注意
- 本节所述的内容都是基于 range-v3, 而不是 C++20 的 ranges
- C++20 的 ranges 与 range-v3 有很大差别,而且有很多 range-v3 的特性没有被支持
- 如果要学 C++20 的 ranges 这章不太合适,但是如果想学 range 的思想,这章还是不错的
10.1 仿函数并不是以前的仿函数
- 仿函数定义
- 如果一个类模板 F 包含一个transform(或 map)函数,则称 F 为仿函数
- transform 函数必须遵守以下两条规定
- 仿函数 transform 转换是等价转换,返回相同的仿函数实例
f | transform([](auto value) { return value; }) == f
- 先用一个函数对仿函数进行转换,然后用另一个函数进行转换,等价于组合这两个函数对仿函数进行转换
f | transform(t1) | transform(t2) ==
f | transform([=](auto value) { return t2(t1(value)); })
- 仿函数 transform 转换是等价转换,返回相同的仿函数实例
- 与 range 中的
std::transform
和view::transform
类似- STL 中通用集合和 range 都是仿函数 (functor)
- 它们都是包装类型 (wrapper types)
- transform 函数必须遵守以下两条规定
- 如果一个类模板 F 包含一个transform(或 map)函数,则称 F 为仿函数
10.1.1 处理可选值
std::optional
类型是一个基本的仿函数- 为
std::optional
定义转换函数示例template <typename T1, typename F> auto transform(const std::optional<T1> &opt, F f)-> decltype(std::make_optional(f(opt.value()))) {if (opt) {return std::make_optional(f(opt.value()));} else {return {};} }std::string user_full_name(const std::string& login) {return "full_name"; } std::string to_html(const std::string& text) {return "html"; } std::string current_login = "login"; auto html = transform(transform(current_login, user_full_name), to_html);
- 书上的这个示例感觉有些不严谨,transform 返回值永远不为 nullotp
- 但是给了我们自定义转换函数的思想
- 为
std::optional
定义 range 示例- Code_9_1_6
10.2 monad: 更强大的仿函数
- 仿函数允许对包装类型的值进行转换,但有一个严重缺陷
- 在每次转换的时候,包装类型返回值会被嵌套
- 也就是说,当第一次转换的返回值会被第二个转换函数再次包装
- 转换函数越多,嵌套越多
- monad 作用就是解决上述问题的
- monad M 是一个定义了附加函数的仿函数
- 去掉一层嵌套的函数
- j o i n : M < M < T > > − > M < T > join: M<M<T>> -> M<T> join:M<M<T>>−>M<T>
- Code
join(transform(join(transform(login,user_full_name)),to_html));
- range 版本 Code
auto login_as_range = as_range(current_login); login_as_range | ranges::v3::view::transform_fn(user_full_name)| ranges::v3::view::join_fn| ranges::v3::view::transform_fn(to_html)| ranges::v3::view::join_fn;
- monad M 是一个定义了附加函数的仿函数
- 可以进行简化
- 定义 monad 的常用方式
- monad M 是一个包装类型,它包含一个构造函数和一个组合 transform 个 join 的 mbind 函数
- 构造函数
- 把 T 类型的值构造成 M 实例的函数
construct : T -> M<T>
- mbind 函数
- 其实就是一个 bind 函数,只是为了避免与 std::bind 混淆
mbind: (M<T1>, T1 -> M<T2>) -> M<T2>
- 构造函数
- monad M 是一个包装类型,它包含一个构造函数和一个组合 transform 个 join 的 mbind 函数
- 定义 monad 的常用方式
- 所有的 monad 都是仿函数
- 和仿函数一样, monad 也有几个条件(使用 monad, 这些也不是必须的)
- 如果有一个函数 f : T 1 − > M < T 2 > f: T1->M<T2> f:T1−>M<T2> 和一个 T 1 T1 T1 类型的值,把这个值包装成 monad M,并与函数 f f f 绑定,与直接对调用函数 f f f 是一样的
- m b i n d ( c o n s t r u c t ( a ) , f ) = = f ( a ) mbind(construct(a), f) == f(a) mbind(construct(a),f)==f(a)
- 如果把一个值与构造函数绑定,则得到与原来相同的值
mbind(m, construct) == m
- 定义 mbind 操作的关联性
mbind(mbind(m, f), g) == mbind(m, [](auto x){ return mbind(f(x), g) })
- 如果有一个函数 f : T 1 − > M < T 2 > f: T1->M<T2> f:T1−>M<T2> 和一个 T 1 T1 T1 类型的值,把这个值包装成 monad M,并与函数 f f f 绑定,与直接对调用函数 f f f 是一样的
10.3 基本的例子
- 用
std::vector
构造一个仿函数- 需要做两项检查
- 仿函数是一个带有一个模板参数的类模板
- 需要一个 transform 函数,它接收一个向量,对向量元素进行转换的函数
- 转换函数将返回转换后元素的向量
//把给定的向量看作 range,对其中每个元素调用f进行转换 //就像仿函数要求的一样,函数 f 返回一个向量 template <typename T, typename F> auto transform(const std::vector<T> xs, F f) {return xs | ranges::v3::view::transform_fn(T)| ranges::v3::to_vector; }
- 把仿函数转换成 monad
- 需要构造函数和 mbind 函数
- 构造函数接收一个值,并用它构造一个向量
template <typename T> std::vector<T> make_vector(T&& value) {return { std::forward<T>(value) }; }
- mbind 函数
- 使用 transform 加 join 是最简单的做法
- 需要一个能把多个值映射成monad实例的函数
template <typename T, typename F> //f 接收一个T类型的值,返回 T 类型或其他类型的vector auto mbind(const std::vector<T>& xs, F f) {//调用 f 产生一个向量类型的 range, 可以把它转换成向量的向量auto transformed =xs | ranges::v3::view::transform_fn(f)| ranges::v3::to_vector;//所需要的不是向量的向量,而是所有值在一个向量中return transformed| ranges::v3::view::join_fn| ranges::v3::to_vector; }
- 该向量的 mbind 函数不高效,有保存中间向量
- 需要做两项检查
10.4 range 与 monad 的嵌套使用
- mbind 更适合集合类结构
- mbing 对与原集合中的每一个元素,不仅可以产生新元素,而且可以产生任意多的新元素
- mbind 实现的过滤
template <typename C, typename P> auto filter(const C& collection, P predicate) { //依据当前元素是否满足谓词,接收0个或1个元素return collection| mbind([=](auto element) {return ranges::v3::view::single_fn(element)| ranges::v3::view::take_fn( predicate(element) ? 1 : 0);}); }
- range 就是 monad, 所有不仅可以用很酷的方式重新实现 range 转换,而且可以嵌套 range
- range 嵌套就是带有过滤的 transform 或 mbind。
- 因为任意的 monad 都包含这些函数,而不仅仅是 range
- 所以也可以把它们称作 monad 嵌套
- 因为任意的 monad 都包含这些函数,而不仅仅是 range
10.5 错误处理
- 函数式编程中的函数的主要功能
- 唯一的功能
- 计算结果并返回它
- 如果函数执行失败,则返回一个值,或在发生错误时不返回任何值(std::optional)
10.5.1 std::optional 作为 monad
- optional 可以表示有可能出现的缺失值现象,但有一个缺陷
- 如果要使用值的化,要检测它是否存在
- 如果链接更多函数,每次调用都要检查错误
- 可以使用 monad
- 在调用其他函数时将值剥离出来
- monad 可以做:组合函数而无须处理额外上下文信息
- 实现
//指定返回类型,如果没有值则返回{} template <typename T, typename F> auto mbind(const std::optional<T>& opt, F f) -> decltype(f(opt.value())) {if (opt) {//如果包含一个值,则调用 f 对值进行转换//并返回转换结果return f(opt.value());}else {return {};} }
- 如果使用这种方式串联多个函数,就会自动处理错误
- 函数会依次执行,知道有一个函数出现错误
- 如果没有函数执行失败,将返回得到处理结果
std::optional<std::string> current_user_html() {return mbind(mbind(current_login, user_full_name),to_html); }
- 使用管道语法,增加可读性
std::optional<std::string> current_user_html() {return current_login | mbind(user_full_name)| mbind(to_html);); }
10.5.2 expected<T, E> 作为 monad
- expected<T, E> 不但可以处理错误,还能知道发生了什么错误
- 组合 expected monad
template <typename T, typename E, typename F,//f 可能返回不同类型,因此在返回之前,需要类型推断typename Ret = std::invoke_result<F(T)>::type > Ret mbind(const expected<T, E>& exp, F f) {if (!exp) {//如果 exp 包含错误,则继续把它传递下去return Ret::error(exp.error());}return f(exp.value()); }
- 示例
expected<std::_Invoker_strategy, int> cuurent_user_html() {return current_login | mbind(user_full_name)| mbind(to_html); }
10.5.3 try monad
- 把异常包装成 expected monad 的函数
template <typename F,typename Ret = std::invoke_result<F()>::type,typename Exp = expected<Ret, std::exception_ptr> > //函数 f 没有参数,如果要使用参数调用它,可以传递lambda表达式 Exp mtry(F f) {try {//如果没有抛出异常,则返回一个 expected 示例//包含 f 的返回结果return (Exp::success(f());}catch (...) {//如果有异常抛出,则返回一个 expected 实例//包含指向该异常的指针return Exp::error(std::current_exception());} }
- 示例
auto result = mtry([=] {auto users = system.users();if (user.empty()) {throw std::runtime_error("No users");}return users[0];});
- 也可以用另一种方式实现
- 如果函数返回一个包含指向异常指针的 expected 实例,可以创建一个函数,要么返回存储在 expected 对象中的值,要么抛出其中的异常
template <typename T> T get_or_throw(const expected<T, std::exception_ptr>& exp) {if (exp) {return exp.value();}else {std::rethrow_exception(exp.error());} }
10.6 monad 状态处理
- monad 在函数式编程中流行的原因时,它可以以 “纯” 的方式处理包含状态的程序
- 如果要用 monad 或 monad 转换链的方式实现程序,跟踪链条中的每个转换的状态,这就十分有用了
- 使用积类型数据
- 因为和类型只能表示一个值
- 不但要包含值,同时还要包含附加信息(调试日志)
- 示例
template <typename T> class with_log {public:with_log(T value, std::string log = std::string()): m_value(value), m_log(log){}T value() const {return m_value;}std::string log() const{return m_log;} private:std::string m_log;T m_value; }
- mbind 维护日志
template <typename T,typename F,typename Ret = std::invoke_result<F()>::type > Ret mbind(const with_log<T1>& val, F f) {//使用 f 进行转换,返回转换结果和 f 的日志字符串const auto result_with_log = f(val.value());//返回处理结果,但日志不仅仅是 f 的日志,还要与原来的日志进行拼接return Rec(result_with_log.value(), val.log() + result_with_log.log()); }
- 上述记录日志方法和把日志输出到标准输出相比有几个优点
- 可以处理多个平行日志
- 每个链中的转换对应一个日志
- 而不需要特殊的日志组件
- 一个函数根据调用者的不同,可以写出各种日志,而无需指明
- “这个日志写到这里”“那个日志写到那里”
- 使同一异步操作链中的日志记录在一起,而不会与其他操作链中的日志混杂
- 可以处理多个平行日志
10.7 并发和延续 monad
10.7.1 future 作为 monad
- future 对象就是一个 monad
- 它是一个类似容器的东西,可以包含0个或1个结果,这取决于异步操作是否完成
- mbing 可以串联任意多个异步操作
future<std::string> current_user_html() {return current_user() | mbind(user_full_name)| mbind(to_html); }
- 上述代码串联了三个异步操作。每个函数处理前一个函数的结果
- 传递给 mbind 的函数通常称为延续函数 (continuation)
- 定义的 future 值的 monad 称为延续 monad (continuation monad)
10.7.2 future 的实现
std::future
与expected<T, std::exception_ptr>
分 future 类似std::future
不能智能地附加延续函数- 为了延续,增加成员函数
then()
(std::experimental::future
)
then()
与 mbind 类似,但有不同之处- monad 的 bind 接收一个函数,它的参数是普通的值,返回值为 future 对象
- 而
then()
接收的函数要求其参数为一个完成的 future 对象,并返回一个新的 future
- 因此
then()
并不是使 future 变为 monad, 而是使实现 mbind 变得简单 - 示例
template <typename T, typename F> //接收一个函数 f,它可以把类型 T 转换成 future 的实例 future<T2> auto mbind(const std::experimental::future<T>& future, F f) {//接收一个把 future<T> 转换成 future<T2>的函数//在把它传递给函数f之前,需要用lambda表达式提取future中的值return future.then([](std::experimental::future<T> finished) {//不会阻塞任何程序,因为延续函数只有在结果准备好时(或有异常时)//才会被调用return f(finished.get());}); }
10.8 monad 组合
- 首先有如下函数定义
user_full_name: std::string -> M<std::string>
to_html: std::string -> M<std::string>
- 其中
M
代替了optional
、expected
或其他包装类型
- 普通 monad 调用如下
M<std::string> user_htlm(const m<std::string>& login) {return mbind(mbind(login, user_full_name),to_html); }
- 进行普通函数组合时
- 假设有两个函数
f: T1->T2
和g: T2->T3
- 得到一个把 T1 转换成 T3 的函数
- 假设有两个函数
- 使用 monad 组合,则稍有些不同
- 函数不是返回一个普通的值,而是包装在 monad 中
- 因此组合的函数变为
f: T1->M<T2>
和g: T2->M<T3>
- monad 组合函数
template <typename F, typename G> auto mcompose(F f, G g) {return [=](auto value) {return mbind(f(value), g);}; }
- 使用
mcompose()
auto user_html = mcompose(user_full_name, to_html);
- 使用
mcompose()
函数可以编写更简短、更通用的代码 - 如果 monad 的构造函数与任意 monad 的函数组合,则得到函数本身
mcompose(f, construct) == f
mcompose(construct, f) == f
- 根据结合的原则,如果有3个函数f、g、h需要组合,无论先式组合f和g,再把结果与h组合,还是先组合g和h,结果再与f组合,都没关系
mcompose(f, mcompose(g, h)) == mcompose(mcompose(f, g), h)
- 这也称为 Kleisli 组合,通常它与普通函数组合具有相同的属性
总结
- 个人认为这章是本书最核心的内容,需要重复去看
第十一章 模板元编程
- 元函数
- 可以接收两种类型,给出一种类型作为结果,就像函数一样,但操作的不是值,而是自己的类型。这种函数称为元函数
- 使用模板的元编程(或 TMP,模板元编程),有专门的书籍介绍
- 本章集中介绍 C++17 引入的一些支持 TMP 的新特性
11.1 编译时操作类型
- 创建一个元函数,接收一个集合返回集合包含的元素类型
template <typename T> using contained_type_t = decltype(*begin(T()));
contained_type_t
就是一个元函数- 它接收一个参数:类型 T
- 这个元函数将返回包含再 decltype 中的表达式的类型
- 不会在运行时出现错误
- 因为在编译时处理的是类型
- decltype 永远不会执行传递给它的代码,它只返回表达式类型,而不是计算它
- 存在的第一个问题
- 类型 T 必须是可默认构建的
- 可以使用
std::declval<T>()
工具代替构造函数调用<type_traits>
- 它接收任何类型的 T
- 假装创建一个该类型的实例,以便在需要值而不是类型时,用在元函数中
- 需要类型这就是
decltype
- 需要类型这就是
- 可以使用
- 类型 T 必须是可默认构建的
- 元函数使用
template <typename C,typename R = contained_type_t<C>> R sum(const C& collection) {... }
- 模板不知道返回类型,可以使用元函数给出
- 虽然称这些函数为元函数,其实就是使用模板定义的函数
11.1.1 推断类型调试
contained_type_t
第二个问题- 它所做的并不是用户想要的,如果试图使用它,会出现问题
- 检测方法
- 声明一个模板
- 不需要实现
- 当需要检查某个类型时,尝试实例化该模板,编译器将报告错误
- 声明一个模板
- 检查
contained_type_t
推断的类型template <typename T> class error {}; error<contained_type_t<std::vector<std::string>>>();
- 会编译错误 (但是我在VS2022中顺利通过编译,暂时忽略,按照书中的逻辑来)
contained_type_t
推断类型是字符串的常引用类型,而不是用户想要的字符串类型
- 完善修正
contained_type_t
- 移除类型引用部分和 const 修饰符
- 移除 const 和 volatile 修饰符,使用
std::remove_cv_t
元函数 - 移除引用使用
std::remove_reference_t
元函数template <typename T> using contained_type_t = std::remove_cv_t<std::remove_reference_t<decltype(*begin(std::declval<T>()))>>;
- 移除 const 和 volatile 修饰符,使用
- 移除类型引用部分和 const 修饰符
<type_traits>
- 大多数标准元函数都定义在此
- 它包含十几个有用的元函数
- 用于在元程序中操作类型,模拟 if 语句和逻辑操作
- 以后缀
_t
结尾的元函数在 C++14 中引入 - 不带后缀为 C++11 中元函数(笨拙结构)
- 编写与调试元程序的工具是
static_assert
static_assert(std::is_same<int, contained_type_t<std::vector<int>>>(),"std::vector<int> should contain integers");
- 检查编译时的 bool 值,如果为 false,则停止编译
std::is_same
- 表示元相等
- 接收两个类型,相同返回 true, 否则返回 false
11.1.2 编译时的模式匹配
- 自己定义
is_same
元函数- 元函数不能返回 true 或 false 值
- 需要返回
std::true_type
或std::false_type
- 需要返回
- 对于
is_same
有两种情况- 如果给定两种不同类型,则返回
std::false_type
- 如果给定两种类型相同,则返回
std::true_type
- 如果给定两种不同类型,则返回
- 第一种情况实现
template <typename T1, typename T2> struct is_same : std::false_type{};
- 创建了两个参数的元函数,无论 T1 和 T2 是什么情况,它总返回
std::false_type
- 创建了两个参数的元函数,无论 T1 和 T2 是什么情况,它总返回
- 第二种情况
template <typename T> struct is_same<T, T> : std::true_type{};
- 示例
is_same<int, contained_type_t<std::vector<int>>>
- 首先
- 计算 contained_type_t 的结果,结果为 int
- 然后
- 查找所有可用与
<int, int>
的 is_same 的定义,并选择最具体的那个
- 查找所有可用与
- 对文中自定义
is_same
元函数实现的理解- 利用了编译器编译模板时的机制
- 选择更具体地模板进行编译
- 元函数不能返回 true 或 false 值
- 自定义 remove_reference_t 元函数
- 有三种情况
- 给定的类型为非引用类型
- 给定的类型为左值引用
- 给定的类型为右值引用
- 对于第一种情况,应返回未修改的类型
- 而对第二和第三种情况,需要将引用剥离
//通常情况下,remove_reference<T>::type 的类型应该是T //它接收什么类型就返回什么类型 template <typename T> struct remove_reference {using type = T; }; //如果接收左值引用T&,剥离引用返回T template <typename T> struct remove_reference <T&>{using type = T; };//如果接收右值引用T&&,剥离引用返回T template <typename T> struct remove_reference <T&&> {using type = T; };
contained_type_t
使用模板类型别名实现的- 而这里模板结构定义了内嵌的别名,称为 type
- 要使用
remove_reference
元函数获取结果类型就必须创建该类型的模板,并获得嵌套在其中的类型定义typename remove_reference<T>::type
- 这样写比较冗长,可以创建一个方便的 remove_reference_t
- 类似与 C++ 对 type_traits 中元函数的所做操作
template <class T> using remove_reference_t = typename remove_reference<T>::type;
- 有三种情况
11.1.3 提供类型的元信息
- 如果需要查找包含在集合中元素的类型时,最常用的方式是
- 对集合来说,通常将包含的元素类型作为名为 value_type 的内嵌类型定义提供
- 示例
template <typename T, typename E> class expected {public:using value_tyoe = T; };
11.2 编译时检查类型的属性
- 如果涉及处理 value_type 的函数,检查给定的集合是否包含内嵌的 value_type 在进行相应的处理
- 接收任意数目的类型并返回 void 的元函数
template<typename ...> using void_t = void;
- 该元函数的作用不在于它的结果
- 它可以在编译时的 SFINAE 上下文中检查给定类型和表达式的有效性
- SFINAE (substitution failure is not an error, 替代失败例程不是错误)
- SFINAE 是模板重载解析时应用的规则
- 如果用推断的类型替代模板参数失败,编译器不会报错,只是忽略这一重载
- C++17 支持
void_t
- 作用
- 它可以和任意多的类型一起使用,如果有些类型无效,使用 void_t 的重载则被忽略
- 可以很容易地创建一个元函数,检查给定类型是否内嵌
value_type
- 检测类型是否包含
value_type
的元函数//通常情况:假设任意类型都没有内嵌 value_type 类型定义 template <typename C, typename = std::void_t<>> struct has_value_type : std::false_type {}; // 特殊情况,只考虑C::value类型为已存在类型 //(如果C包含内嵌的value_type类型) template <typename C> struct has_value_type<C, std::void_t<typename C::value_type>> : std::true_type {};
- 如果有两个 sum 函数
- 一个处理内嵌
value_type
类型的集合 - 一个处理任何迭代的集合
- 就可以使用
has_value_type
判断使用哪个方法template <typename C> auto sum(const C& collection) {if constexpr (has_value_type(collection)) {return sum_collection(collection);}else {return sum_iterable(collection);} }
- 一个处理内嵌
constexpr-if
- 正常的 if 语句在运行时检查它的类型,并把两个分支设置为可编译的
constexpr-if
要求两个分支都必须为有效的语法,但不会编译两个分支
void_t
不但可以检查类型的有效性,还可以通过 decltype 和 std::declval的帮助对表达式进行检查//通常情况:假设任何类型都不可迭代 template <typename C, typename = std::void_t<>> struct is_iterable : std::false_type {}; //特殊情况:仅考虑C可迭代且其begin迭代器可解引用 template <typename C> struct is_iterable<C,std::void_t<decltype(*begin(std::declval<C>())),decltype(end(std::declval<C>()))> >: std::true_type {};
- 定义完整的 sum 函数
template <typename C> auto sum(const C& collection) {if constexpr (has_value_type(collection)){return sum_collection(collection);}else if constexpr (is_iterable<C>){return sum_iterable(collection);}else {} }
11.3 构造柯里化函数
- 柯里化函数(略)
- 前面章节已经做过笔记
- 柯里化函数需要是有状态的函数对象,需要在
std::tuple
中存储所有捕获的参数- 因此需要使用
std::decay_t
保证类型参数不是引用而是实际的值 - Code_11_3_1
- 因此需要使用
std::is_invocable_v
元函数- 它接收可调用对象类型和参数类型列表
- 返回这个对象可否使用参数列表调用
11.3.1 调用所有可用的
std::invoke
- 第一个参数为可调用对象
- 无论是普通可调用对象还是成员函数指针
- 第二个参数传递可调用对象的参数
- 优势
- 通用代码,不知道可调用对象的确切类型的时候调用它
- 实现接收函数作为参数的高阶函数时,不应使用常规函数调用语法,而应使用
std::invoke
- 第一个参数为可调用对象
std::apply
- 行为与
std::invoke
相似 - 不同之处
- 它接收包含参数的元组 (tuple) 作为参数,而不是接收独立的多个参数
- 行为与
- Code_11_3_1
- 柯里化的这种实现适用于
- 普通函数
- 指向成员函数的指针
- 普通和通用 lambda
- 具有调用操作符的类(通用的和非通用的)
- 甚至对于具有多个不同调用操作符重载的类都是适用的
- 柯里化的这种实现适用于
11.4 DSL 构建块
- 领域特定语言(domain-specific language, DSL)
- 创建 DSL可能如下所示
with(martha) (name = "Martha", surname = "Jones", age = 42);
- 实现并不一定优美,但主要关注点是隐藏主代码的复杂性
- 使主程序逻辑尽量简单,而牺牲大多数人看不见的底层部分
- 思考上面代码的语法
- 称为函数(或类型)的 with
- 因为使用参数 martha 调用它,所以它是个函数
- 调用结果是另一个函数,它应接收任意数目的参数
- 称为函数(或类型)的 with
std::is_invocable
- 检查给定的函数是否可用于特定的参数进行调用
std::is_invocable_r
- 可以检查是否返回期望的类型
- 实现
- Code_11_4_1
- C++ 实现 DSL 提供了方便的支持
- 运算符重载和可变参数模板是两个主要工具
- DSL 的优势
- 主程序代码非常简洁,并可以在不同的事务间进行切换,而不需要修改主程序代码
- 例如,如果需要把所有记录保存到数据库,只需要实现 transaction 类的调用操作符
- 程序的其他部分就可以向数据库保存数据了,而不需要修改主程序的任何代码
- 例如,如果需要把所有记录保存到数据库,只需要实现 transaction 类的调用操作符
- 主程序代码非常简洁,并可以在不同的事务间进行切换,而不需要修改主程序代码
总结
关键 API
- std::declval()
- decltype()
- std::remove_cv_t()
- std::remove_reference_t()
- static_assert()
- std::is_same()
- std::true_type
- std::false_type
- void_t
- has_value_type
- constexpr-if
- std::decay_t
- std::invoke
- std::apply
- std::is_invocable
- std::is_invocable_r
思想
- 柯里化函数 Code_11_3_1
- DSL Code_11_4_1
闭关之 C++ 函数式编程笔记(四):monad 和 模板元编程相关推荐
- C++模板元编程 入门简介
最近一直在看STL和Boost,源码里边好多涉及到模板元编程技术,简单了解一下,备忘(Boost Python中的涉及模板元的部分重点关注一下). 范例引入 // 主模板 template<in ...
- C++ 模板元编程简介
文章目录 1.概述 2.模板元编程的作用 3.模板元编程的组成要素 4.模板元编程的控制逻辑 4.1 if 判断 4.2 循环展开 4.3 switch/case 分支 5.特性.策略与标签 6.小结 ...
- MSSQL编程笔记四 解决count distinct多个字段的方法
MSSQL编程笔记四 解决count distinct多个字段的方法 参考文章: (1)MSSQL编程笔记四 解决count distinct多个字段的方法 (2)https://www.cnblog ...
- 基于C++11模板元编程实现Scheme中的list及相关函数式编程接口
前言 本文将介绍如何使用C++11模板元编程实现Scheme中的list及相关函数式编程接口,如list,cons,car,cdr,length,is_empty,reverse,append,map ...
- C++ 模板元编程的应用有哪些,意义是什么?
https://www.cnblogs.com/liangliangh/p/4219879.html 为了谈应用,先谈谈使命.模板元编程的根在模板.模板的使命很简单:为自动代码生成提供方便.提高程序员 ...
- 最好的 C++ 模板元编程干货!
链接 | https://www.cnblogs.com/liangliangh/p/4219879.html 所谓元编程就是编写直接生成或操纵程序的程序,C++ 模板给 C++ 语言提供了元编程的能 ...
- 现代C++模板元编程基础
元函数的基础介绍 C++的模板元编程是函数式编程,所以函数是一等公民.一切在编译期间执行的函数都可以称为元函数.元函数有struct和constexpr两种定义方式,前者是一直使用的,后者是C++11 ...
- C++11模板元编程—std::enable_if使用说明
std::enable_if 顾名思义,满足条件时类型有效.作为选择类型的小工具,其广泛的应用在 C++ 的模板元编程中.它的定义也非常的简单: // STRUCT TEMPLATE enable_i ...
- C++高阶必会操作--模板元编程
泛型编程大家应该都很熟悉了,主要就是利用模板实现"安全的宏",而模板元编程区别于我们所知道的泛型编程,它是一种较为复杂的模板,属于C++的高阶操作了,它最主要的优点就在于把计算过程 ...
最新文章
- vue 写兼容ios的毫秒级刷新时间戳
- 35所大学获批新增「人工智能」本科专业,工学学位、四年制
- mysql手工注入——盲注
- pycharm new project变成灰色
- Halcon例程详解(激光三角系统标定)—— calibrate_sheet_of_light_calplate.hdev
- Java线程的不同状态
- 从文本分类问题中的特征词选择算法追踪如何将数学知识,数学理论迁移到实际工程中去...
- mysql+使用swap_MySQL避免使用SWAP
- 【2016年第2期】大数据背景下的治理现代化:何以可能与何以可为(下)
- 分子进化和系统发育的基础知识
- PHP 报错 Use of undefined constant prop_values - ass...
- c语言程序设计实践教程编程题8.3,C语言程序设计教程(21世纪计算机科学与技术实践型教程)...
- CodeVS 1031 质数环(DP)
- EXCEL调用REFPROP方法
- 格式html载入矢量图片,Web前端矢量小图标的使用方法
- Vscode运行Demo程序出现错误
- javascript表单三级联动
- 小马哥----高仿苹果6S A235刷机拆机图与开机界面图 真八核6735芯片 精仿系列机
- mySql | Error: ER_DATA_TOO_LONG: Data too long for column 'base_info' at row 1
- 世界计算机科学大会,【计算机视觉】世界三大顶级会议介绍
热门文章
- leetcode_605. 种花问题
- 数字图像处理: 一 (上交)
- 函数对象的方法、argument、Date对象、Math、包装类、正则表达式
- win8.1 cygwin编译java轻量虚拟机avian
- dhm-echarts图表架构与说明书
- 不花一分钱,利用免费电脑软件将视频MV变成歌曲音频MP3
- Intel CPU 地址空间总结
- 对[我所认识的BIOS]系列 -- CPU的第一条指令 一文扩充(II):从FDF到Bios Rom image
- 笔记本自动重启原因总结
- 奥艺大会 | “OLYMP‘ARTS中国设计奖”在2023米兰设计周发布