I have a class containing different members (custom run time constructed structs). And I have a compile time tuple containing pairs of pointer-to-member elements and strings. Compile time I need to check if every pointer-to-member and name is used only once in the list, and the custom structs check if they have an entry in the tuple (they know their own pointer-to-member). Having a tuple for this purpose increases the compile time dramatically, it would be great to identify the members in compile time with a void* array and not with a heterogeneous data struct.
As I read in this thread, dereferencing a nullptr is not always undefined behavior.
I read CWG-issue #315 also, that states:
We agreed the example should be allowed. p->f() is rewritten as (*p).f() according to 5.2.5 [expr.ref]. *p is not an error when p is null unless the lvalue is converted to an rvalue (4.1 [conv.lval]), which it isn't here.
I wanted to leverage this to get a normal pointer from a pointer-to-member (I don't want to dereference them, I just want to compare pointers-to-members from the same class but with different types).
So I created the following code:
#include <iostream>
class Test
{
int a;
public:
static constexpr inline int Test::*memPtr = &Test::a;
static constexpr inline int* intPtr = &(static_cast<Test*>(nullptr)->*Test::memPtr);
};
int main () {
std::cout << Test::intPtr << std::endl;
}
In my opinion the &(static_cast<Test*>(nullptr)->*Test::memPtr);
expression uses the same approach as the code that was discussed in CWG-issue #315.
The code above compiles with MSVC but not with clang or gcc.
I checked if similar code that was mentioned in #315 compiles or not:
struct Test {
static constexpr int testFun () { return 10; }
};
int main ()
{
static constexpr int res{static_cast<Test*>(nullptr)->testFun()};
static_assert(res == 10, "error");
}
And yes, it does. test code
Should the construct I used in the first example be available in constexpr
expressions (as undefined behavior is not allowed there)?
Fun fact: If I modify my original code and add a virtual destructor to the class then both MSVC and clang are happy with it, and gcc crashes. I mean literally, it segfaults.
Fun fact 2: If I remove the virtual destructor and make the class templated gcc and MSVC compiles it, but now clang complains.
From the standard on ->*
's behavior:
The expression
E1->*E2
is converted into the equivalent form(*(E1)).*E2
.
And for .*
:
Abbreviating pm-expression.*cast-expression as
E1.*E2
,E1
is called the object expression. If the dynamic type ofE1
does not contain the member to whichE2
refers, the behavior is undefined.
The dynamic type of E1
(which dereferences a nullptr
) does not exist, because it's a reference to no object. Therefore, the behavior of this expression is undefined.