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



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

(*it).*attribute

被编译器接受,但语法

it->*attribute

不接受,但出现错误

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

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

从C++参考中,我发现

表达式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;
}

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

如果你有一个类类型的迭代器,你写

(*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 ->*

最新更新