Home > Software engineering >  Difference in behavior of pointer-to-member access operators
Difference in behavior of pointer-to-member access operators

Time:01-05

In C , I'm searching for the crucial sections of the standard explaining the subtle difference in behavior I've observed between the language's two pointer-to-member access operators, .* and ->*.

According to my test program shown below, whilst ->* seems to allow its right-hand expression to be of any type implicitly convertible to pointer to member of S, .* does not so. When compiling with gcc and clang, both compilers yield errors for the line marked '(2)' stating that my class Offset cannot be used as a member pointer.

Test Program https://godbolt.org/z/46nMPvKxE

#include <iostream>

struct S { int m; };

template<typename C, typename M>
struct Offset
{
    M C::* value;
    operator M C::* () { return value; }  // implicit conversion function
};

int main()
{
    S s{42};
    S* ps = &s;
    Offset<S, int> offset{&S::m};

    std::cout << ps->*offset << '\n';  // (1) ok
    std::cout << s.*offset << '\n';    // (2) error
    std::cout.flush();
}

Compiler Output

GCC 12.2:
    'offset' cannot be used as a member pointer, since it is of type 'Offset<S, int>'
clang 15.0:
    right hand operand to .* has non-pointer-to-member type 'Offset<S, int>'

Program Variation

In order to prove that ->* actually performs an implicit conversion using Offset's conversion function in the test program shown above, I declared it explicit for test purposes,

    explicit operator M C::* () { return value; }  // no longer implicit conversion function

resulting in the compilers to also yield errors for the line marked '(1)':

GCC 12.2:
    error: no match for 'operator->*' (operand types are 'S*' and 'Offset<S, int>')
    note: candidate: 'operator->*(S*, int S::*)' (built-in)
    note:   no known conversion for argument 2 from 'Offset<S, int>' to 'int S::*'
clang 15.0:
    error: right hand operand to ->* has non-pointer-to-member type 'Offset<S, int>'

Research

Whilst there is a well-documented difference between the two operators in that ->* is overloadable and .* is not, my code obviously does not make use of this option but rather relies on the built-in operator ->* defined for raw pointer type S*.

Besides differences in overloadability, I merely found documentation stating the similarity of the expressions. Cited from the standard (https://open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4868.pdf):

[7.6.4.2] The binary operator .* binds its second operand, which shall be of type “pointer to member of T” to its first operand, which shall be a glvalue of class T or of a class of which T is an unambiguous and accessible base class. The result is an object or a function of the type specified by the second operand.

[7.6.4.3] [...] The expression E1->E2 is converted into the equivalent form ((E1)).*E2.

And cited from cppreference.com (https://en.cppreference.com/w/cpp/language/operator_member_access#Built-in_pointer-to-member_access_operators):

The second operand of both operators is an expression of type pointer to member ( data or function) of T or pointer to member of an unambiguous and accessible base class B of T. The expression E1->*E2 is exactly equivalent to (*E1).*E2 for built-in types; that is why the following rules address only E1.*E2.

Nowhere have I found a notion of conversion of the right hand operand.

Question

What have I overlooked? Can someone point me to an explanation of this difference in behavior?

CodePudding user response:

When overloadable operators are used and at least one operand is of class or enumeration type, overload resolution is performed using a candidate set that includes built-in candidates ([over.match.oper]/3) - for ->* in particular, see [over.built]/9.

In this case, a built-in candidate is selected, so the implicit conversion is applied to the second operand, and then ->* is interpreted as the built-in operator ([over.match.oper]/11).

With .*, there's no overload resolution at all, so no implicit conversion either.

  • Related