目录

  • 第十章 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)); })
      • 与 range 中的 std::transformview::transform 类似
        • STL 中通用集合和 range 都是仿函数 (functor)
        • 它们都是包装类型 (wrapper types)

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 的常用方式

      • monad M 是一个包装类型,它包含一个构造函数和一个组合 transform 个 join 的 mbind 函数

        • 构造函数

          • 把 T 类型的值构造成 M 实例的函数
          • construct : T -> M<T>
        • mbind 函数
          • 其实就是一个 bind 函数,只是为了避免与 std::bind 混淆
          • mbind: (M<T1>, T1 -> M<T2>) -> M<T2>
  • 所有的 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) })

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 嵌套

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::futureexpected<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 代替了 optionalexpected 或其他包装类型
  • 普通 monad 调用如下
    M<std::string> user_htlm(const m<std::string>& login)
    {return mbind(mbind(login, user_full_name),to_html);
    }
    
  • 进行普通函数组合时
    • 假设有两个函数 f: T1->T2g: 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
    • 元函数使用
      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>()))>>;
        
  • <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_typestd::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
    • 第二种情况
      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 元函数实现的理解
      • 利用了编译器编译模板时的机制
      • 选择更具体地模板进行编译
  • 自定义 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 调用它,所以它是个函数
      • 调用结果是另一个函数,它应接收任意数目的参数
  • std::is_invocable
    • 检查给定的函数是否可用于特定的参数进行调用
  • std::is_invocable_r
    • 可以检查是否返回期望的类型
  • 实现
    • Code_11_4_1
  • C++ 实现 DSL 提供了方便的支持
    • 运算符重载和可变参数模板是两个主要工具
  • DSL 的优势
    • 主程序代码非常简洁,并可以在不同的事务间进行切换,而不需要修改主程序代码

      • 例如,如果需要把所有记录保存到数据库,只需要实现 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 和 模板元编程相关推荐

  1. C++模板元编程 入门简介

    最近一直在看STL和Boost,源码里边好多涉及到模板元编程技术,简单了解一下,备忘(Boost Python中的涉及模板元的部分重点关注一下). 范例引入 // 主模板 template<in ...

  2. C++ 模板元编程简介

    文章目录 1.概述 2.模板元编程的作用 3.模板元编程的组成要素 4.模板元编程的控制逻辑 4.1 if 判断 4.2 循环展开 4.3 switch/case 分支 5.特性.策略与标签 6.小结 ...

  3. MSSQL编程笔记四 解决count distinct多个字段的方法

    MSSQL编程笔记四 解决count distinct多个字段的方法 参考文章: (1)MSSQL编程笔记四 解决count distinct多个字段的方法 (2)https://www.cnblog ...

  4. 基于C++11模板元编程实现Scheme中的list及相关函数式编程接口

    前言 本文将介绍如何使用C++11模板元编程实现Scheme中的list及相关函数式编程接口,如list,cons,car,cdr,length,is_empty,reverse,append,map ...

  5. C++ 模板元编程的应用有哪些,意义是什么?

    https://www.cnblogs.com/liangliangh/p/4219879.html 为了谈应用,先谈谈使命.模板元编程的根在模板.模板的使命很简单:为自动代码生成提供方便.提高程序员 ...

  6. 最好的 C++ 模板元编程干货!

    链接 | https://www.cnblogs.com/liangliangh/p/4219879.html 所谓元编程就是编写直接生成或操纵程序的程序,C++ 模板给 C++ 语言提供了元编程的能 ...

  7. 现代C++模板元编程基础

    元函数的基础介绍 C++的模板元编程是函数式编程,所以函数是一等公民.一切在编译期间执行的函数都可以称为元函数.元函数有struct和constexpr两种定义方式,前者是一直使用的,后者是C++11 ...

  8. C++11模板元编程—std::enable_if使用说明

    std::enable_if 顾名思义,满足条件时类型有效.作为选择类型的小工具,其广泛的应用在 C++ 的模板元编程中.它的定义也非常的简单: // STRUCT TEMPLATE enable_i ...

  9. C++高阶必会操作--模板元编程

    泛型编程大家应该都很熟悉了,主要就是利用模板实现"安全的宏",而模板元编程区别于我们所知道的泛型编程,它是一种较为复杂的模板,属于C++的高阶操作了,它最主要的优点就在于把计算过程 ...

最新文章

  1. vue 写兼容ios的毫秒级刷新时间戳
  2. 35所大学获批新增「人工智能」本科专业,工学学位、四年制
  3. mysql手工注入——盲注
  4. pycharm new project变成灰色
  5. Halcon例程详解(激光三角系统标定)—— calibrate_sheet_of_light_calplate.hdev
  6. Java线程的不同状态
  7. 从文本分类问题中的特征词选择算法追踪如何将数学知识,数学理论迁移到实际工程中去...
  8. mysql+使用swap_MySQL避免使用SWAP
  9. 【2016年第2期】大数据背景下的治理现代化:何以可能与何以可为(下)
  10. 分子进化和系统发育的基础知识
  11. PHP 报错 Use of undefined constant prop_values - ass...
  12. c语言程序设计实践教程编程题8.3,C语言程序设计教程(21世纪计算机科学与技术实践型教程)...
  13. CodeVS 1031 质数环(DP)
  14. EXCEL调用REFPROP方法
  15. 格式html载入矢量图片,Web前端矢量小图标的使用方法
  16. Vscode运行Demo程序出现错误
  17. javascript表单三级联动
  18. 小马哥----高仿苹果6S A235刷机拆机图与开机界面图 真八核6735芯片 精仿系列机
  19. mySql | Error: ER_DATA_TOO_LONG: Data too long for column 'base_info' at row 1
  20. 世界计算机科学大会,【计算机视觉】世界三大顶级会议介绍

热门文章

  1. leetcode_605. 种花问题
  2. 数字图像处理: 一 (上交)
  3. 函数对象的方法、argument、Date对象、Math、包装类、正则表达式
  4. win8.1 cygwin编译java轻量虚拟机avian
  5. dhm-echarts图表架构与说明书
  6. 不花一分钱,利用免费电脑软件将视频MV变成歌曲音频MP3
  7. Intel CPU 地址空间总结
  8. 对[我所认识的BIOS]系列 -- CPU的第一条指令 一文扩充(II):从FDF到Bios Rom image
  9. 笔记本自动重启原因总结
  10. 奥艺大会 | “OLYMP‘ARTS中国设计奖”在2023米兰设计周发布