I wrote some sample code (below) to understand C++'s pointer-to-member feature. However, I've come across a strange issue wherein the syntax
(*it).*attribute
is accepted by the compiler, but the syntax
it->*attribute
is not accepted, with the error
left hand operand to
->*must be a pointer to class compatible with the right hand operand, but isstd::__1::__wrap_iter<Bowl *>
However, if I uncomment Bowl bowls[3] = and comment out std::vector<Bowl> bowls =, i.e. switch from using std::vector to using a primitive array, then both syntaxes work correctly.
From the C++ reference, I found
The expression
E1->*E2is exactly equivalent to(*E1).*E2for built-in types
so it seems that the error has something to do with the fact that arrays are built-in types but std::vector is not. Later in that section, I found
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::*);
but as std::vector does not define operator->* I am very confused. Why do I get this error on one syntax but not the other, and only when using std::vector instead of a primitive array?
#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;
}
To better understand this, I think it's helpful to talk about the parallels to the * and -> operators.
If you have an iterator of class type and you write
(*itr).field
C++ interprets it to mean
itr.operator*().field
Similarly, if you write
itr->field
C++ interprets it to mean
itr.operator->().field
Notice that these are calling different overloaded operators. The first one calls operator*, and the second calls operator->. This is different than built-in types; as you noted, for built-in types the syntax
base->field
is just a shorthand for
(*base).field
There's something similar going on here when you talk about
(*itr).*memberPtr
and
itr->*memberPtr
In the first case, C++ treats (*itr).*memberPtr to mean
itr.operator*().*memberPtr
Notice that this means that the .* operator is being applied directly to the item being iterated over and that the iterator itself isn't overloading the .* operator. On the other hand, if you write
itr->*memberPtr
C++ treats it as a call to
itr.operator->*(memberPtr)
and reports an error because iterator types aren't required to - and rarely do - implement operator ->*. (In fact, I've seen exactly zero overloads of operator ->* in the course of programming in C++).
The fact that primitive types treat base->*memberPtr as (*base).*memberPtr here is irrelevant, the same reason that the fact that primitive types treat base->field as (*base).field; the compiler will not automatically generate operator* from operator-> or vice-versa.
There's a separate question about why iterators aren't required to do this, and that's something I don't have a good answer to, unfortunately. My guess is that this is a sufficiently rare case that no one thought to put it in the standard, but I'm not sure.
As for why this works for primitive arrays but not std::vector, primitive arrays you're working with raw pointers, which support the ->* operator. The iterators for std::vector aren't required to be raw pointers, and if they're objects of class type, the above reasoning explains why you shouldn't expect them to support operator ->*.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With