11

I have the following code:

#include <iostream>
#include <string>

using namespace std;

struct foo_s {
    string a;
    string b;
    string c;
};

void print_field(foo_s* foo, string foo_s::* field) {
    cout << "field: " << field << " - " << foo->*field << endl;
}

int main() {
   foo_s my_foo = {
       "a",
       "b",
       "c",
   };

   print_field(&my_foo, &foo_s::a);
   print_field(&my_foo, &foo_s::b);
   print_field(&my_foo, &foo_s::c);

   return 0;
}

Its output is:

field: 1 - a                                                                                                                                                                                                              
field: 1 - b                                                                                                                                                                                                              
field: 1 - c  

I'm having a bit of trouble understanding the specifics of what's going on in the print_field() function. Namely:

  1. What's the type of field? I imagine it's pointer-to-string-foo_s-member
  2. Why is the value of field always the same (1 in this case), yet foo->*field yields different results?

Mainly, I'm baffled at #2. I imagined field would be an "offset" from the start of the struct and foo->*field would have been conceptually equivalent to something like

char* ptr = static_cast<char*>(foo);
ptrdiff_t offset = somehow_get_the_byte_offset_from_pointer_to_member(field);
ptr = ptr[offset];
string result = *static_cast<string*>(ptr);

but that seems to be out since field's value doesn't vary across calls. What am I missing? How exactly is this specific operation described by the standard?

anthonyvd
  • 7,329
  • 4
  • 30
  • 51

2 Answers2

18

There's no overload for << to format the value of a member pointer, so you won't get anything particularly useful if you try. There is an overload for bool, and member pointers are convertible to bool, so that is what happens here. The pointer isn't null, so it converts to true, which by default is formatted as 1.

To demonstrate further, you could try streaming boolalpha first; then you should see true rather than 1.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • 2
    Wow, that teaches me to stay with good old printf, and if it is only because I understand it better. – martin May 15 '15 at 14:54
  • 5
    @martin: That will give undefined behaviour if you try to print a member pointer. C++ I/O is at least typesafe, if sometimes confusing. – Mike Seymour May 15 '15 at 14:55
  • 1
    @martin Fun question - what format specifier do you intend to use for the pointer to member? You don't even know the size of the thing (generally). – Angew is no longer proud of SO May 15 '15 at 14:56
  • 1
    @Angew Oh well, may be I should stop writing code ... . For other interested readers, [here](http://stackoverflow.com/questions/15519855/address-of-c-pointer-to-class-data-member-in-visual-studio) is the reference. – martin May 15 '15 at 14:57
  • Ah! Thanks, that makes sense. I should have understood that's what was going on when `static_cast`'ing the pointer to int triggered a compile-time error. – anthonyvd May 15 '15 at 15:26
7
  1. The type of field is, as you say, a pointer to a member of foo_s of type std::string.

  2. The value of field is 1 in all of these cases, because pointers to member are convertible to bool, so when you output them, you get a 1 because they are not null.

TartanLlama
  • 63,752
  • 13
  • 157
  • 193