Amethyst Studio
894 words
4 minutes
C++自定义中缀运算符
2024-06-02

Overview#

看下面的代码:

int main() {
  float x = 2.0;
  float y = 3.0;
  float z = x <pow> y;
  std::print("z = {}", z);
  return 0;
}

上面的代码中会打印出8.0,这个非常容易看出来。主要的问题是,其中的代码float z = x <pow> y非常有意思。它似乎定义了一种中缀运算符。

实际上这并不是C++23的新特性,而是一种很简单的trick。如果你使用clang-tidy这样的代码规整工具,就可以很容易看出来,因为clang-tidy可以将这一行代码,替换为x < pow > y

如果你熟悉C++的运算符重载,此时你应该就能够大概猜出这里的玄机,这里的pow应该是某种类封装了pow函数,接着x < pow则是重载了<运算符,得到一个包含左操作数的闭包,接着这个闭包再与y>运算,得到一个包含两个操作数的闭包,最后赋值,通过重载强制类型转换运算符得到最终的结果。

The Simplest Implementation#

按照这种思路,我们可以实现一个最简单的,让上面的代码可以运行的实现。

首先做一个Function类,保存函数,为了实现最简单的版本,这里是直接使用了STL的std::function,在实际开发的过程中,可能需要按照需求来对Function类做特殊处理。

struct Function {
  std::function<float(float, float)> f;
  float operator()(float a, float b) {
    return f(a, b);
  }
}

接着,我们要实现一个Closure_with_left类,用来代表包含了左操作数的闭包:

struct Closure_with_left {
  Function f;
  float left;
};

然后,重载operator<,让x < pow这样的表达式返回一个Closure_with_left

Closure_with_left operator<(float a, Function f) {
  return {f, a};
}

接着,对于整个x <pow> y来说,x < pow是一个Closure_with_left类,这个类与y再做>运算,应该得到一个包含两个操作数的闭包,我们将其命名为Closure_with_both

struct Closure_with_both {
  Function f;
  float left;
  float right;
};

然后再重载>运算符,让Clousre_with_left>做运算可以得到Closure_with_both

Closure_with_both operator>(Closure_with_left a, float b) {
  return {a.f, a.left, b};
}

这样一来,x <pow> y的部分就完成了,x <pow> y将会得到一个Closure_with_both,接下来要处理的是,如何让它执行这个函数。

如果需要让float z = x <pow> y能够执行成功的话,只需要重载float()转换就可以了,也就是说,将Closure_with_both修改一下,变成下面的形式:

struct Closure_with_both {
  Function f;
  float left;
  float right;

  operator float() {
    return f(left, right);
  }
};

这样就可以执行float z = x <pow> y了。

More Problem#

上面展示的是在C++里面设计中缀运算符的方法,而实际上,中缀运算符的问题并不那么简单,在实际的开发过程中,需要考虑下面的问题:

  1. 类型问题,也就是Function,Closure中的各种类型问题。
  2. 复合表达式运算符优先级的问题,也就是说,假设这样的表达式:float r = x <mul> y <pow> z;,这里面会有一个运算符优先级的问题需要解决。

Full Code#

#include <iostream>
#include <functional>
#include <cmath>

struct Function {
  std::function<float(float, float)> f;
  float operator()(float a, float b) {
    return f(a, b);
  }
};
struct Closure_with_left {
  Function f;
  float left;
};

struct Closure_with_right {
  Function f;
  float right;
};

struct Closure_with_both {
  Function f;
  float left;
  float right;

  operator float() {
    return f(left, right);
  }
};

Closure_with_left operator<(float a, Function f) {
  return {f, a};
}

Closure_with_both operator>(Closure_with_left a, float b) {
  return {a.f, a.left, b};
}

int main() {
  Function pow = {[](float a, float b) { return std::pow(a, b); }};

  float x = 2.0;
  float y = 3.0;
  float z = x <pow> y;

  std::cout << z << std::endl;
}
C++自定义中缀运算符
https://ziyue.cafe/posts/cpp-custom-infix/
Author
Kaida Amethyst
Published at
2024-06-02