自由运算符->* 重载是邪恶的吗?

发布于 2024-08-30 03:54:48 字数 1495 浏览 5 评论 0原文

反驳这样的观点之后,我正在仔细阅读第13.5节:内置运算符不参与重载决策,并注意到没有关于 operator->* 的部分。它只是一个通用的二元运算符。

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

特别是,operator[] 有许多相似之处。它是二进制的(他们错过了将其变成 n 进制的黄金机会),并且它接受左侧的某种容器和右侧的某种定位器。它的特殊规则部分 13.5.5 除了禁止自由函数之外似乎没有任何实际作用。 (这个限制甚至排除了对交换性的支持!)

因此,例如,这是完全合法

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

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

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

标准委员会是否忘记阻止这种情况,是否被认为太难看而无需打扰,或者是否存在实际用例?

I was perusing section 13.5 after refuting the notion that built-in operators do not participate in overload resolution, and noticed that there is no section on operator->*. It is just a generic binary operator.

Its brethren, operator->, operator*, and operator[], are all required to be non-static member functions. This precludes definition of a free function overload to an operator commonly used to obtain a reference from an object. But the uncommon operator->* is left out.

In particular, operator[] has many similarities. It is binary (they missed a golden opportunity to make it n-ary), and it accepts some kind of container on the left and some kind of locator on the right. Its special-rules section, 13.5.5, doesn't seem to have any actual effect except to outlaw free functions. (And that restriction even precludes support for commutativity!)

So, for example, this is 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;
}

It's easy to find uses, but alternative syntax tends not to be that bad. For example, scaled indexes for 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

Did the standards committee forget to prevent this, was it considered too ugly to bother, or are there real-world use cases?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

征﹌骨岁月お 2024-09-06 03:54:48

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

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

( 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==. - 应用于演员 arg1 这些运算符返回另一个演员。可以用这种方式构建的表达式的范围是极端的:

// 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;

这会失败,因为当然 Phoenix 的演员不这样做有一个成员ValidStateBit。 Phoenix 通过重载 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: 成员的地址

它返回一个计算 LHS 并在其中查找指定成员的参与者。 (注意:您真的,真的想要确保arg1返回MyObj * - 在得到一些东西之前,您还没有看到大规模的模板错误Phoenix 中的错误。这个小程序生成了 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;
}

The best example I am aware of is Boost.Phoenix, which overloads this operator to implement lazy member access.

For those unfamiliar with Phoenix, it is a supremely nifty library for building actors (or function objects) that look like normal expressions:

( 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

It achieves the above by overloading operator% and operator==. - applied to the actor arg1 these operators return another actor. The range of expressions which can be built in this manner is extreme:

// 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"
    ]
  ]
);

After you have been using Phoenix for a short while (not that you ever go back) you will try something like this:

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;

Which will fail, because of course Phoenix's actors do not have a member ValidStateBit. Phoenix gets around this by overloading 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->*'s arguments are:

  • LHS: an actor returning MyObj *
  • RHS: address of a member

It returns an actor which evaluates the LHS and looks for the specified member in it. (NB: You really, really want to make sure that arg1 returns MyObj * - you have not seen a massive template error until you get something wrong in Phoenix. This little program generated 76,738 characters of pain (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;
}
神妖 2024-09-06 03:54:48

我同意你的观点,标准上存在不一致,它不允许使用非成员函数重载 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;

理由是什么抱歉,不回答您的问题,但增加了更多的混乱。

I agree with you that there is an incoherence on the standard, It doesn't allows overloading of operator[] with non-member functions and allows it for operator->*. For my point of view operator[] is to arrays as operator->* is to structs/classes (a getter). Members of an array are selected using an index. Members of a struct are selected using member pointers.

The worst is that we can be tempted to use ->* instead of operator[] to get an array like element

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

Array a;

a ->* 2 = 10;

There is also another possible incoherence. We can use a non member function to overload operator+= and all the operator of the form @=) and we cannot do it for operator=.

I don't really know what is the rationale to make the the following legal

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;

and forbidding

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

z = x;

Sorry to not answer to your question, but adding even more confusion.

爱已欠费 2024-09-06 03:54:48

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

有几个地方建议 T &A::operator->*( TB::* )。不确定这是否反映了设计者的意图,还是错误地认为 T &A::operator->*( TA::* ) 是内置函数。与我的问题并不真正相关,但给出了我在在线讨论和讨论中发现的深度的想法。文学。

其中提到了“D&E 11.5.4”,我认为它是 C++ 的设计和演化。或许这其中蕴含着某种暗示。否则,我会得出结论,这是一种被标准化所忽视的无用的丑陋,大多数其他人也是如此。

编辑 请参阅下面的 D&E 报价粘贴。

定量地说,->* 是可以由自由函数重载的最紧密的绑定运算符。所有后缀表达式和一元运算符重载都需要非静态成员函数签名。一元运算符之后的下一个优先级是 C 风格的强制转换,可以说它对应于转换函数(operator type()),但它也不能是自由函数。然后是 ->*,然后是乘法。 ->* 可以像 []% 一样,它们可以选择任何一种方式,并且它们选择了 EEEEEEVIL

Googling around a bit, I found more instances of people asking whether operator->* is ever used than actual suggestions.

A couple places suggest T &A::operator->*( T B::* ). Not sure whether this reflects designer's intent or a misimpression that T &A::operator->*( T A::* ) is a builtin. Not really related to my question, but gives an idea of the depth I found in online discussion & literature.

There was a mention of "D&E 11.5.4" which I suppose is Design and Evolution of C++. Perhaps that contains a hint. Otherwise, I'm just gonna conclude it's a bit of useless ugliness that was overlooked by standardization, and most everyone else too.

Edit See below for a paste of the D&E quote.

To put this quantitatively, ->* is the tightest binding operator that can be overloaded by a free function. All the postfix-expression and unary operators overloads require nonstatic member function signatures. Next precedence after unary operators are C-style casts, which could be said to correspond to conversion functions (operator type()), which also cannot be free functions. Then comes ->*, then multiplication. ->* could have been like [] or like %, they could have gone either way, and they chose the path of EEEEEEVIL.

放我走吧 2024-09-06 03:54:48

标准(工作草案 2010-02-16,第 5.5 条)规定:

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

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

Standard (Working Draft 2010-02-16, § 5.5) says:

The result of an ->* expression is an
lvalue only if its second operand is a
pointer to data member. If the second
operand is the null pointer to member
value (4.11), the behavior is
undefined.

You may want this behavior to be well-defined. For example, check if it is a null pointer and handle this situation. SO I quess it is right decision for a standard to allow ->* overloading.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文