Why are member data pointers to inner members prohibited?

225 views
Skip to first unread message

Myriachan

unread,
Jun 4, 2015, 5:35:07 PM6/4/15
to std-dis...@isocpp.org
Why is it illegal to acquire a member data pointer to a member of a member?  Like:


struct Kitty
{
   
int x;
   
float y;
   
double z[2];
};

struct Meow
{
   
Kitty k1;
   
Kitty k2;
};

int main()
{
   
float Meow::*member = &Meow::k2.y;
   
double Meow::*element = &Meow::k2.z[1];
   
//...
}


If a non-static data member of a class is of a class type with virtual bases, that shouldn't matter, because members must be of most-derived type.  This applies recursively.  Therefore, the location of the virtual base class subobjects should always be known for non-static data members.  The only case of virtual base class subobjects to worry about is the top-level class, which member pointers already have to handle.

So why is this prohibited?

Melissa

Nicol Bolas

unread,
Jun 4, 2015, 6:03:23 PM6/4/15
to std-dis...@isocpp.org
It's not prohibited; you're doing it wrong. `x`, `y`, and `z` are not members of `Meow`; they are members of `Kitty`.

Also, I'm not positive, but I'm fairly sure that you can't get a member pointer to an element of an array.

Myriachan

unread,
Jun 4, 2015, 7:17:35 PM6/4/15
to std-dis...@isocpp.org
On Thursday, June 4, 2015 at 3:03:23 PM UTC-7, Nicol Bolas wrote:
It's not prohibited; you're doing it wrong. `x`, `y`, and `z` are not members of `Meow`; they are members of `Kitty`.

Also, I'm not positive, but I'm fairly sure that you can't get a member pointer to an element of an array.

Why is it wrong?  I know that it is wrong by the current rules, but I don't see a good argument that it should be wrong.

Yes, attempting to get a member pointer to an array element (as opposed to the whole array) is also currently illegal.

Melissa

Richard Smith

unread,
Jun 4, 2015, 8:01:29 PM6/4/15
to std-dis...@isocpp.org, Jeff Snyder
Because the default state for a language feature is "not present"; this is not a natural consequence of the existing rules, and no-one has proposed adding it.

That said, this sort of thing has been discussed by at least a handful of committee members (but as far as I'm aware, not by EWG). I think there may even have been an early draft of a paper. The idea was to allow . and [] on an expression of pointer-to-member type, to compute a pointer-to-member of the specified subobject of the pointee. (This is a bit more general than what you're suggesting, because it can be done after the pointer-to-member is first formed.) That'd look like this:

double Meow::*member = (&Meow::k2).z[1];
// or
auto x = &Meow::k2;
auto y = x.z[1];

There was also a suggestion of allowing unary - on a pointer-to-member, to transform 'T U::*' into 'U T::*' (so you can get back to the surrounding object from a pointer to a subobject), and of allowing .* to combine 'T U::*' and 'U V::*' into 'T V::*'.

Myriachan

unread,
Jun 4, 2015, 10:00:08 PM6/4/15
to std-dis...@isocpp.org, je...@caffeinated.me.uk
On Thursday, June 4, 2015 at 5:01:29 PM UTC-7, Richard Smith wrote:
On Thu, Jun 4, 2015 at 2:35 PM, Myriachan <myri...@gmail.com> wrote:
Why is it illegal to acquire a member data pointer to a member of a member?  Like:


struct Kitty
{
   
int x;
   
float y;
   
double z[2];
};

struct Meow
{
   
Kitty k1;
   
Kitty k2;
};

int main()
{
   
float Meow::*member = &Meow::k2.y;
   
double Meow::*element = &Meow::k2.z[1];
   
//...
}


If a non-static data member of a class is of a class type with virtual bases, that shouldn't matter, because members must be of most-derived type.  This applies recursively.  Therefore, the location of the virtual base class subobjects should always be known for non-static data members.  The only case of virtual base class subobjects to worry about is the top-level class, which member pointers already have to handle.

So why is this prohibited?

Because the default state for a language feature is "not present"; this is not a natural consequence of the existing rules, and no-one has proposed adding it.


OK; it just seems appropriate to match something most implementations support with offsetof().

 
That said, this sort of thing has been discussed by at least a handful of committee members (but as far as I'm aware, not by EWG). I think there may even have been an early draft of a paper. The idea was to allow . and [] on an expression of pointer-to-member type, to compute a pointer-to-member of the specified subobject of the pointee. (This is a bit more general than what you're suggesting, because it can be done after the pointer-to-member is first formed.) That'd look like this:

double Meow::*member = (&Meow::k2).z[1];
// or
auto x = &Meow::k2;
auto y = x.z[1];


This would work.  A small caveat is that to support &Meow::k2.z[1] in addition to (&Meow::k2).z[1] would require both to be implemented separately due to the grammar.  Or just disallowing the first in favor of the second, which is awkward but works.

There was also a suggestion of allowing unary - on a pointer-to-member, to transform 'T U::*' into 'U T::*' (so you can get back to the surrounding object from a pointer to a subobject), and of allowing .* to combine 'T U::*' and 'U V::*' into 'T V::*'.

There's also the version Thiago advocated(?), where these are binary operator - and + respectively.  I even managed to get a simple library-only implementation working on MSVC (thanks to the undocumented /d1nonUDToperators command line option to allow Standard-prohibited operator overloads).

The T U::* becoming U T::* design is a bit problematic from an ABI perspective due to most implementations' representation of null data member pointers as -1.  For example, this in GCC and MSVC in an imagined implementation of unary operator -:

struct Inner
{
   
char c;
};

struct Demo
{
   
Inner first;
   
Inner second;
};

int main()
{
   
Demo Inner::*n = nullptr;
   
Demo Inner::*a = -&Demo::second;

    std
::printf("%d\n", n == a);
   
return 0;
}


Melissa

Nicol Bolas

unread,
Jun 4, 2015, 11:59:09 PM6/4/15
to std-dis...@isocpp.org
On Thursday, June 4, 2015 at 7:17:35 PM UTC-4, Myriachan wrote:
On Thursday, June 4, 2015 at 3:03:23 PM UTC-7, Nicol Bolas wrote:
It's not prohibited; you're doing it wrong. `x`, `y`, and `z` are not members of `Meow`; they are members of `Kitty`.

Also, I'm not positive, but I'm fairly sure that you can't get a member pointer to an element of an array.

Why is it wrong?  I know that it is wrong by the current rules, but I don't see a good argument that it should be wrong.

Define "should". `x`, `y` and `z` are unquestionably not members of `Meow`. Therefore, it is not unreasonable to suggest that they should not be directly accessible via a `Meow::*` member pointer. After all, it is a pointer to a member of `Meow`, and they're not members. This is at least part of the logic behind it.

Another part of that logic starts with a syntactic issue. Namely, how to name members of members of a type, without having an actual instance of that type. `Meow::k1` is the qualified name of that member as a construct. However, using the '.' syntax doesn't mean to get a name; it means to access a member of an object. You don't have an object of type `Meow` or `Kitty` yet, so `Meow::k1.x` is functionally meaningless.

To give it meaning would be to give the '.' operator a new function. It would no longer be an operator; it would become part of the name syntax, but only in certain cases. And that would be... troublesome, syntactically, since it means that the definition of a name needs to change. It'd be more reasonable to use :: notation.

But even that gets into trouble if you want to have array accessing. For several reasons.

First, does the array index need to be a constant expression, or do you allow runtime dynamic indices? Second, `[]` is, like the `.` notation, an operator on expressions; it doesn't define a name. So yet again, you need to expand the definition of a member name.

And regardless of the answer to those, `[]` syntax creates a new problem: `std::array` can't work, since its `[]` syntax is based on calling `operator[]`. Even if you made that operator `constexpr`, it wouldn't be able to be called, since the `[]` syntax isn't evaluating an expression anymore; it's part of a name. Which is again part of the trouble with expanding member name syntax in such a fashion.

And what about members that are tuples? Why can't you get a "member pointer" to a tuple element?

I can keep going with that, but my point is clear: there are a lot of difficulties surrounding something like that. It's not as simple as you're making it out to be.

I can appreciate the desire to have a pointer to a sub-member. But maybe there's a better way to do what you're trying to do than to use pointers-to-members at all.

Richard Smith

unread,
Jun 5, 2015, 1:41:44 AM6/5/15
to std-dis...@isocpp.org, Jeff Snyder
True. Note that such an approach is already broken:

#include <cassert>
struct a {};
struct b : a {};
struct c : a, b { int n; };
int b::*p = (int b::*)&c::n;
int main() { assert(p != nullptr); }

That assert fires in implementations following the Itanium C++ ABI, even though the C++ standard requires it not to. Both issues could be fixed by subtracting 1 from negative pointer to data members (or by changing the representation of a null pointer to member, but that's a much more significant ABI break).
 
For example, this in GCC and MSVC in an imagined implementation of unary operator -:

struct Inner
{
   
char c;
};

struct Demo
{
   
Inner first;
   
Inner second;
};

int main()
{
   
Demo Inner::*n = nullptr;
   
Demo Inner::*a = -&Demo::second;

    std
::printf("%d\n", n == a);
   
return 0;
}


Melissa

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.

David Krauss

unread,
Jun 5, 2015, 1:49:06 AM6/5/15
to std-dis...@isocpp.org
On 2015–06–05, at 11:59 AM, Nicol Bolas <jmck...@gmail.com> wrote:

Define "should". `x`, `y` and `z` are unquestionably not members of `Meow`. Therefore, it is not unreasonable to suggest that they should not be directly accessible via a `Meow::*` member pointer. After all, it is a pointer to a member of `Meow`, and they're not members. This is at least part of the logic behind it.

They’re subobjects of Meow. Members of a base subobject are also not member subobjects of a derived class, but pointer-to-members can reach them.

Another part of that logic starts with a syntactic issue. Namely, how to name members of members of a type, without having an actual instance of that type. `Meow::k1` is the qualified name of that member as a construct. However, using the '.' syntax doesn't mean to get a name; it means to access a member of an object. You don't have an object of type `Meow` or `Kitty` yet, so `Meow::k1.x` is functionally meaningless.

To give it meaning would be to give the '.' operator a new function. It would no longer be an operator; it would become part of the name syntax, but only in certain cases. And that would be... troublesome, syntactically, since it means that the definition of a name needs to change. It'd be more reasonable to use :: notation.

.” and “::” already behave similarly. Both invoke member name lookup. The LHS of :: names a class (or such), and the LHS of . names an object. It’s not too far out to define . for the case that the LHS is an unbound subobject.

Even if it were defined for the case that the LHS is a pointer-to-member type, as Richard mentioned, it would certainly still be a built-in operator. Compare to the complexity of overloaded operator->.

And what about members that are tuples? Why can't you get a "member pointer" to a tuple element?

This could easily be done in the library: std::get_ptm< 5, decltype (my_tuple) >(). Probably already doable in most current tuple implementations, which store objects in members of base classes.

I can keep going with that, but my point is clear: there are a lot of difficulties surrounding something like that. It's not as simple as you're making it out to be.

I can appreciate the desire to have a pointer to a sub-member. But maybe there's a better way to do what you're trying to do than to use pointers-to-members at all.

Well, anything that PTMs can do, can be done by function pointers. For example, int foo:* is just a special-case optimization of int&(*)(foo&). Instead of the member-address syntax, the function pointer could be initialized by a lambda expression.

However, PTM access is just address arithmetic, and that will always be faster than an indirect call.

PTMs are nice, but fickle, like many micro-optimizations are. There’s no reason they shouldn’t get incremental improvements. They’re not an anti-pattern or anything, just a little awkward. Certainly they come in handy for reflection. However… it’s hard to love them.

David Krauss

unread,
Jun 5, 2015, 1:55:04 AM6/5/15
to std-dis...@isocpp.org

On 2015–06–05, at 1:48 PM, David Krauss <pot...@mac.com> wrote:

They’re subobjects of Meow. Members of a base subobject are also not member subobjects of a derived class, but pointer-to-members can reach them.

(… given the standard pointer to member conversion. A PTM “literal” never has such a value, but pointer-to-member-of-base is certainly a valid value.)

Myriachan

unread,
Jun 5, 2015, 5:16:55 PM6/5/15
to std-dis...@isocpp.org, pot...@mac.com
On Thursday, June 4, 2015 at 10:49:06 PM UTC-7, David Krauss wrote:

.” and “::” already behave similarly. Both invoke member name lookup. The LHS of :: names a class (or such), and the LHS of . names an object. It’s not too far out to define . for the case that the LHS is an unbound subobject.

Even if it were defined for the case that the LHS is a pointer-to-member type, as Richard mentioned, it would certainly still be a built-in operator. Compare to the complexity of overloaded operator->.


C++11 and later already have this concept with the extended sizeof: sizeof(Meow::k1.x) and sizeof(Meow::k2.z[1]) are legal.

However, I now think that Richard's syntax of (&Meow::k1).x is necessary to avoid breaking existing code, because &Meow::k1.x can be a valid expression already in a way that would become ambiguous:

struct Base
{
   
struct Inner
   
{
       
int x;
   
} inner;
   
int y[2];
};

struct Derived : Base
{
   
int *Function1() { return &Base::inner.x; }
   
int *Function2() { return &Base::y[1]; }
};


And what about members that are tuples? Why can't you get a "member pointer" to a tuple element?

This could easily be done in the library: std::get_ptm< 5, decltype (my_tuple) >(). Probably already doable in most current tuple implementations, which store objects in members of base classes.


Yes, and then that proposed operator + can be used to make them into pointers to the outer object.

PTMs are nice, but fickle, like many micro-optimizations are. There’s no reason they shouldn’t get incremental improvements. They’re not an anti-pattern or anything, just a little awkward. Certainly they come in handy for reflection. However… it’s hard to love them.

It's them or offsetof, IMO. =^-^=

Melissa

Thiago Macieira

unread,
Jun 5, 2015, 6:51:35 PM6/5/15
to std-dis...@isocpp.org
On Thursday 04 June 2015 17:01:28 Richard Smith wrote:
> There was also a suggestion of allowing unary - on a pointer-to-member, to
> transform 'T U::*' into 'U T::*' (so you can get back to the surrounding
> object from a pointer to a subobject), and of allowing .* to combine 'T
> U::*' and 'U V::*' into 'T V::*'.

And the addition/subtraction of other PTMs to make more composition.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Thiago Macieira

unread,
Jun 5, 2015, 6:57:37 PM6/5/15
to std-dis...@isocpp.org
On Thursday 04 June 2015 22:41:42 Richard Smith wrote:
> > The T U::* becoming U T::* design is a bit problematic from an ABI
> > perspective due to most implementations' representation of null data
> > member
> > pointers as -1.
>
> True. Note that such an approach is already broken:
>
> #include <cassert>
> struct a {};
> struct b : a {};
> struct c : a, b { int n; };
> int b::*p = (int b::*)&c::n;

Is this cast even allowed? And if it is, what does the standard say about the
validity of reinterpret_casting from one PTM type to another?

struct b has no member n, so I don't see why you should be allowed to access
that member from a b pointer.

> int main() { assert(p != nullptr); }
>
> That assert fires in implementations following the Itanium C++ ABI, even
> though the C++ standard requires it not to. Both issues could be fixed by
> subtracting 1 from negative pointer to data members (or by changing the
> representation of a null pointer to member, but that's a much more
> significant ABI break).

Richard Smith

unread,
Jun 5, 2015, 7:04:19 PM6/5/15
to std-dis...@isocpp.org
On Fri, Jun 5, 2015 at 3:57 PM, Thiago Macieira <thi...@macieira.org> wrote:
On Thursday 04 June 2015 22:41:42 Richard Smith wrote:
> > The T U::* becoming U T::* design is a bit problematic from an ABI
> > perspective due to most implementations' representation of null data
> > member
> > pointers as -1.
>
> True. Note that such an approach is already broken:
>
> #include <cassert>
> struct a {};
> struct b : a {};
> struct c : a, b { int n; };
> int b::*p = (int b::*)&c::n;

Is this cast even allowed?

Yes; see 5.2.9/12.
 
And if it is, what does the standard say about the
validity of reinterpret_casting from one PTM type to another?

This is a static_cast, not a reinterpret_cast, so that's not relevant to this case, but the rules for that are in 5.2.10/10. For a non-null pointer to member, you have to cast it back to the original type before you can use it again.

struct b has no member n, so I don't see why you should be allowed to access
that member from a b pointer.

This allows you to use 'p' with 'b' objects that you know are the base class of a 'c' object. (For instance, suppose 'b' is a 'serializable' base class, and its derived classes give it a list of pointers to members to serialize.) But whether or not we think this should work, it's been allowed since C++98.

> int main() { assert(p != nullptr); }
>
> That assert fires in implementations following the Itanium C++ ABI, even
> though the C++ standard requires it not to. Both issues could be fixed by
> subtracting 1 from negative pointer to data members (or by changing the
> representation of a null pointer to member, but that's a much more
> significant ABI break).

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
   Software Architect - Intel Open Source Technology Center
      PGP/GPG: 0x6EF45358; fingerprint:
      E067 918B B660 DBD1 105C  966C 33F5 F005 6EF4 5358

Myriachan

unread,
Jun 10, 2015, 2:26:23 PM6/10/15
to std-dis...@isocpp.org
On Friday, June 5, 2015 at 3:51:35 PM UTC-7, Thiago Macieira wrote:
On Thursday 04 June 2015 17:01:28 Richard Smith wrote:
> There was also a suggestion of allowing unary - on a pointer-to-member, to
> transform 'T U::*' into 'U T::*' (so you can get back to the surrounding
> object from a pointer to a subobject), and of allowing .* to combine 'T
> U::*' and 'U V::*' into 'T V::*'.

And the addition/subtraction of other PTMs to make more composition.


Did you ever formally propose your member pointer extensions?

Melissa

Thiago Macieira

unread,
Jun 10, 2015, 3:29:32 PM6/10/15
to std-dis...@isocpp.org
No, I haven't yet. Most likely, I will not have time to write a paper, at
least until the end of the year.

Myriachan

unread,
Jun 10, 2015, 7:52:11 PM6/10/15
to std-dis...@isocpp.org, je...@caffeinated.me.uk
On Thursday, June 4, 2015 at 10:41:44 PM UTC-7, Richard Smith wrote:
On Thu, Jun 4, 2015 at 7:00 PM, Myriachan <myri...@gmail.com> wrote:
The T U::* becoming U T::* design is a bit problematic from an ABI perspective due to most implementations' representation of null data member pointers as -1.

True. Note that such an approach is already broken:

#include <cassert>
struct a {};
struct b : a {};
struct c : a, b { int n; };
int b::*p = (int b::*)&c::n;
int main() { assert(p != nullptr); }

That assert fires in implementations following the Itanium C++ ABI, even though the C++ standard requires it not to. Both issues could be fixed by subtracting 1 from negative pointer to data members (or by changing the representation of a null pointer to member, but that's a much more significant ABI break).
 

 
Is this also a valid case of brokenness?  This message fires on both Itanium ABI (GCC/Clang Linux) and Visual C++:


#include <cstdio>
#include <cwchar>

struct Base1 { char member1; };
struct Base2 { char member2; };
struct Derived : Base1, Base2 { char member3; };

int main()
{
   
char Derived::*derivedMember = &Derived::member1;
   
char Base2::*base2Member = static_cast<char Base2::*>(derivedMember);
   
if (base2Member == nullptr)
   
{
        std
::wprintf(L"Error: base2Member is null!\n");
   
}
   
return 0;
}

Whether this is a valid case as well seems to depend on whether 5.2.9/12's definition of "class containing the original member" includes derived classes.  In other words, does Derived "contain" member1 for the purpose of making that static_cast well-defined?

If this is well-defined, things get worse than just whether this compares equal to nullptr, because the compiler uses the null check for the idempotency of null member pointers through casting, a lot like null pointers through multiple-inheritance casts.  You can get incorrect behavior when casting a pointer as a result.

Melissa

Thiago Macieira

unread,
Jun 10, 2015, 8:00:19 PM6/10/15
to std-dis...@isocpp.org
On Thursday 04 June 2015 22:41:42 Richard Smith wrote:
> #include <cassert>
> struct a {};
> struct b : a {};
> struct c : a, b { int n; };
> int b::*p = (int b::*)&c::n;
> int main() { assert(p != nullptr); }
>
> That assert fires in implementations following the Itanium C++ ABI, even
> though the C++ standard requires it not to. Both issues could be fixed by
> subtracting 1 from negative pointer to data members (or by changing the
> representation of a null pointer to member, but that's a much more
> significant ABI break).

Ok, so subtracting one from negative offsets seems like the best solution
without breaking the ABI.

I couldn't conceive of any negative PMF offset until the example above. In
turn, that means a pointer-to-container
b int::*x = -p;

Would be positive.

David Krauss

unread,
Jun 10, 2015, 8:14:43 PM6/10/15
to std-dis...@isocpp.org

On 2015–06–05, at 1:41 PM, Richard Smith <ric...@metafoo.co.uk> wrote:

That assert fires in implementations following the Itanium C++ ABI, even though the C++ standard requires it not to. Both issues could be fixed by subtracting 1 from negative pointer to data members (or by changing the representation of a null pointer to member, but that's a much more significant ABI break).

There should be easier fixes. It seems to require an explicit derived-to-base conversion of pointer-to-char type and the last byte of the preceding base in layout order contains a char. (This implies multiple inheritance at that specific point in the hierarchy.)

1. Such a condition could be diagnosed, no?
2. Alternative: multiply all pointer-to-char-member values by 2. Or apply the subtract-one fix only to pointer-to-char-members.
3. Alternative: add padding between two such bases.

If I were designing an ABI, I’d probably represent PTM nullptr as the most negative value…

David Krauss

unread,
Jun 10, 2015, 8:31:17 PM6/10/15
to std-dis...@isocpp.org

On 2015–06–11, at 8:14 AM, David Krauss <pot...@gmail.com> wrote:

It seems to require an explicit derived-to-base conversion of pointer-to-char type

Oh, to be pedantic, empty classes behave the same as chars. You’d need to be insane to create this situation with one, but might as well fix the bug completely :) .

Richard Smith

unread,
Jun 10, 2015, 9:05:09 PM6/10/15
to std-dis...@isocpp.org, Jeff Snyder
The intent (as I understand it) is that that case is not valid. If it were, 5.2.9/12's "or is a [...] derived class of the class containing the original member" would be redundant. (The rule for the base-to-derived implicit conversions for pointers-to-members is even more broken, because it misses this clause entirely, but the rule is supposed to apply there too.)

Essentially, the intended model (as I understand it) is: for a pointer to member for class X, we imagine a hypothetical object A of class X. The pointer to member refers to a direct member of an object B, where either A is B, or A is a base class subobject of B, or B is a base class subobject of A.
 
If this is well-defined, things get worse than just whether this compares equal to nullptr, because the compiler uses the null check for the idempotency of null member pointers through casting, a lot like null pointers through multiple-inheritance casts.  You can get incorrect behavior when casting a pointer as a result.

Melissa

--

David Krauss

unread,
Jun 10, 2015, 9:30:31 PM6/10/15
to std-dis...@isocpp.org
On 2015–06–11, at 9:05 AM, Richard Smith <ric...@metafoo.co.uk> wrote:

The intent (as I understand it) is that that case is not valid. If it were, 5.2.9/12's "or is a [...] derived class of the class containing the original member" would be redundant. (The rule for the base-to-derived implicit conversions for pointers-to-members is even more broken, because it misses this clause entirely, but the rule is supposed to apply there too.)

Essentially, the intended model (as I understand it) is: for a pointer to member for class X, we imagine a hypothetical object A of class X. The pointer to member refers to a direct member of an object B, where either A is B, or A is a base class subobject of B, or B is a base class subobject of A.

The definition of the pointer-to-member operators [expr.mptr.oper] §5.5/4 refers to the dynamic type of the LHS, not class X.

If there’s not a DR on this already, there certainly should be.

Also, on the topic of diagnosis and fixing, along with the char T::* case is the PTMF case fn T::* , where fn is a function type matching some nonstatic member function of a non-initial base class whose preceding byte in layout order is occupied by a base of size 1.

David Krauss

unread,
Jun 10, 2015, 10:25:18 PM6/10/15
to std-dis...@isocpp.org

On 2015–06–11, at 9:30 AM, David Krauss <pot...@gmail.com> wrote:

Also, on the topic of diagnosis and fixing, along with the char T::* case is the PTMF case fn T::* , where fn is a function type matching some nonstatic member function of a non-initial base class whose preceding byte in layout order is occupied by a base of size 1.

(… er no, PTMF nullptr has no problem in the common ABI. It ignores the base adjustment and puts zero in the rest.)

Myriachan

unread,
Jun 11, 2015, 2:58:37 PM6/11/15
to std-dis...@isocpp.org
On Wednesday, June 10, 2015 at 6:30:31 PM UTC-7, David Krauss wrote:
On 2015–06–11, at 9:05 AM, Richard Smith <ric...@metafoo.co.uk> wrote:

The intent (as I understand it) is that that case is not valid. If it were, 5.2.9/12's "or is a [...] derived class of the class containing the original member" would be redundant. (The rule for the base-to-derived implicit conversions for pointers-to-members is even more broken, because it misses this clause entirely, but the rule is supposed to apply there too.)

Essentially, the intended model (as I understand it) is: for a pointer to member for class X, we imagine a hypothetical object A of class X. The pointer to member refers to a direct member of an object B, where either A is B, or A is a base class subobject of B, or B is a base class subobject of A.

The definition of the pointer-to-member operators [expr.mptr.oper] §5.5/4 refers to the dynamic type of the LHS, not class X.

If there’s not a DR on this already, there certainly should be.



Do you mean that you think that my case is valid, or that my case is invalid, but for a different reason than what Richard said?

Even though my code may actually be invoking undefined behavior, I filed a Visual C++ bug here:

https://connect.microsoft.com/VisualStudio/feedback/details/1420642

Note that Visual C++ does not exhibit the problem in the empty base class case; this is because it doesn't overlap the two empty base classes like the Itanium UNIX ABI does.


If I were designing an ABI, I’d probably represent PTM nullptr as the most negative value…


Pretty much the same thing I thought immediately.

dec eax
jo
short member_pointer_was_null

Melissa

David Krauss

unread,
Jun 11, 2015, 5:48:14 PM6/11/15
to std-dis...@isocpp.org
On 2015–06–12, at 2:58 AM, Myriachan <myri...@gmail.com> wrote:

Do you mean that you think that my case is valid, or that my case is invalid, but for a different reason than what Richard said?

I mean that the intent of §5.5/4 seems to differ from §5.2.9/12. I haven’t done deeper research, by the way.

dec eax
jo short member_pointer_was_null


Well, that doesn’t consider base pointer adjustment by casts. It would be better to consider as nullptr all values between INT_MAX-INT_MAX/2 and INT_MAX/2.

(Fortunately, using a null PTM is UB so the check doesn’t need to happen unless the user asks for it.)
Reply all
Reply to author
Forward
0 new messages