1. 引言:希望在 C++ 中直接输入和处理数学公式

演讲开始列出了几个经典的数学公式:

  • 二阶线性常微分方程(阻尼振动):
    d2xdt2+2ζω0dxdt+ω02x=0 \frac{d^2x}{dt^2} + 2 \zeta \omega_0 \frac{dx}{dt} + \omega_0^2 x = 0 dt2d2x+2ζω0dtdx+ω02x=0
  • 万有引力:
    F=Gm1m2r2 F = G \frac{m_1 m_2}{r^2} F=Gr2m1m2
  • 正弦函数表达式:
    y(t)=asin⁡(ωt+ϕ) y(t) = a \sin(\omega t + \phi) y(t)=asin(ωt+ϕ)
    演讲主题:

“在 C++ 中能直接输入、操作数学公式不是很棒吗?”
这就是本次演讲的核心目标:把 C++ 的语法抽象到可以像写公式一样自然地操作符号表达式

2. 前提与历史背景

演讲提到 2019 年的工作:

  • EDSL Infinity Wars: 主流化符号计算
  • 关键思想是 无状态表达式模板(Stateless Expression Templates)
    区别:

旧式 Expression Templates Stateless Expression Templates
表达式模板是有状态的,终端符号由数据(向量、矩阵、张量)表示 公式是无状态的,数据被注入公式中
数据驱动表达式 公式驱动数据
演讲的进步:
  • 利用 C++20/23 的新特性 可以写出更优雅的接口。
  • 目标是 从你希望输入的公式出发,设计抽象(逆向工程语言)。

3. Lambda 技巧生成唯一类型的符号

想法:

symbol a;
symbol w;
symbol t;
symbol phi;
  • 每个符号都应该有一个唯一类型。
  • 问题是如何生成唯一类型。

解决方法:使用 Lambda 生成默认模板参数

template <auto = []{}> 
struct symbol {};
  • 每次声明 symbol a; 会生成不同的类型,因为每个 lambda 表达式的类型唯一。
  • 验证:
symbol x;
symbol y;
std::is_same_v<decltype(x), decltype(y)>; // false

解释:每个 lambda 声明都是一个全新的类型,从而生成唯一类型的符号。

4. 符号的比较

  • 对于公式重排,需要符号可以比较。
  • 不能直接用 std::type_info::hash_code,因为它不是 constexpr
  • 方案:为每个符号生成唯一标识符(singleton lambda 的地址)
template <class>
struct symbol_id {
    static constexpr auto singleton = []{};
    static constexpr const void* address = std::addressof(singleton);
};
template <class Lhs, class Rhs>
constexpr std::strong_ordering operator<=>(symbol_id<Lhs>, symbol_id<Rhs>) {
    return std::compare_three_way{}(
        symbol_id<Lhs>::address,
        symbol_id<Rhs>::address
    );
}
  • 解释:
    • singleton 只是为了获取地址。
    • std::compare_three_way(C++20 的 <=> 运算符)实现全序比较。

5. 符号绑定机制

目标:

formula f = a * sin(w * t + phi);
double y = f(a = 5.0, w = 2.5, t = 1.6, phi = 0);
  • operator= 返回一个 binder 对象,将符号绑定到具体值。

最小化 binder 示例

template <class Symbol, class T>
struct symbol_binder {
    constexpr symbol_binder(Symbol, T x) : value(x) {}
    static constexpr Symbol symbol = {};
    T value;
};
template <auto = []{}> 
struct symbol {
    template <class T>
    constexpr symbol_binder<symbol, T> operator=(T value) const {
        return symbol_binder(*this, value);
    }
};

6. 优化 binder 避免不必要的复制

  • 问题:binder 复制数据,如果是向量或矩阵,效率低。
  • 策略
    • 去掉引用类型(lvalue/rvalue)
    • 给非引用类型加 const
  • 工具模板
template <class T>
struct remove_lvalue_reference { ... };
template <class T>
struct remove_rvalue_reference { ... };
template <class T>
struct requalify_as_const { ... };
template <class Symbol, class T>
struct symbol_binder {
    using symbol_type = Symbol;
    using value_type = std::remove_cvref_t<T>;
private:
    requalify_as_const_t<remove_rvalue_reference_t<T>> value;
public:
    template <class U>
    requires std::is_convertible_v<U&&, requalify_as_const_t<remove_rvalue_reference_t<T>>>
    constexpr symbol_binder(Symbol, U&& x) noexcept(...) : value(std::forward<U>(x)) {}
    const value_type& operator()() const noexcept { return value; }
};
  • 绑定操作最终形式
template <auto = []{}> 
struct symbol {
    template <class T>
    constexpr symbol_binder<symbol, T&&> operator=(T&& value) const {
        return symbol_binder(*this, std::forward<T>(value));
    }
};

7. 总结

  • Lambda trick:利用每个 lambda 的唯一类型生成唯一符号类型。
  • 符号比较:利用 singleton 地址 + <=> 完全序比较符号。
  • 绑定值:利用 symbol_binder 将符号与值绑定,支持传入任意类型,同时避免不必要的复制。
  • 这样就可以在 C++ 中自然书写数学公式,并进行符号计算:
symbol a, w, t, phi;
formula f = a * sin(w * t + phi);
double y = f(a = 5.0, w = 2.5, t = 1.6, phi = 0);

1. 带约束的符号(Symbols with Constraints)

目标

希望能够在声明符号时提供数学语义信息,例如:

symbol a;
symbol w;
symbol t;
symbol phi;

改成:

symbol<real> a;
symbol<real> w;
symbol<real> t;
symbol<real> phi;

动机:

  1. 为用户提供上下文信息,让代码可读性更高。
  2. 避免不期望的类型转换。
  3. 利用数学知识进行优化(例如,某些操作的交换律、结合律可以简化表达式或优化计算)。
    演讲提出的核心思想是:给符号实现一种符号概念(symbolic concept)机制

2. 使用 Lambda 实现约束机制

C++23 的限制:

  • Concepts 不能直接作为模板参数
    解决方案:使用 Lambda 或函数对象检测类型。
struct unconstrained {
    template <class T>
    constexpr std::true_type operator()(T x) const noexcept { return {}; }
};
struct real {
    template <class T>
    constexpr std::false_type operator()(T x) const noexcept { return {}; }
    template <class T>
    requires std::is_floating_point_v<T>
    constexpr std::true_type operator()(T x) const noexcept { return {}; }
};
template <class T = unconstrained, auto = []{}> 
struct symbol {
    template <class Arg>
    requires decltype(std::declval<T>()(std::declval<Arg>()))::value
    constexpr symbol_binder<symbol, Arg&&> operator=(Arg&& arg) const {
        return symbol_binder(*this, std::forward<Arg>(arg));
    }
};

解释:

  • unconstrained 表示符号不受限制。
  • real 表示符号只能绑定浮点类型。
  • operator= 中的 requires 条件保证只有符合约束的类型才能绑定。

3. Trait 基机制

另一种方式是使用 traits:

template <class T>
struct unconstrained: std::true_type {};
template <class T>
struct real: std::is_floating_point<T> {};
template <template <class...> class Trait = unconstrained, auto = []{}> 
struct symbol {
    template <class Arg>
    requires Trait<std::remove_cvref_t<Arg>>::value
    constexpr symbol_binder<symbol, Arg&&> operator=(Arg&& arg) const {
        return symbol_binder(*this, std::forward<Arg>(arg));
    }
};
  • 使用 traits 可以更方便地在编译期约束类型。
  • 限制:C++ 目前支持类型泛型,但不支持“kind 泛型”,即模板参数的种类不能混合(type、NTTP、模板模板参数)。

4. 将 traits 包装成类型

解决“kind 泛型缺失”问题:

struct unconstrained {
    template <class T>
    struct trait: std::true_type {};
};
struct real {
    template <class T>
    struct trait: std::is_floating_point<T> {};
};
template <class Trait = unconstrained, auto = []{}> 
struct symbol {
    template <class Arg>
    requires Trait::template trait<std::remove_cvref_t<Arg>>::value
    constexpr symbol_binder<symbol, Arg&&> operator=(Arg&& arg) const {
        return symbol_binder(*this, std::forward<Arg>(arg));
    }
};

解释:

  • Trait 类型内部包含一个嵌套 trait 模板,用于判断给定类型是否符合约束。
  • symbol 使用 Lambda 生成唯一类型的 ID,并结合 trait 约束。

5. 综合前面技术:最终 symbol 模板

template <
    class Trait = unconstrained,
    auto Id = symbol_id<decltype([]{})>{}
> struct symbol {
    // 唯一标识符
    static constexpr auto id = Id;
    // 绑定机制
    template <class Arg>
    requires Trait::template trait<std::remove_cvref_t<Arg>>::value
    constexpr symbol_binder<symbol, Arg&&> operator=(Arg&& arg) const {
        return symbol_binder(*this, std::forward<Arg>(arg));
    }
};
  • 这整合了:
    • 唯一类型生成(lambda trick)
    • ID 唯一标识(symbol_id)
    • 绑定机制(symbol_binder)
    • 类型约束(Trait)

6. 数学表达式的概念与抽象

  • 符号(Symbols)
    • 变量符号:ϕ,ω,...\phi, \omega, ...ϕ,ω,...
    • 常量符号:π,5.6,42,...\pi, 5.6, 42,...π,5.6,42,...
    • 函数符号:sin⁡,log⁡,...\sin, \log,...sin,log,...
    • 操作符号:+,−,×+, -, \times+,,×
  • 自由变量与绑定变量
    • 自由变量(free variable):符号占位符。
    • 绑定变量(bound variable):已经绑定具体值。
  • 构造(Constructions)
    • Term(项):数学对象,如 ω×t+ϕ\omega \times t + \phiω×t+ϕ
    • Formula(公式):数学语句,如 f=a×sin⁡(ωt+ϕ)f = a \times \sin(\omega t + \phi)f=a×sin(ωt+ϕ)
    • Expression(表达式):符号序列
    • Equation(方程):两个公式的相等关系,如 a×sin⁡(ωt+ϕ)=xa \times \sin(\omega t + \phi) = xa×sin(ωt+ϕ)=x
  • 额外概念
    • Subterm / Subexpression(子项/子表达式)
    • Well-formed expression(符合语法的表达式)
    • Arity(子项数量):unary, binary, ternary 等

7. 符号操作(Actions)

  • 绑定(binding):将值附加到符号
  • 替换(substitution):在表达式中用值替换符号
  • 重写(rewriting):在公式中将子项替换为其他项

8. 抽象语法树(AST, Abstract Syntax Tree)

以公式:
f=a×sin⁡(ωt+ϕ) f = a \times \sin(\omega t + \phi) f=a×sin(ωt+ϕ)
对应的 AST 表示:

      ×
     / \
    a   sin
        |
        +
       / \
     ×     ϕ
    / \
   ω   t
  • 遍历方式
    • Pre-order(前序):×(a, sin(+(×(ω,t),ϕ)))
    • In-order(中序):a × sin(ω t + ϕ)
    • Post-order(后序):a, ω, t, ×, ϕ, +, sin, ×

9. 一个概念到万用概念

  • 使用 type trait 标识符:
template <class>
struct is_symbolic: std::false_type {};
template <class T>
inline constexpr bool is_symbolic_v = is_symbolic<T>::value;
template <class T>
concept symbolic = is_symbolic_v<T>;
// 变量符号特化
template <class T, auto Id>
struct is_symbolic<symbol<T, Id>>: std::true_type {};
  • 这样可以通过 symbolic concept 判断一个类型是否是符号类型,便于泛型算法处理。

小结

  1. 带约束符号允许在编译期限定符号可绑定的类型(如 real 浮点数)。
  2. Lambda trick + symbol_id 保证符号唯一类型和唯一标识。
  3. symbol_binder 负责绑定符号与值,同时避免不必要的复制。
  4. AST 与重写机制支持符号计算和表达式操作。
  5. Concepts/Traits 为符号提供类型语义约束,便于优化和类型检查。
#include <iostream>
#include <type_traits>
#include <utility>
#include <cmath>
// =====================================
// Traits(类型约束)
// =====================================
// unconstrained: 不限制绑定的类型,任何类型都可以绑定
struct unconstrained {
    template <class T>
    struct trait : std::true_type {};
};
// real: 只允许绑定浮点类型(float, double, long double)
struct real {
    template <class T>
    struct trait : std::is_floating_point<T> {};
};
// =====================================
// Unique symbol ID(符号唯一标识符)
// =====================================
// 每个符号都需要一个唯一的 ID 来比较和区分不同符号
template <class>
struct symbol_id {
    static constexpr auto singleton = [] {};         // 匿名 lambda 作为唯一标记
    static constexpr const void* address = std::addressof(singleton);  // 获取 lambda 地址作为唯一 ID
};
// 对 symbol_id 提供三路比较运算符(C++20 支持 <=>)
template <class L, class R>
constexpr auto operator<=>(symbol_id<L>, symbol_id<R>) {
    return symbol_id<L>::address <=> symbol_id<R>::address;
}
// =====================================
// symbol_binder - 符号绑定值
// =====================================
// symbol_binder 负责把一个符号和一个具体的值绑定起来
// Symbol 提供 value_type(绑定值的类型)
template <class Symbol>
struct symbol_binder {
    using symbol_type = Symbol;
    using value_type = typename Symbol::value_type;  // 从 symbol 获取值类型
    const Symbol& sym;  // 绑定的符号
    value_type value;   // 绑定的值
    // 构造函数:允许传入可以转换为 value_type 的值
    template <class U>
        requires std::is_convertible_v<U&&, const value_type&>
    constexpr symbol_binder(const Symbol& s, U&& x) : sym(s), value(std::forward<U>(x)) {}
    // 获取绑定值
    constexpr const value_type& operator()() const noexcept { return value; }
};
// 提供类模板参数推导指导(CTAD),让 symbol_binder{...} 可以自动推导模板参数
template <class Sym, class U>
symbol_binder(Sym, U&&) -> symbol_binder<Sym>;
// =====================================
// symbol - 声明符号可以绑定的类型
// =====================================
template <class Trait = unconstrained, auto Id = symbol_id<decltype([] {})>{}>
struct symbol {
    using value_type = double;  // 明确符号的值类型,这里我们选择 double
    static constexpr auto id = Id; // 符号唯一 ID
    // operator= 允许将值绑定到符号上
    template <class Arg>
        requires Trait::template
    trait<std::remove_cvref_t<Arg>>::value // 只允许满足 Trait 的类型
    constexpr auto operator=(Arg&& arg) const {
        return symbol_binder{*this, std::forward<Arg>(arg)}; // 返回 symbol_binder 对象
    }
};
// =====================================
// Concept - 判断类型是否是符号
// =====================================
template <class T>
struct is_symbolic : std::false_type {};
// 针对 symbol 特化为 true_type
template <class Tr, auto Id>
struct is_symbolic<symbol<Tr, Id>> : std::true_type {};
// C++20 concept,symbolic 用于约束模板或静态断言
template <class T>
concept symbolic = is_symbolic<T>::value;
// =====================================
// main 函数示例
// =====================================
int main() {
    // 定义四个符号变量
    symbol<real> a, w, t, phi;
    // 将值绑定到符号上,返回 symbol_binder
    auto a_val = a = 5.0;    // a -> 5.0
    auto w_val = w = 2.5;    // w -> 2.5
    auto t_val = t = 1.6;    // t -> 1.6
    auto phi_val = phi = 0.0; // phi -> 0.0
    // 使用绑定值计算 y
    double y = a_val() * std::sin(w_val() * t_val() + phi_val());
    std::cout << "y = " << y << std::endl;
    // 编译期断言 a 是符号
    static_assert(symbolic<decltype(a)>);
    return 0;
}

1. Traits(类型约束)

struct unconstrained { ... };
struct real { ... };
  • unconstrained:不限制绑定类型,任何类型都可以绑定。
  • real:只允许绑定浮点类型(float、double、long double)。
  • 作用:为符号绑定值提供类型检查,防止不合法类型绑定。

2. Unique symbol ID(符号唯一标识符)

template <class>
struct symbol_id { ... };
template <class L, class R>
constexpr auto operator<=>(symbol_id<L>, symbol_id<R>) { ... }
  • 每个 symbol 都有一个唯一的 ID(通过 lambda 地址生成)。
  • operator<=> 提供符号比较的能力。
  • 作用:保证不同符号可以唯一区分、排序或比较,即使符号的名字相同。

3. symbol_binder(符号绑定器)

template <class Symbol>
struct symbol_binder { ... };
  • symbol_binder 负责把一个符号和一个具体的值绑定在一起。
  • 它包含:
    • sym:引用符号本身
    • value:绑定的值
  • operator() 可以返回绑定值。
  • 作用:把符号从抽象的概念(symbol)变成可操作的值对象。
    例:
auto a_val = a = 5.0;  // a_val 是 symbol_binder, 保存 a 和 5.0
a_val();                // 返回 5.0

4. symbol(符号类型)

template <class Trait = unconstrained, auto Id = symbol_id<decltype([] {})>{}>
struct symbol { ... };
  • 每个 symbol 是一个可绑定值的符号类型。
  • value_type 定义了符号可以绑定的值类型(这里固定为 double)。
  • operator= 用于绑定值,返回 symbol_binder
  • 作用
    1. 定义符号本身(如 a, w, t, phi)
    2. 限制符号可以绑定的值类型
    3. 支持符号绑定值的操作

5. Concept symbolic

template <class T>
concept symbolic = is_symbolic<T>::value;
  • 判断一个类型是否是符号类型(symbol)。
  • 作用:在编译期检查类型,保证符号相关操作的类型安全。

6. main 函数示例

symbol<real> a, w, t, phi;
auto a_val = a = 5.0;
auto w_val = w = 2.5;
auto t_val = t = 1.6;
auto phi_val = phi = 0.0;
double y = a_val() * std::sin(w_val() * t_val() + phi_val());
  • 定义了四个符号 a, w, t, phi
  • 将具体值绑定到符号上:
    • a = 5.0 返回 symbol_binder<symbol<real>, double>
    • 通过 operator() 获取绑定值。
  • 使用绑定值进行数学计算(计算 y = a * sin(w*t + phi))。
  • 静态断言验证 a 是符号类型。

总结

这个示例的主要功能是:

  1. 抽象数学符号(symbol)为类型。
  2. 为符号绑定值,返回 symbol_binder,可以安全地获取绑定值。
  3. 类型约束检查,保证符号绑定的值符合预期类型。
  4. 支持 编译期静态检查,如 static_assert(symbolic<decltype(a)>)
  5. 提供基础框架,可以进一步扩展为 符号表达式计算(AST 构建、符号代数运算、公式重写等)。
auto expr = a * sin(w*t + phi);

1. 引入示例

假设我们有一个公式:

formula f = a * sin(w * t + phi);

我们希望在符号化公式中直接进行替换(substitution):

double y = f(a = 5.0, w = 2.5, t = 1.6, phi = 0);

这里的操作就是 将具体数值绑定到符号上,并计算公式的结果
用数学形式表示就是:
f(a,w,t,ϕ)=a⋅sin⁡(w⋅t+ϕ) f(a, w, t, \phi) = a \cdot \sin(w \cdot t + \phi) f(a,w,t,ϕ)=asin(wt+ϕ)
经过替换后:
y=f(5.0,2.5,1.6,0.0)=5.0⋅sin⁡(2.5⋅1.6+0) y = f(5.0, 2.5, 1.6, 0.0) = 5.0 \cdot \sin(2.5 \cdot 1.6 + 0) y=f(5.0,2.5,1.6,0.0)=5.0sin(2.51.6+0)

2. 从公式到表达式

2.1 formula 模板

template <symbolic Expression>
struct formula {
    using expression = Expression;
    constexpr formula(Expression expr) noexcept {};
    template <class... Args>
    constexpr auto operator()(Args... args) const noexcept {
        return expression{}(substitution(args...));
    }
};
  • Expression 是一个符号化表达式类型(symbolic)。
  • operator() 接受一组绑定操作 Args...,返回 经过替换后的表达式的值
  • 关键是调用了 substitution(args...),把绑定值打包成 substitution 对象。

2.2 substitution 基本结构

概念:一个 substitution 是 符号绑定器的集合,每个绑定器根据符号 ID 索引,可以快速查找绑定值。

(1) index_constant
template <std::size_t I>
struct index_constant : std::integral_constant<std::size_t, I> {};
template <std::size_t I>
inline constexpr index_constant<I> index = {};
  • 为每个绑定器提供唯一索引,用于在 substitution 中定位。
(2) substitution_element
template <std::size_t I, Binder B>
struct substitution_element {
    using index = index_constant<I>;
    using id_type = decltype(B::symbol_type::id);
    constexpr substitution_element(const Binder& b): _binder(b) {}
    constexpr const T& operator[](index) const { return _binder; }
    constexpr const T& operator[](id_type) const { return _binder; }
private:
    const B _binder;
};
  • 每个元素 _binder 保存 符号绑定值
  • 可以通过 索引 I符号 ID 访问绑定值。
  • 这就是 substitution 的核心魔法

2.3 substitution_base

template <std::size_t... Index, class... Binders>
struct substitution_base<std::index_sequence<Index...>, Binders...>
    : substitution_element<Index, Binders>... {
    using index_sequence = std::index_sequence<Index...>;
    using substitution_element<Index, Binders>::operator[]...;
    constexpr substitution_base(const Binders&... x) : substitution_element<Index, Binders>(x)... {}
};
  • substitution_base 继承所有 substitution_element,从而 打包整个绑定集合
  • 支持通过 operator[] 访问每个绑定值。

2.4 substitution

template <class... Binders>
struct substitution : substitution_base<std::index_sequence_for<Binders...>, Binders...> {
    using base = substitution_base<std::index_sequence_for<Binders...>, Binders...>;
    using base::base;
    using base::operator[];
};
// CTAD 推导
template <class... Binders>
substitution(const Binders&...) -> substitution<Binders...>;
  • 是最终对外使用的 substitution 类型,用户可以写:
s = substitution(a=5, b=2);
std::cout << s[a.id] << std::endl; // 输出 5
  • 核心作用:把多个符号绑定集合封装起来,通过符号 ID 进行查找。

3. 使用流程总结

  1. 定义符号
symbol<real> a, w, t, phi;
  1. 创建公式
formula f = a * sin(w * t + phi);
  1. 绑定值生成 substitution
auto s = substitution(a=5.0, w=2.5, t=1.6, phi=0.0);
  1. 公式计算(替换绑定值)
double y = f(a=5.0, w=2.5, t=1.6, phi=0.0);

数学对应关系
y=f(a,w,t,ϕ)经过 substitutiony=f(5.0,2.5,1.6,0.0) y = f(a, w, t, \phi) \quad\text{经过 substitution}\quad y = f(5.0, 2.5, 1.6, 0.0) y=f(a,w,t,ϕ)经过 substitutiony=f(5.0,2.5,1.6,0.0)

总结

  • substitution 是符号计算系统的关键组成部分,它把符号绑定的具体值打包成集合。
  • 使用 符号 ID索引 可以高效访问绑定值。
  • formulasubstitution 结合,实现了 符号公式的值替换,类似数学里的函数调用。
  • 这套机制为进一步实现 符号表达式计算、公式重写、符号代数运算 打下基础。
    如果你需要,我可以帮你写一个 完整可运行的示例,演示:
formula f = a*sin(w*t + phi);
double y = f(a=5.0, w=2.5, t=1.6, phi=0.0);
#include <iostream>
#include <cmath>
#include <type_traits>
#include <utility>
#include <numbers>  // C++20 中的数学常数
// =====================================
// Traits(类型约束)
// =====================================
struct unconstrained {
    // trait 模板:对所有类型都返回 true
    template <class T>
    struct trait : std::true_type {};
};
struct real {
    // trait 模板:只对浮点类型返回 true
    template <class T>
    struct trait : std::is_floating_point<T> {};
};
// =====================================
// 符号唯一 ID(symbol_id)
// =====================================
// 每个符号(symbol)都需要唯一的标识符
template <class>
struct symbol_id {
    static constexpr auto singleton = [] {};                           // 用 lambda 生成唯一类型
    static constexpr const void* address = std::addressof(singleton);  // 获取唯一地址
};
// 不同符号 ID 可以比较大小
template <class L, class R>
constexpr auto operator<=>(symbol_id<L>, symbol_id<R>) {
    return symbol_id<L>::address <=> symbol_id<R>::address;
}
// =====================================
// symbol_binder(符号绑定值)
// =====================================
template <class Symbol>
struct symbol_binder {
    using symbol_type = Symbol;                      // 绑定的符号类型
    using value_type = typename Symbol::value_type;  // 符号的值类型
    const Symbol& sym;  // 符号引用
    value_type value;   // 符号绑定的实际值
    // 构造函数:将符号和一个值绑定
    template <class U>
        requires std::is_convertible_v<U&&, const value_type&>
    constexpr symbol_binder(const Symbol& s, U&& x) : sym(s), value(std::forward<U>(x)) {}
    // 调用 operator() 返回绑定的值
    constexpr const value_type& operator()() const noexcept { return value; }
};
// 辅助模板推导,可以直接用 symbol_binder(sym, value)
template <class Sym, class U>
symbol_binder(Sym, U&&) -> symbol_binder<Sym>;
// =====================================
// symbol(符号本身)
// =====================================
template <class Trait = unconstrained, auto Id = symbol_id<decltype([] {})>{}>
struct symbol {
    using value_type = double;      // 符号的值类型为 double
    static constexpr auto id = Id;  // 符号唯一 ID
    // 将符号绑定一个值,返回 symbol_binder
    template <class Arg>
        requires Trait::template
    trait<std::remove_cvref_t<Arg>>::value constexpr auto operator=(Arg&& arg) const {
        return symbol_binder{*this, std::forward<Arg>(arg)};
    }
};
// =====================================
// substitution system(符号替换系统)
// =====================================
// 单个符号绑定元素
template <std::size_t I, class Binder>
struct substitution_element {
    using index = std::integral_constant<std::size_t, I>;  // 索引类型
    using id_type = decltype(Binder::symbol_type::id);     // 符号 ID 类型
    constexpr substitution_element(const Binder& b) : binder(b) {}
    // 按索引访问绑定
    constexpr const Binder& operator[](index) const { return binder; }
    // 按符号 ID 访问绑定
    constexpr const Binder& operator[](id_type) const { return binder; }
private:
    const Binder& binder;
};
// 多个符号绑定组合
template <class Seq, class... Binders>
struct substitution_base;
template <std::size_t... I, class... Binders>
struct substitution_base<std::index_sequence<I...>, Binders...>
    : substitution_element<I, Binders>... {                 // 继承每个 substitution_element
    using substitution_element<I, Binders>::operator[]...;  // 继承 operator[]
    constexpr substitution_base(const Binders&... bs) : substitution_element<I, Binders>(bs)... {}
};
// 对外使用的 substitution 类型
template <class... Binders>
struct substitution : substitution_base<std::index_sequence_for<Binders...>, Binders...> {
    using base = substitution_base<std::index_sequence_for<Binders...>, Binders...>;
    using base::base;
    using base::operator[];
};
// 辅助模板推导
template <class... Binders>
substitution(Binders...) -> substitution<Binders...>;
// =====================================
// formula(表达式封装)
// =====================================
template <class Expr>
struct formula {
    Expr expr;  // 内部表达式对象
    constexpr formula(Expr e = {}) : expr(e) {}
    // 调用公式,传入多个符号绑定
    template <class... Binders>
    constexpr auto operator()(Binders... bindings) const {
        auto subst = substitution(bindings...);  // 构建 substitution
        return expr(subst);                      // 调用表达式求值
    }
};
// =====================================
// 全局符号实例(关键!保证唯一性)
// =====================================
inline constexpr symbol<real> a;    // 符号 a
inline constexpr symbol<real> w;    // 符号 w
inline constexpr symbol<real> t;    // 符号 t
inline constexpr symbol<real> phi;  // 符号 phi
// =====================================
// 表达式 example_expr
// =====================================
struct example_expr {
    template <class Subst>
    constexpr double operator()(Subst s) const {
        // 使用全局符号的 .id 查找绑定值
        auto a_val = s[a.id]();      // 获取 a 的值
        auto w_val = s[w.id]();      // 获取 w 的值
        auto t_val = s[t.id]();      // 获取 t 的值
        auto phi_val = s[phi.id]();  // 获取 phi 的值
        // 计算公式 y = a * sin(w * t + phi)
        return a_val * std::sin(w_val * t_val + phi_val);
    }
};
// =====================================
// main 函数
// =====================================
int main() {
    formula f(example_expr{});  // 将表达式封装为 formula
    // 调用 formula,传入符号绑定值
    double y = f(a = 5.0, w = 2.5, t = 1.6, phi = 0.0);
    std::cout << "y = " << y << std::endl;  // 输出结果: y ≈ 2.10701
    return 0;
}

1. 核心概念

  1. stateless expression templates(无状态表达式模板)
    • 所有表达式模板都是无状态的,不保存具体值,值是在之后通过绑定注入。
    • 优点:可以构建任意复杂表达式 AST(抽象语法树),支持符号计算。
  2. lambda trick(lambda 技巧)
    • 利用 lambda 创建唯一类型并结合默认模板参数生成符号唯一 ID。
    • 核心思想:每个符号声明都生成一个唯一类型,通过地址或类型比较实现唯一性。
  3. symbolic
    • 表示任何符号表达式的概念(concept)。
    • 例如 symbolconstant_symbolsymbolic_expression 都是 symbolic
  4. symbol
    • 声明一个符号变量。
    • 示例:
      symbol a;
      
    • 可以绑定值:
      a = 5.0;
      
  5. symbol_id
    • 为每个符号生成唯一 ID,用于 substitution 系统中索引和比较。
  6. symbol_binder
    • 将一个符号绑定到具体数值,形成“符号 → 值”的映射。
  7. symbol_constraint
    • 对符号类型进行约束,例如只能绑定浮点数 real
  8. constant_symbol
    • 表示符号常量,例如 π\piπ 或数学常数:
      template <auto Value>
      struct constant_symbol {
          using type = decltype(Value);
          static constexpr type value = Value;
      };
      
    • 对应操作:
      template <auto Value>
      struct is_symbolic<constant_symbol<Value>> : std::true_type {};
      

2. 符号表达式构建

  1. symbolic_expression
    • 抽象语法树(AST)节点,存储操作符和操作数:
      template <class Operator, symbolic... Terms>
      struct symbolic_expression {};
      
  2. 使其成为符号表达式
    template <class Operator, symbolic... Terms>
    struct is_symbolic<symbolic_expression<Operator, Terms...>> : std::true_type {};
    
  3. 运算符重载
    • 使表达式像普通数学式一样书写:
      template <symbolic Lhs, symbolic Rhs>
      constexpr symbolic_expression<std::plus<void>, Lhs, Rhs> operator+(Lhs, Rhs) noexcept { return {}; }
      template <symbolic Lhs, symbolic Rhs>
      constexpr symbolic_expression<std::multiplies<void>, Lhs, Rhs> operator*(Lhs, Rhs) noexcept { return {}; }
      
  4. 自定义函数符号
    • 定义函数对象:
      struct sin_symbol {
          template <class Arg>
          constexpr auto operator()(Arg&& arg) {
              return std::sin(std::forward<Arg>(arg));
          }
      };
      template <symbolic Arg>
      constexpr symbolic_expression<sin_symbol, Arg> sin(Arg) noexcept { return {}; }
      
  5. 构建公式
    • 组合符号和运算符:
      formula f = a * sin(w * t + phi);
      

3. substitution(符号替换系统)

  1. 核心思想
    • 将符号表达式中的每个符号替换成具体数值,从而计算表达式结果。
    • 对符号:
      template <class Trait = unconstrained, auto Id = symbol_id<decltype([]{})>{}>
      struct symbol {
          template <class... Binders>
          constexpr auto operator()(const substitution<Binders...>& s) const {
              return s[id](); // 通过 ID 获取绑定值
          }
      };
      
    • 对常量:
      template <auto Value>
      struct constant_symbol {
          template <class... Binders>
          constexpr type operator()(const substitution<Binders...>&) const {
              return value;
          }
      };
      
    • 对表达式:
      template <class Operator, symbolic... Terms>
      struct symbolic_expression {
          template <class... Binders>
          constexpr auto operator()(const substitution<Binders...>& s) const noexcept {
              return Operator{}(Terms{}(s)...); // 递归计算
          }
      };
      
  2. 最终计算公式
    double y = f(a = 5.0, w = 2.5, t = 1.6, phi = 0);
    

4. 数学公式示例

  1. 表达式:
    y=a⋅sin⁡(w⋅t+ϕ) y = a \cdot \sin(w \cdot t + \phi) y=asin(wt+ϕ)
  2. 当绑定:
    a=5.0,w=2.5,t=1.6,ϕ=0 a = 5.0, \quad w = 2.5, \quad t = 1.6, \quad \phi = 0 a=5.0,w=2.5,t=1.6,ϕ=0
    计算:
    y≈5.0⋅sin⁡(2.5⋅1.6+0)≈2.10701 y \approx 5.0 \cdot \sin(2.5 \cdot 1.6 + 0) \approx 2.10701 y5.0sin(2.51.6+0)2.10701

5. 总结

  1. 关键思想
    • 所有表达式模板都是无状态的,数据在调用时注入。
    • lambda trick 保证符号唯一性。
    • substitution 系统允许符号替换和表达式求值。
    • 可扩展支持常量符号、运算符、函数、符号表达式。
  2. 未来扩展
    • 部分替换(partial substitution)
    • 表达式重写(rewrite)
    • 符号计算(微分、积分)
    • 高性能计算(矩阵运算、GPU 加速)
    • 自定义规则化简(custom rule-based simplification)
  3. 哲学
    • “从你想要书写的数学式出发设计抽象”
    • 逆向工程语言,使得 C++ 符号表达式直观易用。
#include <iostream>
#include <type_traits>
#include <functional>
#include <cmath>
#include <memory>
#include <utility>
// ============================================================================
// 类型特征工具 - 用于引用类型的操作
// Type Traits for Reference Manipulation
// ============================================================================
// 移除左值引用(但保留右值引用)
// Remove lvalue references only, keep rvalue references
template <typename T>
struct remove_lvalue_reference : std::type_identity<T> {};
template <typename T>
    requires std::is_lvalue_reference_v<T>
struct remove_lvalue_reference<T> : std::type_identity<std::remove_reference_t<T>> {};
template <typename T>
using remove_lvalue_reference_t = typename remove_lvalue_reference<T>::type;
// 移除右值引用(但保留左值引用)
// Remove rvalue references only, keep lvalue references
template <typename T>
struct remove_rvalue_reference : std::type_identity<T> {};
template <typename T>
    requires std::is_rvalue_reference_v<T>
struct remove_rvalue_reference<T> : std::type_identity<std::remove_reference_t<T>> {};
template <typename T>
using remove_rvalue_reference_t = typename remove_rvalue_reference<T>::type;
// 将类型重新限定为const,同时保留引用类型
// Requalify types as const while preserving reference types
// 例如:T& -> const T&, T&& -> const T&&, T -> const T
template <typename T>
struct requalify_as_const
    : std::conditional<std::is_lvalue_reference_v<T>,
                       std::add_lvalue_reference_t<std::add_const_t<std::remove_reference_t<T>>>,
                       std::conditional_t<std::is_rvalue_reference_v<T>,
                                          std::add_rvalue_reference_t<
                                              std::add_const_t<std::remove_reference_t<T>>>,
                                          std::add_const_t<T>>> {};
template <typename T>
using requalify_as_const_t = typename requalify_as_const<T>::type;
// ============================================================================
// 符号约束(概念模拟)
// Symbol Constraints (Concept Simulation)
// ============================================================================
// 无约束 - 接受任何类型
// Unconstrained - accepts any type
template <typename T>
struct unconstrained : std::true_type {};
// 实数约束 - 只接受浮点类型
// Real number constraint - only accepts floating-point types
template <typename T>
struct real : std::is_floating_point<T> {};
// 整数约束 - 只接受整数类型
// Integer constraint - only accepts integral types
template <typename T>
struct integer : std::is_integral<T> {};
// 算术类型约束 - 接受任何算术类型
// Arithmetic constraint - accepts any arithmetic type
template <typename T>
struct arithmetic : std::is_arithmetic<T> {};
// ============================================================================
// 符号标识符 - 使用lambda技巧生成唯一类型
// Symbol Identifier - using the lambda trick to generate unique types
// ============================================================================
// 符号ID结构体,每个lambda都会生成一个唯一的类型
// Symbol ID struct, each lambda generates a unique type
template <auto Lambda>
struct symbol_id {
    static constexpr auto singleton = Lambda;                          // 单例lambda
    static constexpr const void* address = std::addressof(singleton);  // lambda的地址
};
// 三路比较运算符 - 用于比较不同的符号ID
// Three-way comparison operator - for comparing different symbol IDs
template <auto Lambda1, auto Lambda2>
constexpr std::strong_ordering operator<=>(symbol_id<Lambda1>, symbol_id<Lambda2>) {
    // 使用std::compare_three_way而非直接比较指针(避免未定义行为)
    // Use std::compare_three_way instead of direct pointer comparison (avoid UB)
    return std::compare_three_way{}(symbol_id<Lambda1>::address, symbol_id<Lambda2>::address);
}
// 相等比较运算符
// Equality comparison operator
template <auto Lambda1, auto Lambda2>
constexpr bool operator==(symbol_id<Lambda1>, symbol_id<Lambda2>) {
    return symbol_id<Lambda1>::address == symbol_id<Lambda2>::address;
}
// ============================================================================
// 符号绑定器 - 将符号与具体值绑定
// Symbol Binder - binds symbols to concrete values
// ============================================================================
// 符号绑定器:存储符号和对应的值
// Symbol binder: stores a symbol and its corresponding value
template <typename Symbol, typename T>
struct symbol_binder {
    using symbol_type = Symbol;
    using value_type = std::remove_cvref_t<T>;
    static constexpr Symbol symbol = {};  // 关联的符号
    // 构造函数:接受符号和值
    // Constructor: accepts symbol and value
    template <typename U>
        requires std::is_convertible_v<U, remove_rvalue_reference_t<T>>
    constexpr symbol_binder(Symbol, U&& x) noexcept(
        std::is_nothrow_convertible_v<U, remove_rvalue_reference_t<T>>)
        : value(std::forward<U>(x)) {}
    // 访问器:返回存储的值
    // Accessor: returns the stored value
    constexpr const value_type& operator()() const noexcept { return value; }
private:
    // 存储值,使用const限定以避免不必要的拷贝
    // Store value with const qualification to avoid unnecessary copies
    requalify_as_const_t<remove_rvalue_reference_t<T>> value;
};
// 推导指南:自动推导模板参数
// Deduction guide: automatically deduce template parameters
template <typename Symbol, typename T>
symbol_binder(Symbol, T&&) -> symbol_binder<Symbol, T>;
// ============================================================================
// 符号 - 数学公式中的变量
// Symbol - variables in mathematical formulas
// ============================================================================
// 符号类:表示数学表达式中的变量(如x, y, z等)
// Symbol class: represents variables in mathematical expressions (like x, y, z, etc.)
// Id: 使用lambda的默认参数生成唯一类型
// Constraint: 类型约束模板,限制符号可以绑定的值的类型
template <auto Id = [] {}, template <typename> typename Constraint = unconstrained>
struct symbol {
    static constexpr auto id = Id;             // 符号的唯一标识
    using constraint_type = Constraint<void>;  // 约束类型
    // 赋值运算符:创建符号绑定(符号 = 值)
    // Assignment operator: creates a symbol binding (symbol = value)
    template <typename Arg>
        requires Constraint<std::remove_cvref_t<Arg>>::value  // 检查值是否满足约束
    constexpr auto operator=(Arg&& arg) const {
        return symbol_binder(*this, std::forward<Arg>(arg));
    }
};
// ============================================================================
// 表达式模板基类
// Expression Template Base Class
// ============================================================================
// CRTP基类:所有表达式节点的基类
// CRTP base class: base class for all expression nodes
template <typename Derived>
struct expression {
    // 获取派生类的引用(CRTP模式)
    // Get reference to derived class (CRTP pattern)
    constexpr const Derived& derived() const noexcept { return static_cast<const Derived&>(*this); }
};
// ============================================================================
// 表达式节点类型
// Expression Node Types
// ============================================================================
// 常量节点:表示常数值
// Constant node: represents constant values
template <typename T>
struct constant : expression<constant<T>> {
    T value;
    constexpr explicit constant(T v) : value(v) {}
    // 求值:常量的值不依赖于任何符号绑定
    // Evaluation: constant value doesn't depend on any symbol bindings
    template <typename... Binders>
    constexpr T operator()(Binders&&...) const {
        return value;
    }
};
// 符号节点:表示一个符号变量
// Symbol node: represents a symbol variable
template <typename Symbol>
struct symbol_node : expression<symbol_node<Symbol>> {
    static constexpr Symbol symbol = {};
    // 求值:从绑定列表中查找符号对应的值
    // Evaluation: find the value corresponding to the symbol from the binding list
    template <typename... Binders>
    constexpr auto operator()(Binders&&... binders) const {
        return get_value(std::forward<Binders>(binders)...);
    }
private:
    // 递归查找符号对应的绑定值
    // Recursively find the binding value for the symbol
    template <typename First, typename... Rest>
    constexpr auto get_value(First&& first, Rest&&... rest) const {
        if constexpr (std::is_same_v<Symbol, typename std::remove_cvref_t<First>::symbol_type>) {
            // 找到匹配的符号,返回其值
            // Found matching symbol, return its value
            return first();
        } else {
            // 继续在剩余的绑定中查找
            // Continue searching in remaining bindings
            return get_value(std::forward<Rest>(rest)...);
        }
    }
};
// 二元操作节点:表示两个操作数的运算
// Binary operation node: represents an operation on two operands
template <typename Op, typename Left, typename Right>
struct binary_op : expression<binary_op<Op, Left, Right>> {
    Left left;    // 左操作数
    Right right;  // 右操作数
    constexpr binary_op(Left l, Right r) : left(l), right(r) {}
    // 求值:先递归求值左右操作数,然后应用运算符
    // Evaluation: recursively evaluate left and right operands, then apply operator
    template <typename... Binders>
    constexpr auto operator()(Binders&&... binders) const {
        return Op{}(left(std::forward<Binders>(binders)...),
                    right(std::forward<Binders>(binders)...));
    }
};
// 一元操作节点:表示单个操作数的运算
// Unary operation node: represents an operation on a single operand
template <typename Op, typename Arg>
struct unary_op : expression<unary_op<Op, Arg>> {
    Arg arg;  // 操作数
    constexpr explicit unary_op(Arg a) : arg(a) {}
    // 求值:先递归求值操作数,然后应用运算符
    // Evaluation: recursively evaluate operand, then apply operator
    template <typename... Binders>
    constexpr auto operator()(Binders&&... binders) const {
        return Op{}(arg(std::forward<Binders>(binders)...));
    }
};
// ============================================================================
// 运算符函数对象
// Operation Function Objects
// ============================================================================
// 加法运算
// Addition operation
struct add_op {
    template <typename T, typename U>
    constexpr auto operator()(T&& a, U&& b) const {
        return std::forward<T>(a) + std::forward<U>(b);
    }
};
// 减法运算
// Subtraction operation
struct sub_op {
    template <typename T, typename U>
    constexpr auto operator()(T&& a, U&& b) const {
        return std::forward<T>(a) - std::forward<U>(b);
    }
};
// 乘法运算
// Multiplication operation
struct mul_op {
    template <typename T, typename U>
    constexpr auto operator()(T&& a, U&& b) const {
        return std::forward<T>(a) * std::forward<U>(b);
    }
};
// 除法运算
// Division operation
struct div_op {
    template <typename T, typename U>
    constexpr auto operator()(T&& a, U&& b) const {
        return std::forward<T>(a) / std::forward<U>(b);
    }
};
// 取负运算
// Negation operation
struct neg_op {
    template <typename T>
    constexpr auto operator()(T&& a) const {
        return -std::forward<T>(a);
    }
};
// 正弦函数
// Sine function
struct sin_op {
    template <typename T>
    auto operator()(T&& a) const {
        return std::sin(std::forward<T>(a));
    }
};
// 余弦函数
// Cosine function
struct cos_op {
    template <typename T>
    auto operator()(T&& a) const {
        return std::cos(std::forward<T>(a));
    }
};
// 指数函数
// Exponential function
struct exp_op {
    template <typename T>
    auto operator()(T&& a) const {
        return std::exp(std::forward<T>(a));
    }
};
// 平方根函数
// Square root function
struct sqrt_op {
    template <typename T>
    auto operator()(T&& a) const {
        return std::sqrt(std::forward<T>(a));
    }
};
// ============================================================================
// 运算符重载 - 构建表达式树
// Operator Overloads - building expression trees
// ============================================================================
// 加法运算符重载(表达式 + 表达式)
// Addition operator overload (expression + expression)
template <typename L, typename R>
    requires std::derived_from<L, expression<L>> && std::derived_from<R, expression<R>>
constexpr auto operator+(const L& left, const R& right) {
    return binary_op<add_op, L, R>(left, right);
}
// 加法运算符重载(表达式 + 数值)
// Addition operator overload (expression + numeric)
template <typename L, typename T>
    requires std::derived_from<L, expression<L>> && std::is_arithmetic_v<T>
constexpr auto operator+(const L& left, T right) {
    return binary_op<add_op, L, constant<T>>(left, constant<T>(right));
}
// 加法运算符重载(数值 + 表达式)
// Addition operator overload (numeric + expression)
template <typename T, typename R>
    requires std::is_arithmetic_v<T> && std::derived_from<R, expression<R>>
constexpr auto operator+(T left, const R& right) {
    return binary_op<add_op, constant<T>, R>(constant<T>(left), right);
}
// 减法运算符重载(表达式 - 表达式)
// Subtraction operator overload (expression - expression)
template <typename L, typename R>
    requires std::derived_from<L, expression<L>> && std::derived_from<R, expression<R>>
constexpr auto operator-(const L& left, const R& right) {
    return binary_op<sub_op, L, R>(left, right);
}
// 减法运算符重载(表达式 - 数值)
// Subtraction operator overload (expression - numeric)
template <typename L, typename T>
    requires std::derived_from<L, expression<L>> && std::is_arithmetic_v<T>
constexpr auto operator-(const L& left, T right) {
    return binary_op<sub_op, L, constant<T>>(left, constant<T>(right));
}
// 减法运算符重载(数值 - 表达式)
// Subtraction operator overload (numeric - expression)
template <typename T, typename R>
    requires std::is_arithmetic_v<T> && std::derived_from<R, expression<R>>
constexpr auto operator-(T left, const R& right) {
    return binary_op<sub_op, constant<T>, R>(constant<T>(left), right);
}
// 乘法运算符重载(表达式 * 表达式)
// Multiplication operator overload (expression * expression)
template <typename L, typename R>
    requires std::derived_from<L, expression<L>> && std::derived_from<R, expression<R>>
constexpr auto operator*(const L& left, const R& right) {
    return binary_op<mul_op, L, R>(left, right);
}
// 乘法运算符重载(表达式 * 数值)
// Multiplication operator overload (expression * numeric)
template <typename L, typename T>
    requires std::derived_from<L, expression<L>> && std::is_arithmetic_v<T>
constexpr auto operator*(const L& left, T right) {
    return binary_op<mul_op, L, constant<T>>(left, constant<T>(right));
}
// 乘法运算符重载(数值 * 表达式)
// Multiplication operator overload (numeric * expression)
template <typename T, typename R>
    requires std::is_arithmetic_v<T> && std::derived_from<R, expression<R>>
constexpr auto operator*(T left, const R& right) {
    return binary_op<mul_op, constant<T>, R>(constant<T>(left), right);
}
// 除法运算符重载(表达式 / 表达式)
// Division operator overload (expression / expression)
template <typename L, typename R>
    requires std::derived_from<L, expression<L>> && std::derived_from<R, expression<R>>
constexpr auto operator/(const L& left, const R& right) {
    return binary_op<div_op, L, R>(left, right);
}
// 除法运算符重载(表达式 / 数值)
// Division operator overload (expression / numeric)
template <typename L, typename T>
    requires std::derived_from<L, expression<L>> && std::is_arithmetic_v<T>
constexpr auto operator/(const L& left, T right) {
    return binary_op<div_op, L, constant<T>>(left, constant<T>(right));
}
// 除法运算符重载(数值 / 表达式)
// Division operator overload (numeric / expression)
template <typename T, typename R>
    requires std::is_arithmetic_v<T> && std::derived_from<R, expression<R>>
constexpr auto operator/(T left, const R& right) {
    return binary_op<div_op, constant<T>, R>(constant<T>(left), right);
}
// 取负运算符重载(-表达式)
// Negation operator overload (-expression)
template <typename E>
    requires std::derived_from<E, expression<E>>
constexpr auto operator-(const E& expr) {
    return unary_op<neg_op, E>(expr);
}
// ============================================================================
// 数学函数
// Mathematical Functions
// ============================================================================
// 正弦函数
// Sine function
template <typename E>
    requires std::derived_from<E, expression<E>>
auto sin(const E& expr) {
    return unary_op<sin_op, E>(expr);
}
// 余弦函数
// Cosine function
template <typename E>
    requires std::derived_from<E, expression<E>>
auto cos(const E& expr) {
    return unary_op<cos_op, E>(expr);
}
// 指数函数
// Exponential function
template <typename E>
    requires std::derived_from<E, expression<E>>
auto exp(const E& expr) {
    return unary_op<exp_op, E>(expr);
}
// 平方根函数
// Square root function
template <typename E>
    requires std::derived_from<E, expression<E>>
auto sqrt(const E& expr) {
    return unary_op<sqrt_op, E>(expr);
}
// ============================================================================
// 公式包装器 - 继承自expression以支持运算符重载
// Formula wrapper - inherits from expression to work with operators
// ============================================================================
// 公式类:包装表达式树,使其可以像函数一样调用
// Formula class: wraps expression tree, making it callable like a function
template <typename Expr>
struct formula : expression<formula<Expr>> {
    Expr expr;  // 包装的表达式树
    constexpr formula(Expr e) : expr(e) {}
    // 函数调用运算符:使用符号绑定求值表达式
    // Function call operator: evaluate expression with symbol bindings
    template <typename... Binders>
    constexpr auto operator()(Binders&&... binders) const {
        return expr(std::forward<Binders>(binders)...);
    }
};
// 转换函数:将符号转换为符号节点(表达式)
// Conversion function: convert symbol to symbol node (expression)
template <auto Id, template <typename> typename Constraint>
constexpr auto make_expr(symbol<Id, Constraint> s) {
    return symbol_node<symbol<Id, Constraint>>{};
}
// 恒等转换:表达式保持不变
// Identity conversion: expressions remain unchanged
template <typename E>
    requires std::derived_from<E, expression<E>>
constexpr const E& make_expr(const E& expr) {
    return expr;
}
// ============================================================================
// 示例代码 - 演示符号计算系统的使用
// Demo Code - demonstrating the symbolic calculus system
// ============================================================================
int main() {
    // 示例1:简单方程 y(t) = a * sin(w * t + phi)
    // Example 1: Simple equation y(t) = a * sin(w * t + phi)
    std::cout << "=== 示例1:正弦波方程 ===" << std::endl;
    std::cout << "=== Example 1: Sine wave equation ===" << std::endl;
    symbol<[] {}, real> a;    // 振幅 (amplitude)
    symbol<[] {}, real> w;    // 角频率 (angular frequency)
    symbol<[] {}, real> t;    // 时间 (time)
    symbol<[] {}, real> phi;  // 相位 (phase)
    // 构建表达式树:a * sin(w * t + phi)
    // Build expression tree: a * sin(w * t + phi)
    auto f = make_expr(a) * sin(make_expr(w) * make_expr(t) + make_expr(phi));
    // 求值:将具体值绑定到符号
    // Evaluate: bind concrete values to symbols
    double y = f(a = 5.0, w = 2.5, t = 1.6, phi = 0.0);
    std::cout << "y = a * sin(w * t + phi)" << std::endl;
    std::cout << "  a=5.0, w=2.5, t=1.6, phi=0.0" << std::endl;
    std::cout << "  结果(result) = " << y << std::endl << std::endl;
    // 示例2:二次方程 x^2 + b*x + c
    // Example 2: Quadratic equation x^2 + b*x + c
    std::cout << "=== 示例2:二次方程 ===" << std::endl;
    std::cout << "=== Example 2: Quadratic equation ===" << std::endl;
    symbol<[] {}, real> x;  // 自变量 (independent variable)
    symbol<[] {}, real> b;  // 一次项系数 (linear coefficient)
    symbol<[] {}, real> c;  // 常数项 (constant term)
    // 构建表达式树:x*x + b*x + c
    // Build expression tree: x*x + b*x + c
    auto quad = make_expr(x) * make_expr(x) + make_expr(b) * make_expr(x) + make_expr(c);
    double result = quad(x = 2.0, b = 3.0, c = 1.0);
    std::cout << "f(x) = x^2 + b*x + c" << std::endl;
    std::cout << "  x=2.0, b=3.0, c=1.0" << std::endl;
    std::cout << "  结果(result) = " << result << std::endl << std::endl;
    // 示例3:高斯分布概率密度函数
    // Example 3: Gaussian distribution probability density function
    std::cout << "=== 示例3:高斯分布PDF ===" << std::endl;
    std::cout << "=== Example 3: Gaussian distribution PDF ===" << std::endl;
    symbol<[] {}, real> mu;     // 均值 (mean)
    symbol<[] {}, real> sigma;  // 标准差 (standard deviation)
    // 构建高斯分布公式
    // Build Gaussian distribution formula
    // PDF(x) = (1 / (σ * sqrt(2π))) * exp(-(x-μ)² / (2σ²))
    auto gaussian = (1.0 / (make_expr(sigma) * sqrt(constant<double>(2.0 * 3.14159265359)))) *
                    exp(-(make_expr(x) - make_expr(mu)) * (make_expr(x) - make_expr(mu)) /
                        (2.0 * make_expr(sigma) * make_expr(sigma)));
    double prob = gaussian(x = 1.0, mu = 0.0, sigma = 1.0);
    std::cout << "PDF(x) = (1/(σ*sqrt(2π))) * exp(-(x-μ)²/(2σ²))" << std::endl;
    std::cout << "  x=1.0, μ=0.0, σ=1.0" << std::endl;
    std::cout << "  概率密度(probability density) = " << prob << std::endl << std::endl;
    // 示例4:展示符号的唯一性
    // Example 4: Demonstrate symbol uniqueness
    std::cout << "=== 示例4:符号唯一性验证 ===" << std::endl;
    std::cout << "=== Example 4: Symbol uniqueness verification ===" << std::endl;
    symbol<[] {}, real> s1;
    symbol<[] {}, real> s2;
    symbol<[] {}, real> s3;
    std::cout << "s1和s2是同一类型? "
              << std::is_same_v<decltype(s1), decltype(s2)> << " (应为1/true)" << std::endl;
    std::cout << "s1 and s2 same type? "
              << std::is_same_v<decltype(s1), decltype(s2)> << " (should be 1/true)" << std::endl;
    std::cout << "s1和s3是同一类型? "
              << std::is_same_v<decltype(s1), decltype(s3)> << " (应为0/false)" << std::endl;
    std::cout << "s1 and s3 same type? "
              << std::is_same_v<decltype(s1), decltype(s3)> << " (should be 0/false)" << std::endl;
    return 0;
}

1) 高层概述(这段代码做什么)

这是一套表达式模板 + 符号绑定的小框架,允许你以类似数学表达式的写法构造表达式树(a * sin(w * t + phi) 等),然后以语法 f(a = 5.0, w = 2.5, t = 1.6, phi = 0.0) 把具体数值“绑定”到符号上并求值。
主要组件包括:

  • 一组类型工具(处理引用/const 重限定等)。
  • 一组“约束”模板(realintegerunconstrained 等)用于限制符号可绑定的类型。
  • 使用 lambda trick 为符号生成唯一的类型 ID(symbol_id / symbol)。
  • symbol_binder 保存“符号 = 值”的绑定。
  • 表达式模板类型:constantsymbol_nodebinary_opunary_op
  • 一系列运算符对象(add_opsin_op 等)与相应的运算符/函数重载,用于构建表达式树。
  • make_exprsymbol 转换为 symbol_node 从而可参与表达式构造。
  • main() 包含 4 个示例(正弦、二次式、高斯 PDF、符号唯一性验证)。

2) 关键类型工具(简述和目的)

文件开头有若干模板用于引用/const 操作:

  • remove_lvalue_reference<T>去掉左值引用(T& -> T),保留右值引用(T&& 不变)。用于处理模板参数时保留语义。
  • remove_rvalue_reference<T>去掉右值引用(T&& -> T),保留左值引用。
  • requalify_as_const<T>:把类型重新限定为 const,但保留引用性质:
    • int -> const int
    • int& -> const int&
    • int&& -> const int&&
      这些工具在 symbol_binder 中用于选择合适的存储类型(尽量避免不必要拷贝与保持正确的引用/const 语义)。

3) 约束(concept 模拟)

  • unconstrained<T>:总是 true_type,不限制类型。
  • real<T>:封装 std::is_floating_point<T>,用于限制符号只能绑定浮点类型。
  • integer<T>arithmetic<T> 类似。
    这些模板被用于 symbol 的赋值 operator= 上的 requires,确保绑定类型满足约束(例如 real 只能绑定浮点数)。

4) 符号 ID 与 lambda 技巧(如何得到唯一类型)

核心思想:用一个 lambda 闭包对象作为 non-type 模板参数,从而让每个不同的 lambda 表达式产生独一无二的类型(lambda 的闭包类型是唯一的)。实现细节:

  • template <auto Lambda> struct symbol_id { static constexpr auto singleton = Lambda; static constexpr const void* address = std::addressof(singleton); };
    • singleton 保存 lambda 实例(constexpr),address 保存其地址(用作比较)。
  • 重载 operator<=>operator== 来允许不同 symbol_id 之间比较(通过地址比较封装的 lambda 单例地址)。
    注意(重要):在 C++ 中每个 lambda 表达式都有其唯一的闭包类型。如果你在不同位置写了两个文本上相同的 [] {} 表达式,它们通常仍然是不同的闭包类型(因此不是同一个 symbol)。如果想要多个变量共享同一个 symbol 类型,应该把闭包对象作为一个 constexpr 名字变量(例如 static constexpr auto id = []{};),然后在多个地方使用同一个 id。我在后面“潜在问题”会详细说明这个点。

5) symbol_binder(符号绑定器)

template <typename Symbol, typename T> struct symbol_binder

  • symbol_type:绑定对应的 Symbol 类型(用于 symbol_node 在查找绑定时进行类型匹配)。
  • 保存值的成员 value 的类型是 requalify_as_const_t<remove_rvalue_reference_t<T>>
    • 目的:把要保存的值以合适的形式(带 const)存储,既支持引用语义也避免不必要拷贝。
  • 构造函数模板 symbol_binder(Symbol, U&& x) 使用 requires std::is_convertible_v<U, remove_rvalue_reference_t<T>>,并转发 xvalue,保证可转换并尽量使用 noexcept 推断。
  • operator()() 返回 const value_type&,供 symbol_node 在求值时调用以得到绑定的值。
    另外代码提供了 deduction guide symbol_binder(Symbol, T&&) -> symbol_binder<Symbol, T>,便于自动推导 binder 的模板参数。

6) symbol(符号类)

template <auto Id = [] {}, template <typename> typename Constraint = unconstrained> struct symbol { ... };

  • Id:用来唯一标识符号(使用 lambda)。
  • Constraint:用于限制符号可绑定的类型(例如 real)。
  • operator=(Arg&& arg) const:当你写 a = 5.0 时,会创建 symbol_binder(*this, std::forward<Arg>(arg))requires 子句使用 Constraint<std::remove_cvref_t<Arg>>::value 做静态类型检查。
    这让写 f(a = 5.0, ...) 成为可能,a = 5.0 是一个 binder 对象。

7) 表达式模板层次(如何表示与求值表达式)

CRTP 基类:

  • template <typename Derived> struct expression:供所有节点继承,主要用于 std::derived_from 要求和统一接口。
    节点类型:
  • constant<T>:保存一个常数值,operator()(Binders&&...) 忽略任何 binder,直接返回常量。
  • symbol_node<Symbol>:在求值时从传入的 binder 列表中逐个递归查找匹配 Symbolsymbol_binder,找到后调用 first() 返回其值。
    • 注意:实现是递归式的 get_value(First, Rest...);如果没有找到匹配的 binder,会在编译期尝试继续展开直到没有参数 —— 程序中没有提供“未找到时的终止 overload”或明确的静态断言,缺 binder 的情况会导致编译错误(通常为模板匹配失败或找不到 get_value() 的可调用重载)。
  • binary_op<Op, Left, Right> / unary_op<Op, Arg>:分别评估子表达式并将 Op{} 应用到得到的结果上。

8) 运算符对象与运算符重载

  • add_op, sub_op, mul_op, div_op, neg_op, sin_op, cos_op, exp_op, sqrt_op 等分别封装具体的运行时操作(大多数直接调用标准算术/std::sin 等)。
  • 通过一系列 operator+ / operator- / operator* / operator/ 的重载,支持:
    • expression + expression
    • expression + numeric
    • numeric + expression
    • 同理对减/乘/除
  • sin(expr)cos(expr)exp(expr)sqrt(expr) 被定义为返回对应的 unary_op<..., E>,从而可以直接写 sin(make_expr(w) * make_expr(t) + make_expr(phi))

9) make_exprformula

  • make_expr(symbol<...> s) —— 把 symbol 转为 symbol_node<symbol<...>>,即把符号纳入表达式系统。
  • formula<Expr> 包装表达式树,使其本身是 expression 派生类型,并提供 operator()(Binders&&...) 转发到内部的表达式(Expr)上。主程序里并没有显式使用 formula(可以用来包裹更复杂的表达式以便重用/传递)。

10) main() 做了哪些具体示例(并给出数值结果)

程序在 main() 中演示了 4 个示例 —— 我把每个示例的意图与精确数值列出(数值是用高精度数学库计算并在回答中贴出):

示例1:正弦波 y(t) = a * sin(w * t + phi)

代码:

symbol<[] {}, real> a, w, t, phi;
auto f = make_expr(a) * sin(make_expr(w) * make_expr(t) + make_expr(phi));
double y = f(a = 5.0, w = 2.5, t = 1.6, phi = 0.0);

计算结果(精确到双精度):

  • w * t = 2.5 * 1.6 = 4.0
  • sin(4.0)-0.7568024953079282
  • y = 5.0 * sin(4.0)-3.7840124765396412
    (代码会打印类似 结果(result) = -3.7840124765396412。)

示例2:二次方程 f(x) = x^2 + b*x + c,评估点 x=2.0,b=3.0,c=1.0

代码:

symbol<[] {}, real> x, b, c;
auto quad = make_expr(x) * make_expr(x) + make_expr(b) * make_expr(x) + make_expr(c);
double result = quad(x = 2.0, b = 3.0, c = 1.0);

计算:

  • x^2 = 2^2 = 4
  • b*x = 3*2 = 6
  • 总和 4 + 6 + 1 = 1111.0

示例3:高斯分布概率密度函数(标准正态在 x=1)

公式:

PDF(x) = (1 / (σ * sqrt(2π))) * exp(-(x-μ)² / (2σ²))

代码里用 musigmax 构造该表达式并用 x=1.0, mu=0.0, sigma=1.0 评估。
结果(标准正态在 1 的概率密度):

  • 0.24197072451914337

示例4:符号唯一性验证(注意:代码作者的注释/预期与实际行为需注意)

代码声明:

symbol<[] {}, real> s1;
symbol<[] {}, real> s2;
symbol<[] {}, real> s3;
std::cout << std::is_same_v<decltype(s1), decltype(s2)> << ...;
  • 重要说明(常见误解):每个 lambda 表达式在 C++ 中具有独一无二的闭包类型。即使文本上写两次 [] {},在多数情况下它们也是不同的类型(除非你把同一个闭包对象命名并复用)。因此,如果你在多个独立位置各写一个 [] {} 并把它们作为模板非类型参数,很可能得到不同的 symbol 类型。
  • 如果你 真的想s1s2 是相同类型(相同 symbol),应该把 lambda 单例放到一个 constexpr 变量中并复用,例如:
    static constexpr auto id = []{};
    symbol<id, real> s1, s2; // s1, s2 将是相同类型
    
  • 因此,程序中期待的输出注释(s1和s2是同一类型? ... (应为1/true)可能不成立,取决于编译器对相同文本 lambda 表达式是否被视为同一闭包(通常不是)。更稳妥的方式是显式复用同一个 id。

11) 潜在问题、边界条件与改进建议

  1. 符号绑定缺失导致的错误信息不友好
    • symbol_node::get_value 递归查找没有为“未找到”情况提供明确静态断言或错误消息。建议添加一个基 case 或在最外层提供 static_assert(false, "...")(用 SFINAE/概念技巧以便在编译时给出清晰提示),否则编译器会报非常冗长/难懂的模板错误。
  2. lambda 用法的细节
    • 如上所说,文本上相同的 []{} 并不保证产生相同闭包类型。若希望可重用符号,推荐使用 static constexpr auto id = []{}; 并用 symbol<id>
  3. symbol_binder 的存储策略
    • 目前将值存为 requalify_as_const_t<remove_rvalue_reference_t<T>>,这有助于避免意外修改,但可能对大对象的移动/拷贝策略需要更细的控制(例如:希望转移所有权时应支持移动语义并且不总是 const)。根据使用场景可权衡。
  4. 表达式中常量与类型推导一致性
    • 你在表达式中既有 constant<double>(2.0 * 3.14159265359),也有裸 1.0(当与表达式混合时,运算符重载会把 1.0 包装成 constant<double>)。这没问题,但要注意不同常量类型(int, double)下的隐式转换与精度问题。
  5. 可扩展性/性能
    • 表达式模板在编译期会生成大量的小对象/内联函数,若表达式层级很深可能导致编译慢或二进制膨胀。可以考虑对常见子表达式做 constexpr 评估或按需缓存(但这会增加复杂度)。

12) 示例输出(精确数值汇总)

  • 示例1(y = a * sin(w * t + phi)a=5.0,w=2.5,t=1.6,phi=0.0):
    • y ≈ -3.7840124765396412
  • 示例2(二次式,x=2.0,b=3.0,c=1.0):
    • result = 11.0
  • 示例3(标准正态 PDF 在 x=1):
    • prob ≈ 0.24197072451914337

13) 总结(简短)

  • 这段代码实现了一个类型安全、可约束的符号表达式系统,利用 lambda 作为符号唯一标识(非类型模板参数),以表达式模板的方式构造和求值数学公式。
  • 关键要点:注意 lambda 的唯一性语义(若需要相同 symbol,请复用同一个 constexpr lambda 对象),并为“找不到绑定”这种情况添加明确静态检查以提高可用性。
  • 如果你想,我可以:
    • 把示例 4 的 symbol 唯一性部分改写为更确定的写法(用 static constexpr auto),并展示编译器行为;
    • symbol_node::get_value 增加一个友好的静态断言/错误信息;
    • 或将 formula 包裹使用示例写出来,便于复用表达式。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐