【问题标题】:User-defined infix operators用户定义的中缀运算符
【发布时间】:2016-04-01 12:42:49
【问题描述】:

在 C++ 中很容易引入新的中缀运算符

// User-defined infix operator framework

template <typename LeftOperand, typename Operation>
struct LeftHelper
{
    const LeftOperand& leftOperand;
    const Operation& operation;
    LeftHelper(const LeftOperand& leftOperand, 
               const Operation& operation)
        : leftOperand(leftOperand), operation(operation) {}
};

template <typename LeftOperand, typename Operation >
auto operator < (const LeftOperand& leftOperand, 
                 Operation& operation)
{
    return LeftHelper<LeftOperand, Operation>(leftOperand, operation);
}

template <typename LeftOperand, typename Operation, typename RightOperand>
auto operator > (LeftHelper<LeftOperand, Operation> leftHelper, 
                 const RightOperand& rightOperand)
{
    return leftHelper.operation(leftHelper.leftOperand, rightOperand);
}

// Defining a new operator

#include <cmath>
static auto pwr = [](const auto& operand1, const auto& operand2) { return std::pow(operand1, operand2); };

// using it
#include <iostream>
int main() 
{
   std::cout << (2 <pwr> 16) << std::endl;
   return 0;
}

Live demo

不幸的是,这个幂运算符有错误的优先级和关联性。所以我的问题是:如何解决这个问题?我希望我的 &lt;pow&gt; 具有比 * 更高的优先级并关联到右侧,就像在数学符号中一样。

编辑 可以通过使用不同的括号来改变优先级,例如|op|/op/*op* 甚至,如果有人愿意的话,&lt;&lt;--op--&gt;&gt;,但这样一来就不能高于内置运算符的最高优先级。但如今,C++ 在模板元编程和类型推导方面如此强大,根本就应该有其他方法来达到预期的效果。

此外,如果我可以使用pow 而不是pwr,那就太好了。不幸的是,在某些实现中,#include &lt;cmath&gt;pow 带入了全局命名空间,因此会有冲突。我们可以重载operator not 以便声明表单

not using std::pow;

从全局命名空间中删除了std::pow

延伸阅读:a related proposal by Bjarne Stroustrup.

【问题讨论】:

  • “简单”......哈哈。 (就像,说真的,我希望这只是为了研究目的)
  • 不能“发明”新的运算符,您只需使用现有的运算符来模拟与运算符相似但不是实际运算符的东西。这就是运算符优先级不起作用的原因。为了回答你的问题,没有办法解决这个问题。
  • 这有点类似于“运行到”运算符的想法:while(i ----&gt; 0)。非常方便!
  • 哈哈,Bjarne Stroustrup 的提议简直是个巨魔:D

标签: c++ c++14 user-defined infix-operator


【解决方案1】:

最小意外原则很重要,关键是a*b *power* c * d 评估为a* (b^c) *d。幸运的是,有一个简单的解决方案。

为确保*power* 的优先级高于乘法,您必须使用类似的命名运算符技术进行乘法运算。

那么你不是直接计算*power**times*的结果,而是构建一个表达式树。此表达式树在评估时可以应用任意优先规则

我们可以对每个内置运算符执行此操作,为我们提供易于阅读的语法,允许对运算符优先级进行编译时元编程:

auto z =equals= bracket<
  a *plus* b *times* c *power* bracket<
    a *plus* b
  >bracket *power* x *times* y
>bracket;

为避免此表达式模板的存储时间超过最佳值,只需重载 operator auto()&amp;&amp; 即可返回推导的类型。如果您的编译器不支持该功能,=equals= 可以以适度的清晰度返回正确的类型。

请注意,上述语法实际上可以在 C++ 中使用类似于 OP 的技术来实现。实际的实现比 SO 帖子应该包含的要大。

还有其他好处。众所周知,编程语言中晦涩难懂的 ASCII 字符已经失宠,读 C++ 的人可能会被以下表述迷惑:

int z = (a + b* pow(c,pow(x,a+b))*y);

使用这种技术,所有运算符都有可读的名称,使其含义清晰,并且所有操作都使用中缀而不是混合中缀和前缀表示法。

可以通过自己将&lt;cmath&gt; 重新实现为&lt;cmath_nopow&gt; 来确保pow 可用的类似解决方案。这避免了重载不在语言结构上的运算符,这会导致 AST 语法单子解耦和/或违反标准。也许试试 Haskell?

【讨论】:

  • 这是个好主意。如果一个人总是在顶层使用bracket/ ... /bracket 进行括号括起来,那么甚至应该可以保留标准的* 符号进行乘法运算!至于 Haskell……我不知道,也许让 Snoyman fix the Either monad first
  • @n.m.我不知道,bracket/ 看起来很混乱。我们可以使用bra&lt;&gt;ket,这适合之前在量子力学中的使用。
  • 如果使用像 * 这样的传统单符号运算符,您需要具有最高优先级的括号,因此 bra&lt; &gt;ket 很有吸引力,但无法正常工作。 bra/ /ket 对可能是下一个最好的选择。
  • 我认为遗留的原始 * 只会让程序员感到困惑;想象一下,如果有人有一个名为 cross 的变量!只是不值得向后弯腰支持不会保持最新状态的用户。
猜你喜欢
  • 1970-01-01
  • 2013-03-15
  • 2015-09-11
  • 1970-01-01
  • 1970-01-01
  • 2017-09-10
  • 1970-01-01
  • 2015-09-22
  • 1970-01-01
相关资源
最近更新 更多