What does "S::" mean in the declaration "int S::* pmi = &S::mi"?

This is a two-part question.

1. I'm trying to understand how to use the pointer-to-member access operator but I don't understand the example code provided by [1].

Specifically, I don't understand what's being declared on line 17 and 18:

17
18
int S::* pmi = &S::mi;
int (S::* pf)(int) = &S::f;


Line 17 looks like the initialization of a pointer pmi but what the heck is int S:: part of the type? Does it mean "an int, but not just any int; an int that must be inside the class S"? I guess it could also be a "scoped pointer", as in, "an S-class member pointer that is an int". -\_( o.O)_/-

Line 18 looks like a function pointer initialization (pf is a pointer to a function that takes int and returns an int). But, again, what the heck is up with S::?

2. Does lhr->*rhs mean *(*(lhs).rhs)?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
 
struct S
{
    S(int n): mi(n) {}
    mutable int mi;
    int f(int n) { return mi + n; }
};
 
struct D: public S
{
    D(int n): S(n) {}
};
 
int main()
{
    int S::* pmi = &S::mi;
    int (S::* pf)(int) = &S::f;
 
    const S s(7);
    //s.*pmi = 10; // error: cannot modify through mutable
    std::cout << s.*pmi << '\n';
 
    D d(7); // base pointers work with derived object
    D* pd = &d;
    std::cout << (d.*pf)(7) << ' '
              << (pd->*pf)(8) << '\n';
}


So (pd->*pf)(8) is a function invocation and my hypothesis is that it is equivalent to *(*(pd).pf)(8)

[1] cppref: C++ Operator Precedence
https://en.cppreference.com/w/cpp/language/operator_member_access#Built-in_pointer-to-member_access_operators
Last edited on
I don't understand what's being declared on line 17 and 18

In a sense they are not really pointers at all. Consider that even though you've initialized them, they are not pointing to anything! They need an underlying struct/class to offset from before they actually "point" to anything. So they are more like offsets than pointers.

Does lhr->*rhs mean *(*(lhs).rhs)?

No. The analogy to a->b is:

a->b means (*a).b

a->*b means (*a).*b

So (pd->*pf)(8) is a function invocation and my hypothesis is that it is equivalent to *(*(pd).pf)(8)

An easy hypothesis to test.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
 
struct T {
    int x, y;
    int a(int n) { return n * 2; }
    int b(int n) { return n * 3; }
};
 
int main() {
    T t {1, 2};
 
    int T::* p = &T::x;
    std::cout << t.*p << '\n';        // 1
    p = &T::y;
    std::cout << t.*p << '\n';        // 2
 
    int (T::* pf)(int) = &T::a;
    std::cout << (t.*pf)(7) << '\n';  // 14
    pf = &T::b;
    std::cout << (t.*pf)(7) << '\n';  // 21
 
    T* tp = &t;
    std::cout << tp->*p << '\n';      // 2
 
    // Test of hypothesis
    // std::cout << *(*(tp).pf)(8) << '\n';  // will not compile

    // This works:
    std::cout << ((*tp).*pf)(8) << '\n';
}

Last edited on
DizzyDon wrote:
a->*b means (*a).*b

Thanks for the correction. Indeed, the member-access operator . has higher precedence than the dereference * operator. What I meant to type, instead of *(*(lhs).rhs), was *((*lhs).rhs).

The .* operator, as the notation/precedence rule suggests, is just member-access followed by a dereference of that member, right?

So does (*a).*b also mean *((*a).b)?

DizzyDon wrote:
In a sense they are not really pointers at all. Consider that even though you've initialized them, they are not pointing to anything! They need an underlying struct/class to offset from before they actually "point" to anything. So they are more like offsets than pointers.

That's really strange. I've never come across that concept before. Is there a name for it?

Plugging the int T::* p declaration into [2], I get the english equivalent of "declare p as pointer to member of class T int".

[2] cdecl: C gibberish <-> English
https://cdecl.org/
Last edited on
ElusiveTau wrote:
The .* operator, as the notation/precedence rule suggests, is just member-access followed by a dereference of that member, right?

So does (*a).*b also mean *((*a).b)?

Apparently not!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

struct S
{
    int *pb = nullptr;
    S(int *n) : pb(n) {}
};

int main()
{
    int x = 10; 
    S s1{&x}; 
    S *s1ptr = &s1; 

    std::cout << ++(*(s1ptr->pb));  // Increment x by 1, print the result
    std::cout << ++(s1ptr->*pb);   // Was hoping to do the same but got: "identifier "pb" is undefined"
}


Just came across this article (reading ...):
https://learn.microsoft.com/en-us/cpp/cpp/pointer-to-member-operators-dot-star-and-star?view=msvc-170

I see. First, pointers not only can point to types:
1
2
3
4
5
6
7
8
9
10
11
12
struct Foo {
    int fooi1 = 10;
    int fooi2 = 20;
};

struct Bar {
    int bari = 30;
    char barc = 'd';
};

int *iptr;  // Plain old pointer-to-int
Foo *fptr;  // Plain old pointer-to-Foo (a custom class) 

but a pointer can be declared to point to a member of a specific class with some associated type:

1
2
3
4
5
6
7
8
int Foo::* foomemptr;     // Pointer to a Foo member (which must be an int)
foomemptr = &Foo::fooi1;  // Ok: foomemptr points to Foo::fooi1 member. The member is of a type that foomem expects: int
foomemptr = &Foo::fooi2;  // Ok: foomemptr points to (still) a Foo member of integer type
foomemptr = &Bar::bari;   // Error: a value of type "int Bar::*" cannot be assigned to an entity of type "int Foo::*"

int Bar::* barmemptr;
barmemptr = &Bar::bari;   // Ok: Initializer is a pointer-to-Bar-member-that-is-int
barmemptr = &Bar::barc;   // Error: a value of type "char Bar::*" cannot be assigned to an entity of type "int Bar::*"  


So the int Foo::* part of the declarator for foomemptr means "a pointer that can only point to int found in the Foo class".

So just as we can dereference a "normal pointer":
 
std::cout << *iptr << std::endl;

We can also dereference a "pointer-to-specific-class-member" using the .* operator:

1
2
3
4
5
6
7
8
    Foo f1; // Let's instantiate a Foo object
    
    // Above, we've assigned foomemptr to point to the Foo::fooi2 member
    std::cout << f1.*foomemptr << std::endl;  // Equivalent to:  std::cout << f1.fooi2 << std::endl;

    // Let's reassign foomemptr so that it points to another Foo-int-member (Foo::fooi1)
    foomemptr = &Foo::fooi1;
    std::cout << f1.*foomemptr << std::endl;  // Equivalent to:  std::cout << f1.fooi1 << std::endl;  


But suppose instead of a Foo object, we started out with a pointer-to-Foo object. We can access fooi1 and fooi2 the same way by using the ->* operator:

1
2
3
4
5
6
7
    Foo *f1ptr = &f1; 

    // Recall: foomemptr still points to Foo:fooi1 at this point
    std::cout << f1ptr->*foomemptr << std::endl;  // Equivalent to:  std::out << f1.fooi1 << std::endl;

    foomemptr = &Foo::fooi2; 
    std::cout << f1ptr->*foomemptr << std::endl;  // Equivalent to:  std::out << f1.fooi2 << std::endl; 


Full Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>

struct Foo {
    int fooi1 = 10;
    int fooi2 = 20;
};

struct Bar {
    int bari = 30;
    char barc = 'd';
};

int main()
{
    int Foo::* foomemptr;     // Pointer to a Foo member (which must be an int)
    foomemptr = &Foo::fooi1;  // Ok: foomemptr points to Foo::fooi1 member. The member is of a type that foomem expects: int
    foomemptr = &Foo::fooi2;  // Ok: foomemptr points to (still) a Foo member of integer type
    //foomemptr = &Bar::bari; // Error: a value of type "int Bar::*" cannot be assigned to an entity of type "int Foo::*"

    int Bar::* barmemptr;
    barmemptr = &Bar::bari;   // Ok: Initializer is a pointer-to-Bar-member-that-is-int
    //barmemptr = &Bar::barc;   // Error: a value of type "char Bar::*" cannot be assigned to an entity of type "int Bar::*" 

    Foo f1; // Let's instantiate a Foo object
    
    // Above, we've assigned foomemptr to point to the Foo::fooi2 member
    std::cout << f1.*foomemptr << std::endl;  // "20"

    // Let's reassign foomemptr so that it points to another Foo-int-member (Foo::fooi1)
    foomemptr = &Foo::fooi1;
    std::cout << f1.*foomemptr << std::endl;  // "10"

    // Say we started out with a pointer-to-Foo object instead. We can access fooi1 and fooi2 the same way by using the ->* operator
    Foo *f1ptr = &f1; 

    // Recall: foomemptr still points to Foo:fooi1 at this point
    std::cout << f1ptr->*foomemptr << std::endl;  // "10"

    foomemptr = &Foo::fooi2; 
    std::cout << f1ptr->*foomemptr << std::endl;  // "20"
}
Last edited on
What I meant to type, instead of *(*(lhs).rhs), was *((*lhs).rhs).

But that doesn't compile either, as I guess you've learned.
rhs cannot be used after a dot since it is not the name of a member but is only a kind of pseudo-pointer to a member of a certain type.
It is a new kind of thing and needs a new notation.
To use a member pointer you will always need either .* or ->*

I guess you can kind of simulate it like this:

1
2
    T o { 3, 4 };
    std::cout << *(int*)((char*)&o + offsetof(T, y)) << '\n';  // print y member of o 


Interestingly,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
 
int main() {
    struct T {
        int a, b, c, d;
    } o { 11, 22, 33, 44 };

    union U {
        int T::* xp;
        size_t   sz;
    } u;

    u.xp = &T::d;
    std::cout << u.sz << '\n';               // 12
    std::cout << offsetof(T, d) << '\n';     // 12
}

Last edited on
Topic archived. No new replies allowed.