C++ 中并没有直接内置的 Y Combinator,但通过现代 C++ 特性(如 lambda 表达式 和 std::function
),我们可以实现一个类似 Y Combinator 的功能。
下面我们来详细讲解如何在 C++ 中实现 Y Combinator。
我们希望实现一个支持递归的匿名函数,而不需要显式命名递归函数。关于Y combinator的概念,可以参考笔者的另一篇博客:理解 Y Combinator:函数式编程中的递归技巧(中英双语)
基础概念
在 C++ 中,lambda 表达式默认是无法直接递归调用自己的,因为它无法在定义时引用自己的名字。为了实现递归,我们需要一种方式让 lambda 表达式能够间接调用自身。
Y Combinator 模板实现
我们可以通过一个辅助的类或函数,把递归的能力注入到 lambda 表达式中。
以下是一个通用的 Y Combinator 实现:(代码解析请看下文)
#include
#include
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
#include
// 引入 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; // 输出 120
return 0;
}
YCombinator
类
YCombinator
是一个高阶函数包装器,它将递归逻辑注入到 lambda 表达式中。func_
存储了实际的 lambda 表达式。operator()
实现了调用逻辑,同时将自身 (*this
) 作为参数传递给 lambda 表达式,允许 lambda 表达式递归调用。递归逻辑
在 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
可以为任何递归逻辑提供支持,而不仅仅是阶乘。使用相同的方法,我们可以定义 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++ 特性,我们可以优雅地实现一个通用的递归解决方案。这个实现既具有理论意义,也能在实践中帮助我们更深入地理解递归和高阶函数的机制。
这段代码的关键在于 operator()
的实现,以及它如何通过 make_y_combinator
提供递归能力。我们分别解析以下几个核心点:
operator()
是如何工作的?operator()
的定义在 YCombinator
类中,operator()
被定义为一个模板方法:
template <typename... Args>
auto operator()(Args&&... args) const {
return func_(*this, std::forward<Args>(args)...);
}
YCombinator
的实例像普通函数一样被调用。operator()
被调用时,它将当前对象(*this
,即 YCombinator
自身)作为参数传递给存储的递归函数 func_
,从而实现递归能力。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
实例。当调用 factorial(5)
时:
YCombinator
的 operator()
方法。operator()
将自身传入 lambda,支持递归调用。std::forward
的作用std::forward
是 C++11 引入的模板工具,用于保持参数的“值类别”(左值或右值)属性。
在 operator()
中:
return func_(*this, std::forward<Args>(args)...);
Args&&... args
是 万能引用,它可以接受左值或右值参数。std::forward(args)...
会根据 args
的实际类型(左值或右值)转发给 func_
,确保传递时不会改变参数的值类别。std::forward
?args...
,左值可能被误认为右值,导致性能问题或语义错误。std::forward
保证了递归函数的参数传递是高效且正确的。template <typename T>
void print(T&& value) {
forward_example(std::forward<T>(value)); // 确保原样传递
}
value
是左值,std::forward(value)
会保持为左值。value
是右值,std::forward(value)
会保持为右值。std::decay_t
的作用std::decay_t
是 std::decay
的别名,用于移除模板参数的修饰符(如引用、const、volatile),并将数组或函数指针转换为普通指针。
在 make_y_combinator
中:
return YCombinator<std::decay_t<Func>>(std::forward<Func>(func));
Func
可能是一个复杂类型(比如引用或 const 修饰的 lambda 表达式)。std::decay_t
可以将 Func
转换为适合存储的普通类型。std::decay_t
?Func
,某些类型(如引用或 const 修饰的函数)可能导致编译错误。std::decay_t
确保 YCombinator
中存储的 func_
是一个干净的可调用对象。int arr[] = {1, 2, 3};
std::decay_t<decltype(arr)> ptr; // 等价于 int*
以阶乘计算为例:
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;
make_y_combinator
创建 YCombinator
实例:
Func
是 lambda。std::decay_t
确保 func_
存储的是简化版本的 lambda。调用 factorial(5)
:
operator()
被调用,*this
(即 factorial
自身)作为参数传递。std::forward(args)...
确保参数类型不变。递归过程:
self
是 YCombinator
的实例。self(n - 1)
时再次触发 operator()
,实现递归。operator()
的作用:提供函数调用接口,使 YCombinator
的实例可以像普通函数一样调用,并支持递归。std::forward
的作用:高效地传递参数,保证左值或右值的原始属性不变。std::decay_t
的作用:清理类型修饰符,确保存储的 func_
类型是普通可调用对象。在 C++ 中,template
是一种 嵌套模板声明,它通常出现在一个类模板的成员函数中,表示该成员函数本身也是一个模板。
让我们逐步拆解它的含义:
template
这一部分定义了一个类模板,表示类 YCombinator
是基于某种类型 Func
的模板类。例如:
template <typename Func>
class YCombinator {
// 成员函数、变量等定义
};
Func
是一个泛型类型参数,用于表示函数对象类型(如 lambda 表达式)。YCombinator
可以接受任意类型的函数对象。template
这是一个 函数模板声明,用来表示该成员函数可以接受任意数量、任意类型的参数。它和 typename... Args
中的 可变参数模板 结合使用,可以让函数变得非常灵活。例如:
template <typename Func>
class YCombinator {
public:
template <typename... Args>
auto operator()(Args&&... args) const {
// 实现
}
};
Args...
是类型参数包,可以代表 0 个或多个类型。Args&&... args
是函数参数包,对应每个类型的实际值。这允许 operator()
函数在调用时接受任意数量和类型的参数。
由于 YCombinator
类已经是一个模板类,operator()
又需要定义自己的模板参数(Args...
),所以这里必须嵌套一个新的 template
。这是 C++ 的语法要求。
具体来说:
template
定义了类的类型参数。template
定义了函数自己的参数类型。template <typename Func>
class YCombinator {
public:
template <typename... Args>
auto operator()(Args&&... args) const {
return func_(*this, std::forward<Args>(args)...);
}
private:
Func func_;
};
假设我们使用如下代码:
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;
factorial(5)
触发 operator()
。Args...
为 {int}
,因此 operator()
实际上被实例化为:auto operator()(int&& n) const {
return func_(*this, std::forward<int>(n));
}
operator()
不断被实例化为适合当前参数的版本。template
定义了一个模板类,使类能够接受任意类型的函数对象。template
定义了一个模板成员函数,使函数能够接受任意数量和类型的参数。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.
Enable recursion in lambda functions without requiring explicit function names.
Here’s a template-based implementation of the Y Combinator:
#include
#include
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 recursion
return 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));
}
Here’s how to use the above Y Combinator to calculate factorials:
#include
int main() {
// Define the factorial logic using the Y Combinator
auto 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 function
std::cout << "Factorial of 5: " << factorial(5) << std::endl; // Output: 120
return 0;
}
YCombinator
Class:
operator()
function enables recursion by passing the current instance (*this
) to the lambda.Recursive Logic:
self
(the Y Combinator instance) as its first argument.self(...)
instead of an explicitly named function.General Usage:
make_y_combinator
to wrap any recursive logic into a callable lambda.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
YCombinator
class is reusable for any recursive problem.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大模型辅助下完成。