Topic: Why is pointer-to-member-of-member not possible?


Author: n2xssvv.g02gfr12930@ntlworld.com (n2xssvv g02gfr12930)
Date: Fri, 10 Mar 2006 19:42:13 GMT
Raw View
Steve Rencontre wrote:
> Following discussions in c.l.c++ and elsewhere, it's pretty clear that
> the following is not possible in C++, but it was suggested I ask here
> for the rationale why.
>
> Basically, I have:
>
>  struct S1 { int a1; };
>  struct S2 ( S1 s1; int a2; };
>
> What I want is a pointer-to-member which will let me access either of
> the ints in S2 - that is, I'd like to be able to say,
>
>  S2 s2;
>  int S2::*p;
>
>  p = &S2::a2; // fine
>  s2.*p = 123; // equivalent to s2.a2 = 123
>
>  p = &S2::s1.a1; // ILLEGAL!
>  s2.*p = 345; // would be equiv to s2.s1.a1 = 345
>
> Now sure, I understand why '&S2::s1.a1' is illegal in terms of the C++
> syntax rules, but it seems to me that it's both meaningful and
> reasonable (if not common!) to want to do it.
>
> I can, of course, get the effect I want in true C hacker style with,
>
>  size_t offset = offsetof (S2, s1.a1);
>  *(int *) ((char *) &s2 + offset) = 345;
>
> which, while ugly, is legal and portable.
>
> Can anyone enlighten me as to /why/ there is no syntax for this which is
> a bit closer to the spirit of C++?
>
> Thanks.
>
> ---
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]
>

p is a pointer to an int member of an S2 object
not a pointer to an int member of an S1 object
the only way to have p able to point to either
is to have a union of the 2 pointer types.

 {
  struct S1
  {
   int a1;
  };
  struct S2
  {
   S1 a1;
   int a2;
  };
  union
  {
   int S1::*s1;
   int S2::*s2;
  }
  p;
  S2 s2;
  p.s2 = &S2::a2;
  s2.*p.s2 = 5;
  p.s1 = &S1::a1;
  s2.a1.*p.s1 = 7;
 }

Personally I don't recommend this sort of thing as any misuse will
potentially cause serious bugs that will be very difficult to fix.

If you want a pointer to member of a member why not create a template
class. Below is a start at defining such a beast.

template <typename BASE,typename M1, typename M2>
class MyM2Pointer
{
   private:
     M1 BASE::*p1;
     M2 M1::*p2;
   public:
};

JB

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: reply-to@valid.address (Steve Rencontre)
Date: Mon, 13 Mar 2006 15:31:01 GMT
Raw View
n2xssvv g02gfr12930 wrote:

> p is a pointer to an int member of an S2 object
> not a pointer to an int member of an S1 object
> the only way to have p able to point to either
> is to have a union of the 2 pointer types.

Thanks for the response, but you miss my point. I was originally after a
syntactical construct which was identical in both cases, but this isn't
possible in C++. The union just complicates things without changing that
fact.

All I was really asking here was /why/ I couldn't do something in a nice
C++-like fashion instead of having to bodge it with offsetof and casts.

I think the answer resolves to something like:

 Nobody suggested doing it, or if they did, nobody on the
 Committee thought it worth the effort, given that there are
 cases in which it /couldn't/ work. If you want it that badly,    hey,
you've got offsetof!

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: usenet@aristeia.com (Scott Meyers)
Date: Sun, 19 Mar 2006 20:07:13 GMT
Raw View
Martin Bonner wrote:
> In the context of POD objects, no.  But consider:
>
> .    struct vb { int vbi };
> .    struct s : virtual vb { int si };
>
> The offset of vbi from the start of s is not a compile time constant,
> but has to be evaluated at run-time.

As long as you know the complete hierarchy, computing the offset of each data
member of an object is easy.  The problem arising from virtual inheritance is
that, given a pointer or reference to a base only, the distance from the base
subobject to data members of the most derived class depends on the hierarchy,
and you don't know that until you've seen the most derived class.

My understanding is that the way such cases are handled is that the most derived
constructor does whatever is necessary to make it possible to find derived class
fields given a base class pointer or reference (e.g., initialize
vbase-to-derived pointers or put values in offset tables stored off the vtbl),
but the distance from the virtual base to the derived fields is known *during
compilation* at the time the code for the most derived constructor is generated.

In the code above, the offset from vbi to the start of s is constant and
computable during compilation, but only if you know that no further classes
derive from s -- if you know the complete hierarchy.  Which, in general, you
can't know.

Scott

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Martin Bonner" <martinfrompi@yahoo.co.uk>
Date: Wed, 8 Mar 2006 10:07:37 CST
Raw View
Steve Rencontre wrote:
> Thanks for your more detailed reply here, but my response is the same as
> the one to your post in c.l.c++, to wit:
>
> Greg wrote:
>
> > I would suggest focussing on the capabilities of the pointer you are
> > asking for - and not on the relatively simple manner with which you
> > intend to use it.
>
> In an ultimate sense, yes, but my requirement is strictly in the context
> of a POD struct, and I can't conceive of any situation in which what I
> want doesn't boil down to a simple numerical offset. This may be a
> failure of my imagination, but I don't think so.
>
> TBH, I can't think of /any/ situation in which the address of a data
> item relative to the base address of its containing object is not a
> compile-time constant, and I'd be fascinated if you could give me an
> example.

In the context of POD objects, no.  But consider:

.    struct vb { int vbi };
.    struct s : virtual vb { int si };

The offset of vbi from the start of s is not a compile time constant,
but has to be evaluated at run-time.
>
> Sure, there are (non-ISO) concepts like MSVC++ properties, which /look/
> like simple data items but aren't, but I'm not asking for that. A
> pointer-to-data-member that actually referenced a property in that sense
> would be a weird and wonderful thing. Sticking within the normal
> definition of data members, though, I really don't see where the problem
> lies.

I don't actually see that they would be much worse.  They would have to
be pointers to get and set functions under the hood, but nothing that
isn't readily implemented.
>
> To expand a little further, can you give me an instance of a case in
> which the offsetof() hack would /not/ work? I can, of course, wrap
> /that/ in a macro or template, but that's just wallpapering over the
> ugliness, and I'd prefer something more intrinsically beautiful.

See above.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: reply-to@valid.address (Steve Rencontre)
Date: Wed, 8 Mar 2006 19:13:54 GMT
Raw View
Martin Bonner wrote:
> Steve Rencontre wrote:
>>
>> TBH, I can't think of /any/ situation in which the address of a data
>> item relative to the base address of its containing object is not a
>> compile-time constant, and I'd be fascinated if you could give me an
>> example.
>
> In the context of POD objects, no.  But consider:
>
> .    struct vb { int vbi };
> .    struct s : virtual vb { int si };
>

Fair enough! I guess I still had POD too firmly in my head.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Greg Herlihy" <greghe@pacbell.net>
Date: Sun, 19 Feb 2006 10:18:41 CST
Raw View
Steve Rencontre wrote:
> Following discussions in c.l.c++ and elsewhere, it's pretty clear that
> the following is not possible in C++, but it was suggested I ask here
> for the rationale why.
>
> Basically, I have:
>
>  struct S1 { int a1; };
>  struct S2 ( S1 s1; int a2; };
>
> What I want is a pointer-to-member which will let me access either of
> the ints in S2 - that is, I'd like to be able to say,
>
>  S2 s2;
>  int S2::*p;
>
>  p = &S2::a2; // fine
>  s2.*p = 123; // equivalent to s2.a2 = 123
>
>  p = &S2::s1.a1; // ILLEGAL!
>  s2.*p = 345; // would be equiv to s2.s1.a1 = 345
>
> Now sure, I understand why '&S2::s1.a1' is illegal in terms of the C++
> syntax rules, but it seems to me that it's both meaningful and
> reasonable (if not common!) to want to do it.
>
> I can, of course, get the effect I want in true C hacker style with,
>
>  size_t offset = offsetof (S2, s1.a1);
>  *(int *) ((char *) &s2 + offset) = 345;
>
> which, while ugly, is legal and portable.
>
> Can anyone enlighten me as to /why/ there is no syntax for this which is
> a bit closer to the spirit of C++?

C++ could support a "member member pointer" but for the language to do
so, the Standard would have treat it as its own pointer type - one
distinct from the member pointer type that it currently supports. The
reason for having to do so would be the additional storage requirements
necessitated by the additional level of reference in the pointer.

Recall that a member pointer, such as int S2::*, must be capable of
pointing to any int member of S2 (whether S2 has only one int member or
100 such members). The requirements of the member pointer's
representation are independent of the class's actual definition. So
even if S2 has no int members, an int S2::* member pointer is
unchanged: and has the same storage requirements for an S2 class
defined with any number of members of type int. So it would be
reasonable to expect that a member member pointer would require twice
the storage of a member pointer, and therefore could be treated as the
same type.

Language support for a member member pointer would naturally prompt the
question, well why not also support a member member member pointer?
Since each additional level of membership effectively requires a new
pointer type, the Standard would have to specify how many of these new
pointer types (that is, how many layers of membership should be
navigable via a pointer) a conforming implementation would have to
support (I think 6 sounds about right. Anyone else?).

When considering any possible addition to the language, the preference
is always to find a comparable alternative using the current facilities
of the language. One reason is to prevent "feature sprawl" since a
language that incorporated every suggestion would be so large as to
become unmanageable (well, it could also turn into Perl :-). But
another reason is that a custom implementation can usually be fine
tuned to achieve the desired behavior; while a feature incorporated
into the language may not provide such flexibility. And indeed, the
recursive nature of a member member pointer suggests at least one
existing C++ technique - namely templates - that when combined with
operator overloading could perhaps provide a member member pointer
implementation.

What would a member member pointer implementation look like? Well, I
can show what I came up with. And while my implementation will probably
not be appearing in <functional> anytime soon, I think this code does
show that the C++ as a language is user-extensible - and often to a
surprising (at least for me) degree:

    #include <cstddef>
    #include <iostream>

    template<class T, class M1, class M2>
    class member_member_ptr
    {
        public:
            typedef  M1 T::*outer;
            typedef  M2 M1::*inner;

        member_member_ptr(outer mp1, inner mp2) :
                m_outer(mp1),
                m_inner(mp2)
                {
                }

        M2 operator->*(const T& c)
        {
            return &(&c->*m_outer)->*m_inner;
        }

    private:
        outer m_outer;
        inner m_inner;
    };

    // Test case

    struct S2
    {
        S2() : b(42)
        {
        }
        int b;
    };

    struct S1
    {
        S2 s2;
        int a;
    };

    // helper template creation function

    template <class C, class M1, class M2>
    member_member_ptr<C,M1,M2>
    inline mm_ptr( M1 C::*mp1, M2 M1::*mp2)
    {
        return member_member_ptr<C,M1,M2>(mp1,mp2);
    }

    int main()
    {
        // declare an instance of S1
        S1  s;

        // apply member member pointer
        // to retrieve s.s2.b
        int b = mm_ptr(&S1::s2, &S2::b)->*s;

        // inspect result
        std::cout << "b is " << b << "\n";
    }

    Output:
    b is 42

Greg

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Steve Rencontre <reply-to@valid.address>
Date: Sun, 19 Feb 2006 17:46:11 CST
Raw View
Thanks for your more detailed reply here, but my response is the same as
the one to your post in c.l.c++, to wit:

Greg wrote:

> I would suggest focussing on the capabilities of the pointer you are
> asking for - and not on the relatively simple manner with which you
> intend to use it.

In an ultimate sense, yes, but my requirement is strictly in the context
of a POD struct, and I can't conceive of any situation in which what I
want doesn't boil down to a simple numerical offset. This may be a
failure of my imagination, but I don't think so.

TBH, I can't think of /any/ situation in which the address of a data
item relative to the base address of its containing object is not a
compile-time constant, and I'd be fascinated if you could give me an
example.

Sure, there are (non-ISO) concepts like MSVC++ properties, which /look/
like simple data items but aren't, but I'm not asking for that. A
pointer-to-data-member that actually referenced a property in that sense
would be a weird and wonderful thing. Sticking within the normal
definition of data members, though, I really don't see where the problem
lies.

To expand a little further, can you give me an instance of a case in
which the offsetof() hack would /not/ work? I can, of course, wrap
/that/ in a macro or template, but that's just wallpapering over the
ugliness, and I'd prefer something more intrinsically beautiful.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: skaller@users.sourceforge.net (skaller)
Date: Fri, 3 Mar 2006 07:30:29 GMT
Raw View
On Fri, 17 Feb 2006 00:59:30 -0600, Steve Rencontre wrote:

> Following discussions in c.l.c++ and elsewhere, it's pretty clear that
> the following is not possible in C++, but it was suggested I ask here
> for the rationale why.
>
> Basically, I have:
>
>  struct S1 { int a1; };
>  struct S2 ( S1 s1; int a2; };
>

> Can anyone enlighten me as to /why/ there is no syntax for this which is
> a bit closer to the spirit of C++?

Politics. The committee didn't consider a complete algebraic
type model for pointers to members to be a high priority.
Therefore they ignored my proposal in this respect.

For this reason, you should just use C offsetof() macro,
and ptrdiff_t, since it is complete if not type safe,
and complain about the rule that prohibits
taking the offset of a member of a constructible type.

The correct way to handle this is very simple and quite
obvious. However you have missed the most
important part of the proposal which is more
general than your example suggests:

 S2 S1::*p1;     // offset of an S2 in S1
 int S2::*p2;    // offset of an int in S2
 int S1::*p3;    // offset of an int in S1

 p1 = &S2::s1; // fine
 p2 = &S1::a1;   // fine
 p3 = &S1::s1.a1; // ERROR!

You complained about the lack illustrated on this line
and so missed the real point:

 p3 = p1 . p2; // ADD offsets

Adding offsetof()/ptrdiff_t offsets in C is no problem!
The fact C++ doesn't provide the correct typesafe operator
to do this is inexcusable. It isn't as if the idea and a
sketch of a proposal was not presented.

Note that it isn't immediately obvious .. but operator . is
the CORRECT notation to use for addition of offsets (you'll
realise this after thinking about it for a while).

Offset addition is associative. In particular delayed binding
to pointers should be possible: this works now:

 int *pi;
 S2 *ps2;
 pi = (ps2 .* p1) .* p2;

but you cannot apply the associative law:

 pi = ps .* (p1 . p2);
    = ps .* p3;

This is just daft.

You can fix the problem easily using:

 template <class Container, class Member>
 class ptm {
  ptrdiff_t offset;
 public:
  ptm(ptrdiff_t o) : offset(o) {}
  ..
 };

and then overload operator +. However you will have
to use offsetof() to obtain the offset and pass it to
the constructor. Such offsets are NOT compatible with
C++ pointers to members, which are best not used at all,
since they don't provide a complete algebra. Offsetof
with a few templates can.

The main problem with the template solution is that
C++ notion of const is broken and can't be generalised.
So whilst providing templates for pointers to members
is easy for non-const cases, it will never work if you
want to take constness into account. A native implementation
would handle that.


--
John Skaller <skaller at users dot sourceforge dot net>
Async P/L, Realtime software consultants
Felix for C/C++ programmers http://felix.sourceforge.net


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Steve Rencontre <reply-to@valid.address>
Date: Fri, 17 Feb 2006 00:59:30 CST
Raw View
Following discussions in c.l.c++ and elsewhere, it's pretty clear that
the following is not possible in C++, but it was suggested I ask here
for the rationale why.

Basically, I have:

 struct S1 { int a1; };
 struct S2 ( S1 s1; int a2; };

What I want is a pointer-to-member which will let me access either of
the ints in S2 - that is, I'd like to be able to say,

 S2 s2;
 int S2::*p;

 p = &S2::a2; // fine
 s2.*p = 123; // equivalent to s2.a2 = 123

 p = &S2::s1.a1; // ILLEGAL!
 s2.*p = 345; // would be equiv to s2.s1.a1 = 345

Now sure, I understand why '&S2::s1.a1' is illegal in terms of the C++
syntax rules, but it seems to me that it's both meaningful and
reasonable (if not common!) to want to do it.

I can, of course, get the effect I want in true C hacker style with,

 size_t offset = offsetof (S2, s1.a1);
 *(int *) ((char *) &s2 + offset) = 345;

which, while ugly, is legal and portable.

Can anyone enlighten me as to /why/ there is no syntax for this which is
a bit closer to the spirit of C++?

Thanks.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]