0

i took the below code from a different question on stackoverflow, im not sure What do the lines int (Foo :: * ptr); and int (Foo :: * ptr) (); mean? Can anyone share some answers?

struct Foo {
   int a;
   int b;
};


int main ()
{
    Foo foo;
    int (Foo :: * ptr);

    ptr = & Foo :: a;
    foo .*ptr = 123; // foo.a = 123;

    ptr = & Foo :: b;
    foo .*ptr = 234; // foo.b = 234;
}

Member functions are almost the same.

struct Foo {
   int a ();
   int b ();
};


int main ()
{
    Foo foo;
    int (Foo :: * ptr) ();

    ptr = & Foo :: a;
    (foo .*ptr) (); // foo.a ();

    ptr = & Foo :: b;
    (foo .*ptr) (); // foo.b ();
}

Debugging to no avail

Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
20MikeMike
  • 13
  • 4
  • `int (Foo ::*ptr)` declares `ptr` as a a pointer to a member of `Foo` that is of type `int`. `int (Foo ::*ptr)()` declares `ptr` as a pointer to a member function of `Foo` that accepts no arguments and return an `int`. – Peter Dec 04 '22 at 05:43

1 Answers1

1

Pointer to members is a long story to tell. First we assume that you've known what a normal pointer is.

Pointer to members suggests that it can point to the specific member of any instance of class. There are two types of pointer to members, first to member variables and second to member functions.

Before that, the variables and functions can be static or non-static. For static ones, it's no other than normal global ones from the program's perspective, e.g. in Linux ELF, static data are stored in .data directly, where the global variables are also stored. From the angle of the programmers, they are just accessing a special global function / variable as well, just adding some Class::. So, the pointer to static member variable / function is just the same as the pointer to a normal variable / function.

Now let's talk about the non-static ones. Non-static members should always bind to some specific object, e.g. obj.a or obj.func() and Class::a or Class::func() is illegal. Then, is it possible to use a pointer to suggest that "I hope to point to a specific member of any instance, and when I want to use it, I will bind an instance"? That's what the pointer to members do.

Wait... you may think: "That bothers! Why can't I just use the .?". To maintain the consistency, we will go back to this question finally. Now we assume it's useful first, and see what syntax it uses.

class ClassOpTest
{
public:
    int nsVar; // non-static variable.
    void nsFunc(int){return;} // non-static function.
};

int ClassOpTest::* nsVarPtr = &ClassOpTest::nsVar;
void (ClassOpTest::*nsFuncPtr)(int) = &ClassOpTest::nsFunc;
int main()
{
    ClassOpTest object2;
    ClassOpTest* object2Ptr = &object2;
    
    object.*nsVarPtr = 1; // equals to object.nsVar = 1;
    object2.*nsVarPtr = 2; // equals to object2.nsVar = 2;
    object2Ptr->*nsVarPtr = 3; // equals to object2.nsVar = 3;
    
    // Note that these paratheses are necessary, considering the operation order. 
    // If there are not, nsFuncPtr() will be resolved first rather than object.*nsFuncPtr().
    // That is, the compiler will regard the nsFuncPtr as a normal function (pointer)
    // rather than pointer to member function, so "obj.*" is just a meaningless mess. 
    // All in all, no paratheses will cause compilation error.
    (object.*nsFuncPtr)(1); // equals to object.nsFunc(1);
    (object2Ptr->*nsFuncPtr)(2); // equals to object2.nsFunc(2);
    return 0;
}

You may find it's troublesome to write types like this, so you can use deduced type in C++11 as:

using ClassOpTestIntPtr = decltype(&ClassOpTest::nsVar);
using ClassOpTestFuncPtr = decltype(&ClassOpTest::nsFunc);
ClassOpTestIntPtr nsVarPtr = &ClassOpTest::nsVar;
ClassOpTestFuncPtr nsFuncPtr = &ClassOpTest::nsFunc;

Notice that the decltype doesn't mean it always points to nsVar or nsFunc; it means the type same as them.

You may also think .* or ->* is oblique(me too!), then you can use std::invoke in C++17 like this :

std::invoke(nsVarPtr, object2) = 1; // equals to object.*nsVarPtr = 1;
std::invoke(nsVarPtr, &object2) = 2; // equals to object2Ptr->*nsVarPtr = 2; 
// both work.
std::invoke(nsFuncPtr, object2, 1); // equals to (object.*nsFunc)(1);
std::invoke(nsFuncPtr, &object2, 2); // equals to (object2Ptr->*nsFunc)(2);

std::invoke is significantly useful, but that's not the point of the answer. In a nutshell, it will use corresponding operator when the second calling parameter varies.

Finally, why is it useful? In my point of view, that's mostly because the pointer only conveys the type, and the type may infer lots of members. For instance:

struct RGB
{
    std::uint8_t r;
    std::uint8_t g;
    std::uint8_t b;
};

and I hope to blend two std::vector<RGB> using Intel's SIMD intrinsics. First for r, that is:

reg1 = _mm_set_epi16(RGBdata1[i + 7].r, RGBdata1[i + 6].r, RGBdata1[i + 5].r,
        RGBdata1[i + 4].r, RGBdata1[i + 3].r, RGBdata1[i + 2].r,
        RGBdata1[i + 1].r, RGBdata1[i].r);

reg2 = _mm_set_epi16(RGBdata2[i + 7].r, RGBdata2[i + 6].r, RGBdata2[i + 5].r,
        RGBdata2[i + 4].r, RGBdata2[i + 3].r, RGBdata2[i + 2].r,
        RGBdata2[i + 1].r, RGBdata2[i].r);

reg1 = _mm_mullo_epi16(reg1, alphaReg1);
reg2 = _mm_mullo_epi16(reg2, alphaReg2);
resultReg1 = _mm_add_epi16(reg1, reg2);
// for simplicity, code below omitted; there are also manys operation to get the result.
// ...
// store back
_mm_store_si128((__m128i*)buffer, resultReg1);
for(int k = 0; k < 16; k++)
{
    outRGBdata[i + k].r = buffer[k];
}

So what about g and b? Oops, okay, you have to paste the code twice. What if you find some bugs and want to change something? You have to paste again for g and b. That suffers! If we use pointer to members, then :

using RGBColorPtr = std::uint8_t RGB::*;
void SIMDBlendColor(RGB* begin1, RGB* begin2,
                    RGB* outBegin, RGBColorPtr color, 
                    __m128i alphaReg1, __m128i alphaReg2)
{
    __m128i resultReg1, reg1, reg2;
    alignas(16) std::uint8_t buffer[16];
    reg1 = _mm_set_epi16((begin1 + 7)->*color, (begin1 + 6)->*color, 
                         (begin1 + 5)->*color, (begin1 + 4)->*color, 
                         (begin1 + 3)->*color, (begin1 + 2)->*color, 
                         (begin1 + 1)->*color, begin1->*color);

    reg2 = _mm_set_epi16((begin2 + 7)->*color, (begin2 + 6)->*color, 
                         (begin2 + 5)->*color, (begin2 + 4)->*color, 
                         (begin2 + 3)->*color, (begin2 + 2)->*color, 
                         (begin2 + 1)->*color, begin2->*color);
    
    reg1 = _mm_mullo_epi16(reg1, alphaReg1);
    reg2 = _mm_mullo_epi16(reg2, alphaReg2);
    resultReg1 = _mm_add_epi16(reg1, reg2);
    // ...
    _mm_store_si128((__m128i*)buffer, resultReg1);
    for(int k = 0; k < 16; k++)
    {
        (outBegin + k)->*color = buffer[k];
    }
    return;
}

Then, you can just call like this :

SIMDBlendColor(RGBdata1.data() + i, RGBdata2.data() + i, outRGBdata.data() + i, &RGB::r, alphaReg1, alphaReg2);
SIMDBlendColor(RGBdata1.data() + i, RGBdata2.data() + i, outRGBdata.data() + i, &RGB::g, alphaReg1, alphaReg2);
SIMDBlendColor(RGBdata1.data() + i, RGBdata2.data() + i, outRGBdata.data() + i, &RGB::b, alphaReg1, alphaReg2);

Clean and beautiful!

BTW, I strongly recommend you to check iso-cpp-wiki for more information.

o_oTurtle
  • 1,091
  • 3
  • 12
  • I would be better if you explained the argument types alittle. Is begin1 a pointer address to an RGB instance? If so, why are you applying pointer arithmetic to the address? Does it access the struct members of the instance? But i think i understand the general use case, essentially member pointers allow you to configure access to a group of members. By defining a generic member pointer type, we can pass in a group of members with the same generic type to a function. Which can then be used to access members of an instance or pointer to instance. – 20MikeMike Dec 04 '22 at 17:28
  • Bare in mind, i have only recently came back to C++ since 7 years ago. – 20MikeMike Dec 04 '22 at 17:29
  • @20MikeMike `begin1` is a pointer, and a pointer only stores an address. Sometimes there is only one valid instance, so it's just as your saying "to an RGB instance". But here, it means the begin address of an array, and in fact a bunch of RGB instances are stored sequentially. Concretely, `RGBdata1` is `std::vector`, and `.data()` will get the begin address, `+i` will make an offset, so that the i th element is viewed as begin address for the function. – o_oTurtle Dec 05 '22 at 02:05