【问题标题】:Is it possible to overload operator associativity in C++?是否可以在 C++ 中重载运算符关联性?
【发布时间】:2014-02-22 02:50:06
【问题描述】:

我正在构建一个具有略微不对称添加的类。在投诉出现之前,它必然是不对称的。当两个对象相加时,必须进行转换(需要一些时间的操作),并且转换最自然地发生在正确的加法上。

为了具体说明,这里有一个通用的例子来说明正在发生的事情......

class Foo {
    char _fav;
    int _prop;

public:
    const char fav() const {return _fav;}
    const int prop() const (return _prop;}
    void changeFav(char); // complicated method that also changes _prop
    void changeProp(int); // straightforward method
}

Foo
operator + (Foo A, Foo B) {
    Foo sum;
    if (A.fav() != B.fav()) A.changeFav(B.fav);
    sum.changeProp(A.prop() + B.prop());   
    return sum;
}

为了添加两个Foo,它们需要具有相同的_fav,因此必须选择要转换的_fav。根据Foo的详细信息,最自然的是将左和数改为右和数。

但是,当这样做时:

Foo A,B,C;
Foo D = A + B + C; // D = (A + B) + C
Foo E = A + (B + C);

如果A 已经拥有与C 相同的_fav,则changeFav 会为D 调用两次(一次将A._fav 更改为B._fav,然后再次将(A+B)._fav 更改为C._fav)和一次E(将B._fav更改为C._fav)。我更喜欢后者,但希望避免强迫用户使用括号进行多次添加。

有没有办法重载operator + 的关联性来实现这一点?

【问题讨论】:

  • 您的operator+ 没有返回任何内容,并且两个成员函数缺少返回类型。
  • 无法提供完整的答案,但简而言之,不要返回 foo 并使正确的操作数不是 foo。这只是我亲爱的打出的意识流。
  • @PengOne:我们通常希望您的代码至少是有效的 C++。 :)
  • @PengOne:是的,一直都是。当您编写包含错误的伪代码时,我们无法保证您的问题就是您要问的问题。确保您记得编写返回类型和返回语句很容易,所以是的,我们要求您这样做。
  • 不改变关联性只是为了解决这个例子?你可以想出一个场景,左关联会比右关联产生更快的代码,反之亦然。为什么不应该只是一个可变参数 Foo sum(Foos...) 函数并在那里实现它?

标签: c++ operator-overloading associative


【解决方案1】:

你可以做一个黑客。您必须使用另一种类型来保存操作的中间结果,然后您可以使用隐式转换来评估结果。以下是如何在 C++ 中实现 Python 风格的比较运算符的示例:

#include <vector>
#include <cstdio>

struct S {
    S(int x) : val(x) { }
    int val;
};

struct Comparison {
    std::vector<S> operands;
    explicit Comparison(S x)
    {
        operands.push_back(x);
    }
    operator S()
    {
        auto i = operands.begin(), e = operands.end();
        S prev = *i;
        for (i++; i != e; i++) {
            S cur = *i;
            if (prev.val >= cur.val)
                return S(0);
            prev = cur;
        }
        return S(1);
    }
    void append(const Comparison &a)
    {
        operands.insert(
            operands.end(),
            a.operands.begin(),
            a.operands.end());
    }
    void append(const S &a)
    {
        operands.push_back(a);
    }
};

Comparison operator<(const Comparison &left, const Comparison &right)
{ Comparison result(left); result.append(right); return result; }
Comparison operator<(const Comparison &left, const S &right)
{ Comparison result(left); result.append(right); return result; }
Comparison operator<(const S &left, const Comparison &right)
{ Comparison result(left); result.append(right); return result; }
Comparison operator<(const S &left, const S &right)
{ Comparison result(left); result.append(right); return result; }

int main()
{
    S x(0);
    x = S(0) < S(1) < S(2) < S(3);
    std::printf("0 < 1 < 2 < 3 = %d\n", x.val);
    x = S(0) < S(1) < S(3) < S(2);
    std::printf("0 < 1 < 3 < 2 = %d\n", x.val);
    return 0;
}

但是,在这种情况下,我会很快放弃 + 运算符。我会避免将+ 用于任何非关联和可交换的操作,因为这是数学中+ 的约定。相反,您可以使用可变参数函数(使用模板)来执行所需的计算。

【讨论】:

  • 我喜欢这种精神。关于+,在数学中添加两个线性变换时,必须先用同一个基表示,这和我的例子很相似。
  • @PengOne:没有必要使用基来表示线性变换来添加它们 - 无论如何,并非所有线性变换都可以使用基来表示。您可以只定义“(f + g)(x)= f(x)+ g(x)”。
  • 是的,但我要说明的是,如果 _fav 是选择的基础,_prop 是该基础的矩阵表示,我的示例是自然的。
【解决方案2】:

有点,但你不会喜欢它的“如何”。

首先,您需要阅读并理解Boost.Proto 文档。然后,您需要弄清楚如何转换所有表达式树以反转操作顺序。然后,您需要对最终用户透明地评估表达式树。可以派上用场吗?我对 Proto 没有太多了解,但类似于 this article on Proto-based optimizations 的内容可能是一个有用的起点。

【讨论】:

    【解决方案3】:

    来自 c++ 标准第 5 条,

    重载运算符遵守第 5 条中指定的语法规则。

    Where 第 5 条规定了运算符的优先级和关联性。

    【讨论】:

      【解决方案4】:

      没有。那么为什么不改变右手操作数的偏好呢?

      【讨论】:

      • 这很复杂,涉及到(真实的)Foo 的技术细节以及+Foo 的意义。你能详细说明“不”吗?这是一成不变的还是你从未见过?
      • @PengOne:如果您的问题是“我可以更改+ 的关联性”,那么答案是“不可以”。这不仅仅是我“从未见过它完成”:这是语言语法的一个事实。
      • @LightnessRacesinOrbit:但是,可以让A+B 表现得好像它具有相反的关联性,如果更多代码的话,这几乎是相同的结果。
      猜你喜欢
      • 2012-09-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-01
      • 2010-10-20
      • 2010-10-21
      • 1970-01-01
      • 2018-08-31
      相关资源
      最近更新 更多