用户自定义的中缀运算符

8

在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;
}

现场演示

不幸的是,这个幂运算符的优先级和结合性是错误的。所以我的问题是:如何修复这个问题? 我希望我的<pow>*拥有更高的优先级,并向右结合,就像数学符号一样。

编辑 通过使用不同的括号,例如|op|/op/*op*甚至,如果一个人愿意的话,<<--op-->>可以改变优先级。但是这种方式无法超过最高内置操作符优先级。但是今天C++具有模板元编程和类型推断的强大功能,一定有其他方法可以实现所需的结果。

此外,如果我能使用pow而不是pwr,那就太好了。不幸的是,在某些实现中,#include <cmath>会将pow带入全局命名空间,从而产生冲突。我们能否重载not运算符,使得声明形式为

not using std::pow;

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

更多阅读:Bjarne Stroustrup提出的相关建议


7
“轻松”……哈哈。(就像,说真的,我希望这只是为了研究目的) - Bartek Banachewicz
8
你无法“发明”新的运算符,你只能使用现有的运算符来模拟类似运算符但不是实际运算符的功能。这就是为什么运算符优先级不起作用的原因。回答你的问题,没有办法解决这个问题。 - Some programmer dude
2
这与“runs to”运算符有点类似的想法:while(i ----> 0)。非常方便! - eerorika
3
哈哈,Bjarne Stroustrup的那个提案完全是在耍人 :D - Morwenn
3
可能是提契诺意面收成的重复内容。 - Mike Kinghan
显示剩余3条评论
1个回答

6
最少惊讶原则很重要,关键在于 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()&&以返回推断出的类型。如果您的编译器不支持该功能,则=equals=可以以较小的清晰度代价返回正确的类型。
请注意,上述语法实际上可以在C++中使用类似于OP的技术实现。实际实现比SO帖子应包含的内容更多。
还有其他好处。众所周知,在编程语言中,晦涩的ASCII字符已经不受欢迎,而阅读C++的人可能会被像以下这样的表达式搞糊涂:
int z = (a + b* pow(c,pow(x,a+b))*y);

使用这种技术,所有操作符都有易于理解的可读名称,并且所有操作都在中缀表示法下执行,而不是混合前缀和中缀表示法。
为确保可用性,可以通过重新实现<cmath><cmath_nopow>来实现类似的解决方案。 这可以避免对语言结构上未定义的运算符进行重载,从而导致AST语法单元的解耦,或者违反标准。 可以尝试使用Haskell?

1
这是一个很棒的想法。如果在顶层始终使用 bracket/ ... /bracket 进行括号处理,甚至还可以保留标准的 * 表示乘法!至于 Haskell...我不知道,也许让 Snoyman 先 修复 Either monad 的问题? - n. m.
1
@n.m. 我不知道,bracket/ 看起来很混乱。我们可以使用 bra<>ket,这符合量子力学中先前的使用。 - Yakk - Adam Nevraumont
如果使用类似于 * 的旧符号运算符,则需要使用具有最高优先级的括号,因此即使它们很吸引人,bra< >ket 也不起作用。 bra/ /ket 对可能是下一个最好的选择。 - n. m.
我认为传统的原始 * 符号会让程序员感到困惑;想象一下如果有人有一个名为 cross 的变量!这并不值得为了支持那些不愿意保持更新的用户而费尽心思。 - Yakk - Adam Nevraumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接