是免费的运营商 - > *超载邪恶吗?

时间:2010-04-23 07:20:43

标签: c++ operator-overloading

我在refuting the notion之后仔细阅读了第13.5节,内置运算符没有参与重载解析,并注意到operator->*上没有任何部分。它只是一个通用的二元运算符。

其兄弟operator->operator*operator[]都必须是非静态成员函数。这排除了用于从对象获取引用的操作符通常的自由函数重载的定义。但是不常见的operator->*被遗漏了。

特别是,operator[]有许多相似之处。它是二进制的(它们错过了使它成为n-ary的黄金机会),它接受左侧的某种容器和右侧的某种定位器。除了禁止免费功能外,其特殊规则部分13.5.5似乎没有任何实际效果。 (而这种限制甚至排除了对交换性的支持!)

因此,例如,这是perfectly legal

#include <utility>
#include <iostream>
using namespace std;

template< class T >
T &
operator->*( pair<T,T> &l, bool r )
    { return r? l.second : l.first; }

template< class T >
 T & operator->*( bool l, pair<T,T> &r ) { return r->*l; }

int main() {
        pair<int, int> y( 5, 6 );
        y->*(0) = 7;
        y->*0->*y = 8; // evaluates to 7->*y = y.second
        cerr << y.first << " " << y.second << endl;
}

很容易找到用途,但替代语法往往不会那么糟糕。例如,vector的缩放索引:

v->*matrix_width[2][5] = x; // ->* not hopelessly out of place

my_indexer<2> m( v, dim ); // my_indexer being the type of (v->*width)
m[2][5] = x; // it is probably more practical to slice just once

标准委员会是否忘记防止这种情况,是否认为太难看了,或是否存在真实世界的用例?

4 个答案:

答案 0 :(得分:19)

我所知道的最好的例子是Boost.Phoenix,它会重载此运算符以实现惰性成员访问。

对于那些不熟悉Phoenix的人来说,它是一个非常漂亮的库,用于构建看起来像普通表达式的actor(或函数对象):

( arg1 % 2 == 1 )     // this expression evaluates to an actor
                 (3); // returns true since 3 % 2 == 1

// these actors can also be passed to standard algorithms:
std::find_if(c.begin(), c.end(), arg1 % 2 == 1);
// returns iterator to the first odd element of c

通过重载operator%operator==来实现上述目标。 - 应用于actor arg1这些运算符返回另一个actor。可以以这种方式构建的表达式范围是极端的:

// print each element in c, noting its value relative to 5:
std::for_each(c.begin(), c.end(),
  if_(arg1 > 5)
  [
    cout << arg1 << " > 5\n"
  ]
  .else_
  [
    if_(arg1 == 5)
    [
      cout << arg1 << " == 5\n"
    ]
    .else_
    [
      cout << arg1 << " < 5\n"
    ]
  ]
);

在你使用Phoenix一段时间之后(不是你曾经回去过),你会尝试这样的事情:

typedef std::vector<MyObj> container;
container c;
//...
container::iterator inv = std::find_if(c.begin(), c.end(), arg1.ValidStateBit);
std::cout << "A MyObj was invalid: " << inv->Id() << std::endl;

哪个会失败,因为凤凰城的演员当然没有成员ValidStateBit。凤凰城通过重载operator->*来解决这个问题:

(arg1 ->* &MyObj::ValidStateBit)              // evaluates to an actor
                                (validMyObj); // returns true 

// used in your algorithm:
container::iterator inv = std::find_if(c.begin(), c.end(), 
      (arg1 ->* &MyObj::ValidStateBit)    );

operator->*的论点是:

  • LHS:演员返回MyObj *
  • RHS:会员地址

它返回一个actor,它评估LHS并在其中查找指定的成员。 (注意:你真的,真的希望确保arg1返回MyObj * - 在凤凰城出现问题之前你还没有看到大量的模板错误。这个小程序产生了76,738个痛苦的字符(Boost 1.54,gcc 4.6):

#include <boost/phoenix.hpp>
using boost::phoenix::placeholders::arg1;

struct C { int m; };
struct D { int n; };

int main() {
  ( arg1  ->*  &D::n ) (new C);
  return 0;
}

答案 1 :(得分:6)

我同意你的看法标准不一致,它不允许operator[]重载非成员函数,并允许operator->*。对于我的观点operator[]是数组,因为operator->*是结构/类(一个getter)。使用索引选择数组的成员。使用成员指针选择结构的成员。

最糟糕的是,我们可以尝试使用->*代替operator[]来获取像元素一样的数组

int& operator->*(Array& lhs, int i);

Array a;

a ->* 2 = 10;

还有另一种可能的不连贯性。我们可以使用非成员函数来重载operator+=以及@=形式的所有运算符,但我们无法对operator=执行此操作。

我真的不知道制定以下法律的理由是什么

struct X {
    int val;
    explicit X(int i) : val(i) {}
};
struct Z {
    int val;
    explicit Z(int i) : val(i) {}
};
Z& operator+=(Z& lhs, const X& rhs) {
    lhs.val+=rhs.val;
    return lhs;
}

Z z(2);
X x(3);
z += x;

并禁止

Z& operator=(Z& lhs, const X& rhs) {
    lhs.val=i;
    return lhs;
}

z = x;

很抱歉不回答你的问题,但更加混乱。

答案 2 :(得分:2)

谷歌搜索了一下,我发现有更多人询问是否使用过operator->*而不是实际建议。

有几个地方建议T &A::operator->*( T B::* )。不确定这是否反映了设计师的意图或T &A::operator->*( T A::* )内置的错误印象。与我的问题没有关系,但是我对在线讨论中发现的深度有所了解。文献

有人提到“D&amp; E 11.5.4”,我想这是C ++的设计和演变。也许这包含一个提示。否则,我只是得出结论,标准化忽略了一些无用的丑陋,而且其他大多数人都忽略了。

修改请参阅下文,了解D&amp; E报价的粘贴。

为了定量地说明,->*是可以通过自由函数重载的最紧密的绑定运算符。所有postfix-expression和一元运算符重载都需要非静态成员函数签名。一元运算符之后的下一个优先级是C样式转换,可以说对应于转换函数(operator type()),它也不能是自由函数。然后是->*,然后是乘法。 ->*可能像[]或类似%,他们可能已经走了两条路,他们选择了 EEEEEEVIL 的路径

答案 3 :(得分:1)

标准(2010-02-16工作草案,第5.5节)说:

  

- &gt; *表达式的结果是   仅当第二个操作数是a时才值左值   指向数据成员的指针。如果是第二个   operand是指向成员的空指针   值(4.11),行为是   未定义。

您可能希望此行为明确定义。例如,检查它是否为空指针并处理这种情况。因此,我认为标准允许 - &gt; *重载是正确的决定。