指向成员的语法糖适用于数组但不适用于std :: vector

时间:2017-10-22 01:58:34

标签: c++ operator-overloading stdvector dereference pointer-to-member

我写了一些示例代码(如下)来理解C ++的指向成员指针功能。但是,我遇到了一个奇怪的问题,其中包含语法

(*it).*attribute

被编译器接受,但语法

it->*attribute

未被接受,错误

  

左手操作数到->*必须是指向与右手操作数兼容的类的指针,但是std::__1::__wrap_iter<Bowl *>

但是,如果我取消注释Bowl bowls[3] =并注释掉std::vector<Bowl> bowls =,即从使用std::vector切换到使用基本数组,则两种语法都能正常工作。

C++ reference,我找到了

  

表达式E1->*E2完全等同于内置类型的(*E1).*E2

所以似乎错误与数组是内置类型但std::vector不是这样的事实有关。在那一节的后面,我找到了

  

针对用户定义的运算符的重载解析,对于类型D,B,R的每种组合,其中类类型B与D是同一类,或者是D的明确且可访问的基类,并且R是对象或函数类型,以下函数签名参与重载解析:

R& operator->*(D*, R B::*);

但由于std::vector未定义operator->*,我感到非常困惑。为什么我在一种语法而不是另一种语法上得到此错误,并且只有在使用std::vector而不是原始数组时?

#include <iostream>
#include <iterator>
#include <vector>

class Bowl
{
public:
  unsigned int apples;
  unsigned int oranges;
  unsigned int bananas;

  Bowl(unsigned int apples, unsigned int oranges, unsigned int bananas)
    : apples(apples), oranges(oranges), bananas(bananas)
  {
    // nothing to do here
  }
};

template <typename TClass, typename TIterator, typename TResult>
TResult sum_attribute(TIterator begin, TIterator end, TResult TClass::*attribute)
{
  TResult sum = 0;
  for (TIterator it = begin; it != end; ++it)
  {
    sum += (*it).*attribute;
    sum += it->*attribute;
  }
  return sum;
}

int main()
{
  std::vector<Bowl> bowls =
  // Bowl bowls[3] =
    {
      {1, 2, 3},
      {4, 5, 6},
      {7, 8, 9},
    };

  int num_apples = sum_attribute(std::begin(bowls), std::end(bowls), &Bowl::apples);
  int num_oranges = sum_attribute(std::begin(bowls), std::end(bowls), &Bowl::oranges);
  int num_bananas = sum_attribute(std::begin(bowls), std::end(bowls), &Bowl::bananas);

  std::cout << "We have " << num_apples << " apples, " << num_oranges << " oranges, and " <<
    num_bananas << " bananas. Now wasn't that fun?" << std::endl;
}

1 个答案:

答案 0 :(得分:3)

为了更好地理解这一点,我认为讨论与*->运算符的相似之处很有帮助。

如果你有一个类类型的迭代器并且你写了

(*itr).field

C ++将其解释为

itr.operator*().field

同样,如果你写

itr->field

C ++将其解释为

itr.operator->().field

请注意,这些是调用不同的重载运算符。第一个调用operator*,第二个调用operator->。这与内置类型不同;正如您所指出的,对于内置类型,语法为

base->field

只是

的简写
(*base).field

当你谈到

时,会发生类似的事情
(*itr).*memberPtr

itr->*memberPtr

在第一种情况下,C ++将(*itr).*memberPtr视为

itr.operator*().*memberPtr

请注意,这意味着.*运算符直接应用于正在迭代的项,并且迭代器本身不会使.*运算符重载。另一方面,如果你写

itr->*memberPtr

C ++将其视为对

的调用
itr.operator->*(memberPtr)

并报告错误,因为迭代器类型不需要 - 并且很少 - 实现operator ->*。 (事实上​​,我在C ++编程过程中看到了operator ->*的零重载。)

原始类型将base->*memberPtr视为(*base).*memberPtr这一事实无关紧要,原因与原始类型将base->field视为(*base).field的事实相同;编译器不会自动从operator*生成operator->,反之亦然。

有一个单独的问题,为什么迭代器不需要这样做,不幸的是,这是我没有很好的答案。我的猜测是,这是一个非常罕见的案例,没人想到把它放在标准中,但我不确定。

至于为什么这适用于原始数组但不适用于std::vector,原始数组是使用支持->*运算符的原始指针。 std::vector的迭代器不需要是原始指针,如果它们是类类型的对象,上面的推理解释了为什么你不应该期望它们支持operator ->*