C++没有Y Combinator?使用 C++ 实现 Y Combinator(中英双语)

C++ 中并没有直接内置的 Y Combinator,但通过现代 C++ 特性(如 lambda 表达式std::function),我们可以实现一个类似 Y Combinator 的功能。

下面我们来详细讲解如何在 C++ 中实现 Y Combinator。


使用 C++ 实现 Y Combinator

目标

我们希望实现一个支持递归的匿名函数,而不需要显式命名递归函数。关于Y combinator的概念,可以参考笔者的另一篇博客:理解 Y Combinator:函数式编程中的递归技巧(中英双语)

实现步骤
  1. 基础概念
    在 C++ 中,lambda 表达式默认是无法直接递归调用自己的,因为它无法在定义时引用自己的名字。为了实现递归,我们需要一种方式让 lambda 表达式能够间接调用自身。

  2. Y Combinator 模板实现
    我们可以通过一个辅助的类或函数,把递归的能力注入到 lambda 表达式中。


示例代码

以下是一个通用的 Y Combinator 实现:(代码解析请看下文

#include <iostream>
#include <functional>template <typename Func>
class YCombinator {
public:explicit YCombinator(Func&& func) : func_(std::forward<Func>(func)) {}template <typename... Args>auto operator()(Args&&... args) const {// 将函数本身作为参数传递给 lambda,允许递归return func_(*this, std::forward<Args>(args)...);}private:Func func_;
};// 帮助函数:创建 Y Combinator
template <typename Func>
auto make_y_combinator(Func&& func) {return YCombinator<std::decay_t<Func>>(std::forward<Func>(func));
}

使用示例:计算阶乘

以下是如何使用 Y Combinator 计算阶乘的例子:

#include <iostream>
#include <functional>// 引入 Y Combinator 模板
template <typename Func>
class YCombinator {
public:explicit YCombinator(Func&& func) : func_(std::forward<Func>(func)) {}template <typename... Args>auto operator()(Args&&... args) const {return func_(*this, std::forward<Args>(args)...);}private:Func func_;
};template <typename Func>
auto make_y_combinator(Func&& func) {return YCombinator<std::decay_t<Func>>(std::forward<Func>(func));
}int main() {// 定义阶乘逻辑auto factorial = make_y_combinator([](auto self, int n) -> int {if (n == 0) {return 1;} else {return n * self(n - 1); // 使用 `self` 实现递归}});// 测试阶乘std::cout << "Factorial of 5: " << factorial(5) << std::endl; // 输出 120return 0;
}

工作原理解析

  1. YCombinator

    • YCombinator 是一个高阶函数包装器,它将递归逻辑注入到 lambda 表达式中。
    • 内部的 func_ 存储了实际的 lambda 表达式。
    • operator() 实现了调用逻辑,同时将自身 (*this) 作为参数传递给 lambda 表达式,允许 lambda 表达式递归调用。
  2. 递归逻辑
    factorial 中:

    auto factorial = make_y_combinator([](auto self, int n) -> int {if (n == 0) {return 1;} else {return n * self(n - 1);}
    });
    
    • self 是递归调用的关键,表示当前的递归函数。
    • 使用 self(n - 1) 代替传统的函数名调用,解决了匿名函数无法直接调用自己的问题。

优点

  • 通用性YCombinator 可以为任何递归逻辑提供支持,而不仅仅是阶乘。
  • 现代 C++ 风格:利用了 C++11/14/17 的 lambda 和模板特性,代码简洁且高效。

扩展:其他递归问题

Fibonacci 数列

使用相同的方法,我们可以定义 Fibonacci 数列:

auto fibonacci = make_y_combinator([](auto self, int n) -> int {if (n <= 1) {return n;} else {return self(n - 1) + self(n - 2);}
});
std::cout << "Fibonacci of 10: " << fibonacci(10) << std::endl; // 输出 55

总结

虽然 C++ 没有直接支持 Y Combinator,但通过使用现代 C++ 特性,我们可以优雅地实现一个通用的递归解决方案。这个实现既具有理论意义,也能在实践中帮助我们更深入地理解递归和高阶函数的机制。

解析上述C++代码

这段代码的关键在于 operator() 的实现,以及它如何通过 make_y_combinator 提供递归能力。我们分别解析以下几个核心点:


1. operator() 是如何工作的?

operator() 的定义

YCombinator 类中,operator() 被定义为一个模板方法:

template <typename... Args>
auto operator()(Args&&... args) const {return func_(*this, std::forward<Args>(args)...);
}
  • 它允许 YCombinator 的实例像普通函数一样被调用。
  • operator() 被调用时,它将当前对象(*this,即 YCombinator 自身)作为参数传递给存储的递归函数 func_,从而实现递归能力。
调用位置
  1. make_y_combinator 返回了一个 YCombinator 实例。

    auto factorial = make_y_combinator([](auto self, int n) -> int {if (n == 0) {return 1;} else {return n * self(n - 1);}
    });
    
    • factorial 是一个 YCombinator 实例。
  2. 当调用 factorial(5) 时:

    • 会调用 YCombinatoroperator() 方法。
    • operator() 将自身传入 lambda,支持递归调用。

2. std::forward 的作用

定义

std::forward 是 C++11 引入的模板工具,用于保持参数的“值类别”(左值或右值)属性。

具体作用

operator() 中:

return func_(*this, std::forward<Args>(args)...);
  • Args&&... args万能引用,它可以接受左值或右值参数。
  • std::forward<Args>(args)... 会根据 args 的实际类型(左值或右值)转发给 func_,确保传递时不会改变参数的值类别。
为什么需要 std::forward
  • 如果直接传递 args...,左值可能被误认为右值,导致性能问题或语义错误。
  • std::forward 保证了递归函数的参数传递是高效且正确的。
例子
template <typename T>
void print(T&& value) {forward_example(std::forward<T>(value)); // 确保原样传递
}
  • 如果 value 是左值,std::forward<T>(value) 会保持为左值。
  • 如果 value 是右值,std::forward<T>(value) 会保持为右值。

3. std::decay_t 的作用

定义

std::decay_t<T>std::decay<T>::type 的别名,用于移除模板参数的修饰符(如引用、const、volatile),并将数组或函数指针转换为普通指针。

具体作用

make_y_combinator 中:

return YCombinator<std::decay_t<Func>>(std::forward<Func>(func));
  • Func 可能是一个复杂类型(比如引用或 const 修饰的 lambda 表达式)。
  • 使用 std::decay_t<Func> 可以将 Func 转换为适合存储的普通类型。
为什么需要 std::decay_t
  • 如果直接用 Func,某些类型(如引用或 const 修饰的函数)可能导致编译错误。
  • std::decay_t 确保 YCombinator 中存储的 func_ 是一个干净的可调用对象。
例子
int arr[] = {1, 2, 3};
std::decay_t<decltype(arr)> ptr; // 等价于 int*

4. 完整调用流程

以阶乘计算为例:

auto factorial = make_y_combinator([](auto self, int n) -> int {if (n == 0) {return 1;} else {return n * self(n - 1);}
});
std::cout << factorial(5) << std::endl;
流程解析
  1. make_y_combinator 创建 YCombinator 实例

    • Func 是 lambda。
    • std::decay_t<Func> 确保 func_ 存储的是简化版本的 lambda。
  2. 调用 factorial(5)

    • operator() 被调用,*this(即 factorial 自身)作为参数传递。
    • std::forward<Args>(args)... 确保参数类型不变。
  3. 递归过程

    • 在 lambda 中,selfYCombinator 的实例。
    • 调用 self(n - 1) 时再次触发 operator(),实现递归。

总结

  • operator() 的作用:提供函数调用接口,使 YCombinator 的实例可以像普通函数一样调用,并支持递归。
  • std::forward 的作用:高效地传递参数,保证左值或右值的原始属性不变。
  • std::decay_t 的作用:清理类型修饰符,确保存储的 func_ 类型是普通可调用对象。

解析C++嵌套模板声明

在 C++ 中,template <typename Func> template <typename... Args> 是一种 嵌套模板声明,它通常出现在一个类模板的成员函数中,表示该成员函数本身也是一个模板。

让我们逐步拆解它的含义:


1. template <typename Func>

这一部分定义了一个类模板,表示类 YCombinator 是基于某种类型 Func 的模板类。例如:

template <typename Func>
class YCombinator {// 成员函数、变量等定义
};
  • Func 是一个泛型类型参数,用于表示函数对象类型(如 lambda 表达式)。
  • 这个模板类的作用是让 YCombinator 可以接受任意类型的函数对象。

2. template <typename... Args>

这是一个 函数模板声明,用来表示该成员函数可以接受任意数量、任意类型的参数。它和 typename... Args 中的 可变参数模板 结合使用,可以让函数变得非常灵活。例如:

template <typename Func>
class YCombinator {
public:template <typename... Args>auto operator()(Args&&... args) const {// 实现}
};
  • Args... 是类型参数包,可以代表 0 个或多个类型。
  • Args&&... args 是函数参数包,对应每个类型的实际值。

这允许 operator() 函数在调用时接受任意数量和类型的参数。


3. 为什么使用嵌套模板?

模板类的成员函数需要额外的模板参数

由于 YCombinator 类已经是一个模板类,operator() 又需要定义自己的模板参数(Args...),所以这里必须嵌套一个新的 template。这是 C++ 的语法要求。

具体来说:

  1. 外层模板 template <typename Func> 定义了类的类型参数。
  2. 内层模板 template <typename... Args> 定义了函数自己的参数类型。
示例:
template <typename Func>
class YCombinator {
public:template <typename... Args>auto operator()(Args&&... args) const {return func_(*this, std::forward<Args>(args)...);}private:Func func_;
};

4. 具体调用流程

假设我们使用如下代码:

auto factorial = make_y_combinator([](auto self, int n) -> int {if (n == 0) return 1;return n * self(n - 1);
});std::cout << factorial(5) << std::endl;
调用过程:
  1. factorial(5) 触发 operator()
  2. 编译器自动推断 Args...{int},因此 operator() 实际上被实例化为:
    auto operator()(int&& n) const {return func_(*this, std::forward<int>(n));
    }
    
  3. 通过递归调用,operator() 不断被实例化为适合当前参数的版本。

5. 总结:

  • template <typename Func> 定义了一个模板类,使类能够接受任意类型的函数对象。
  • template <typename... Args> 定义了一个模板成员函数,使函数能够接受任意数量和类型的参数。
  • 这种嵌套模板机制是 C++ 提供的灵活性工具,使模板类和函数可以适配更广泛的需求。

英文版

Does C++ Have a Y Combinator?

C++ does not natively include a Y Combinator, but you can implement it using modern C++ features such as lambda expressions and std::function. The Y Combinator is a functional programming concept that allows recursion for anonymous functions. In C++, this can be achieved by creating a wrapper that injects recursion into a lambda.


Implementing a Y Combinator in C++

Goal

Enable recursion in lambda functions without requiring explicit function names.


Implementation

Here’s a template-based implementation of the Y Combinator:

#include <iostream>
#include <functional>template <typename Func>
class YCombinator {
public:explicit YCombinator(Func&& func) : func_(std::forward<Func>(func)) {}template <typename... Args>auto operator()(Args&&... args) const {// Pass itself as an argument to allow recursionreturn func_(*this, std::forward<Args>(args)...);}private:Func func_;
};// Helper function to create the Y Combinator
template <typename Func>
auto make_y_combinator(Func&& func) {return YCombinator<std::decay_t<Func>>(std::forward<Func>(func));
}

Example: Calculating Factorial

Here’s how to use the above Y Combinator to calculate factorials:

#include <iostream>int main() {// Define the factorial logic using the Y Combinatorauto factorial = make_y_combinator([](auto self, int n) -> int {if (n == 0) {return 1;} else {return n * self(n - 1); // Recursive call using `self`}});// Test the factorial functionstd::cout << "Factorial of 5: " << factorial(5) << std::endl; // Output: 120return 0;
}

How It Works

  1. YCombinator Class:

    • It wraps a lambda function and provides the ability to call itself recursively.
    • The operator() function enables recursion by passing the current instance (*this) to the lambda.
  2. Recursive Logic:

    • The lambda function receives self (the Y Combinator instance) as its first argument.
    • The recursive calls use self(...) instead of an explicitly named function.
  3. General Usage:

    • You can use make_y_combinator to wrap any recursive logic into a callable lambda.

Advantages

  • Generic: This implementation works for any recursive logic, not just specific examples like factorials.
  • Modern: Leverages C++11/14/17 features like lambda expressions and perfect forwarding for clean and efficient code.

Example: Fibonacci Sequence

Here’s how to calculate Fibonacci numbers using the Y Combinator:

auto fibonacci = make_y_combinator([](auto self, int n) -> int {if (n <= 1) {return n;} else {return self(n - 1) + self(n - 2); // Recursive calls}
});std::cout << "Fibonacci of 10: " << fibonacci(10) << std::endl; // Output: 55

Key Takeaways

  1. Recursive Lambdas: Normally, lambda expressions in C++ cannot call themselves by name. The Y Combinator solves this by injecting recursion externally.
  2. Generalized Pattern: The YCombinator class is reusable for any recursive problem.
  3. Power of Functional Programming: This demonstrates how functional programming concepts like fixed-point combinators can be implemented in imperative languages like C++.

Why Use a Y Combinator?

  • Functional Programming: Encourages a declarative approach to recursion.
  • Anonymous Functions: Eliminates the need for explicitly naming recursive functions.
  • Learning Exercise: A great way to deepen your understanding of recursion, higher-order functions, and lambda calculus.

Even though it may not be necessary for most practical use cases in C++, it showcases the expressive power of the language and is a valuable theoretical concept.

后记

2025年1月16日20点31分于上海,在GPT4o大模型辅助下完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/67413.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu本地部署网站

目录 1.介绍 2.安装apache 3.网页升级 1.介绍 网站其实就相当于一个文件夹&#xff0c;用域名访问一个网页&#xff0c;就相当于访问了一台电脑的某一个文件夹&#xff0c;在网页中看见的视频&#xff0c;视频和音乐其实就是文件夹里面的文件。为什么网页看起来不像电脑文件夹…

jenkins-node节点配置

一.简述&#xff1a; Jenkins有一个很强大的功能&#xff1a; 即&#xff1a;支持分布式构建(jenkins配置中叫节点(node),也被称为slave)。分布式构建通常是用来吸收额外的负载。通过动态添加额外的机器应对构建作业中的高峰期&#xff0c;或在特定操作系统或环境运行特定的构建…

20.<Spring图书管理系统①(登录+添加图书)>

PS&#xff1a;关于接口定义 接口定义&#xff0c;通常由服务器提供方来定义。 1.路径&#xff1a;自己定义 2.参数&#xff1a;根据需求考虑&#xff0c;我们这个接口功能完成需要哪些信息。 3.返回结果&#xff1a;考虑我们能为对方提供什么。站在对方角度考虑。 我们使用到的…

Vue2+OpenLayers实现点位拖拽功能(提供Gitee源码)

目录 一、案例截图 二、安装OpenLayers库 三、代码实现 3.1、初始化变量 3.2、创建一个点 3.3、将点添加到地图上 3.4、实现点位拖拽 3.5、完整代码 四、Gitee源码 一、案例截图 可以随意拖拽点位到你想要的位置 二、安装OpenLayers库 npm install ol 三、代码实现…

计算机网络 (46)简单网络管理协议SNMP

前言 简单网络管理协议&#xff08;SNMP&#xff0c;Simple Network Management Protocol&#xff09;是一种用于在计算机网络中管理网络节点的标准协议。 一、概述 SNMP是基于TCP/IP五层协议中的应用层协议&#xff0c;它使网络管理员能够管理网络效能&#xff0c;发现并解决网…

掌握C语言内存布局:数据存储的智慧之旅

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 目录 引言正文一、数据类型介绍1.内置类型2.自定义…

MySQL NaviCat 安装及配置教程(Windows)【安装】

文章目录 一、 MySQL 下载 1. 官网下载2. 其它渠道 二、 MySQL 安装三、 MySQL 验证及配置四、 NaviCat 下载 1. 官网下载2. 其它渠道 五、 NaviCat 安装六、 NaviCat 逆向工程 软件 / 环境安装及配置目录 一、 MySQL 下载 1. 官网下载 安装地址&#xff1a;https://www.m…

C语言结构体漫谈:从平凡中见不平凡

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文《1》 结构体的两种声明一、结构…

深度学习图像算法中的网络架构:Backbone、Neck 和 Head 详解

深度学习已经成为图像识别领域的核心技术&#xff0c;特别是在目标检测、图像分割等任务中&#xff0c;深度神经网络的应用取得了显著进展。在这些任务的网络架构中&#xff0c;通常可以分为三个主要部分&#xff1a;Backbone、Neck 和 Head。这些部分在整个网络中扮演着至关重…

信安大赛单机取证

22年国赛单机取证 Evidence4 先搜索Evidence 找到一个 Evidence4nsOh2.pngf5b9ce3e485314c23c40a89d994b2dc8 Evidence2 之后再一个个找 这个是压缩包格式的 导出来 伪加密 修复一下 Evidence2ZQOo2.jpg9e69763ec7dac69e2c5b07a5955a5868 Evidence3 png的文件 改个宽高 E…

jmeter事务控制器-勾选Generate Parent Sample

1、打开jmeter工具&#xff0c;添加线程组&#xff0c;添加逻辑控制器-事务控制器 2、在事务控制器&#xff0c;勾选Generate parent sample&#xff1a;生成父样本&#xff1b;说明勾选后&#xff0c;事务控制器会作为父节点&#xff0c;其下面的请求作为子节点 3、执行&#…

Flutter插件制作、本地/远程依赖及缓存机制深入剖析(原创-附源码)

Flutter插件在开发Flutter项目的过程中扮演着重要的角色&#xff0c;我们从 ​​​​​​https://pub.dev 上下载添加到项目中的第三方库都是以包或者插件的形式引入到代码中的&#xff0c;这些第三方工具极大的提高了开发效率。 深入的了解插件的制作、发布、工作原理和缓存机…

Linux 服务器挖矿木马防护实战:快速切断、清理与加固20250114

Linux 服务器挖矿木马防护实战&#xff1a;快速切断、清理与加固 引言 挖矿木马作为一种常见的恶意软件&#xff0c;对服务器资源和安全构成严重威胁。据安全机构统计&#xff0c;2023 年全球约 45%的 Linux 服务器遭受过挖矿木马攻击&#xff0c;平均每台被感染服务器每月造…

OpenAI推出首个AI Agent!日常事项自动化处理!

2025 年1月15日&#xff0c;OpenAI 正式宣布推出一项名为Tasks的测试版功能 。 该功能可以根据你的需求内容和时间实现自动化处理。比方说&#xff0c;你可以设置每天早晨 7 点获取天气预报&#xff0c;或定时提醒遛狗等日常事项。 看到这里&#xff0c;有没有一种熟悉的感觉&a…

闪豆多平台视频批量下载器

1. 视频链接获取与解析 首先&#xff0c;在哔哩哔哩网页中随意点击一个视频&#xff0c;比如你最近迷上了一个UP主的美食制作视频&#xff0c;想要下载下来慢慢学。点击视频后&#xff0c;复制视频页面的链接。复制完成后&#xff0c;不要急着关闭浏览器&#xff0c;因为接下来…

卷积神经网络的底层是傅里叶变换

1 卷积神经网络的底层是傅里叶变换&#xff0c;傅里叶变换的底层是希尔伯特空间坐标变换_哔哩哔哩_bilibili 卷积神经网络的底层是傅里叶变换&#xff0c;傅里叶变换的底层是希尔伯特空间坐标变换_哔哩哔哩_bilibili从“卷积”、到“图像卷积操作”、再到“卷积神经网络”&…

攻防世界_SQL注入

inget 尝试万能钥匙。 输入?id1or11# supersqli 1.找注入点 输入框 2.判断字符型&#xff0c;数字型 输入1 and 11 和1 and 12&#xff0c;发现两次提交后页面一样&#xff0c;判断出为字符型注入 3.判断闭合符号 输入1&#xff0c;回显正常 输入1&#xff0c;报错 加上…

怎么把文章发表到网上?在平台上投稿文章会有哪些常见问题?

怎么把文章发表到网上&#xff1f;在发表文章的过程中&#xff0c;我们可能会碰到各种各样的问题。这些问题要么导致发表时间被拖长&#xff0c;要么可能导致文章被拒稿。 就让我们来了解下文章发表过程中需要注意的一些细节&#xff0c;知晓怎么顺利地把文章发表到网上。 一、…

检验统计量与p值笔记

一、背景 以雨量数据为例&#xff0c;当获得一个站点一年的日雨量数据后&#xff0c;我们需要估计该站点的雨量的概率分布情况&#xff0c;因此我们利用有参估计的方式如极大似然法估计得到了假定该随机变量服从某一分布的参数&#xff0c;从而得到该站点的概率密度函数&#x…

【Flink系列】6. Flink中的时间和窗口

6. Flink中的时间和窗口 在批处理统计中&#xff0c;我们可以等待一批数据都到齐后&#xff0c;统一处理。但是在实时处理统计中&#xff0c;我们是来一条就得处理一条&#xff0c;那么我们怎么统计最近一段时间内的数据呢&#xff1f;引入“窗口”。 所谓的“窗口”&#xff…