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

Syntax sugar for pointer-to-member works for array but not std::vector

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

(*it).*attribute

被编译器接受,但是语法

it->*attribute

不被接受,错误

left hand operand to ->* must be a pointer to class compatible with the right hand operand, but is std::__1::__wrap_iter<Bowl *>

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

C++ reference,我发现

The expression E1->*E2 is exactly equivalent to (*E1).*E2 for built-in types

所以错误似乎与数组是内置类型但 std::vector 不是。在该部分的后面,我发现

In overload resolution against user-defined operators, for every combination of types D, B, R, where class type B is either the same class as D or an unambiguous and accessible base class of D, and R is either an object or function type, the following function signature participates in overload resolution:

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

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

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

(*itr).field

C++ 将其解释为

itr.operator*().field

同样,如果你写

itr->field

C++ 将其解释为

itr.operator->().field

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

base->field

只是shorthand

(*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 的迭代器不需要是原始指针,如果它们是 class 类型的对象,上述推理解释了为什么你不应该期望它们支持 operator ->*