c++c++20member-pointers

Dynamically allocated member pointer is not zero initialized?


Consider the following code, where T is a member pointer:

#include <cassert>

struct Foo { double v = 0; };

int main()
{
  using T = double Foo::*;

  T p1 = nullptr;
  T p2 = T();
  T p3{};
  T * pp4 = new T();
  T * pp5 = new T{};

  assert(p1 == p2);
  assert(p1 == p3);
  assert(p1 == *pp4); // Fails on MSVC
  assert(p1 == *pp5); // Fails on MSVC

  delete pp4;
  delete pp5;
}

clang and gcc compile and run this without problems (live on godbolt). However, MSVC (e.g. in version 19.35, but also others) does not: It compiles, but the asserts for pp4 and pp5 fail. If I understand the cppreference for value initialization correctly, pp4 and pp5 should be zero initialized, where in turn I would expect the resulting value to compare equal to nullptr. Printing the values to cout shows that all values are 0, except for pp4 and pp5 which are 1. Is this a MSVC bug, or is my understanding of member pointers and reading the cppreference wrong?

Background: Of course, having dynamically allocated member pointers is rather uncommon. I stumbled upon this while testing whether my tiny::optional can cope with member pointers (optional<double Foo::*>). Internally, the library uses placement new to construct the value, which shows the same issue.


Solution

  • MSVC is not standard conformant here because T = double Foo::* is a scalar type and new T() is value initialization which will result in zero initialization in this case.

    This can be seen from value initialization:

    To value-initialize an object of type T means:

    • (8.1)
    • (8.2)
    • (8.3) otherwise, the object is zero-initialized.

    This means that zero initialization will be performed here.


    Next, from zero initialization:

    To zero-initialize an object or reference of type T means:

    • if T is a scalar type (6.8.1), the object is initialized to the value obtained by converting the integer literal 0 (zero) to T;83

    This means that the effect is the same as if we were to write T * pp4 = 0; and T * pp5 = 0. Thus, pp4 and pp5 both should compare equal to nullptr.