On Thu, Jan 16, 2014 at 11:38 AM, Herb Sutter <hsutter@microsoft.com> wrote:

Richard, it cannot mean that (or if it does, IMO we have an obvious bug) for at least two specific reasons I can think of (below), besides the general reasons that it would not be sensical and would violate type safety.

 

First, objects must have unique addresses. Consider, still assuming B is trivially constructible:

  void *p = malloc(sizeof(B));

  B* pb = (B*)p;
  pb->i = 0;

  short* ps = (short*)p;
  *ps = 0;

This cannot possibly be construed as starting the lifetime of a B object and a short object, else they would have the same address, which is illegal. Am I missing something?

 

Second, this function:

 

    template<typename T> T* test() {

        auto p = (T*)malloc(sizeof(T));

        new (p) T{};

        return p;

    }

 

clearly returns a pointer to a T object for any default-constructible type T – and that has to be true whether or not T is trivially constructible.

 

If the current rules are as Richard says, then it would true that test() returns a pointer to a T, unless T is trivially constructible in which case it returns a pointer to anything of size T. That doesn’t make any sense, and if the rules say that now I’d say we have a defect.

 

 

The more I see about this, the more I think the lifetime wording (a) is correct for non-trivial-ctor cases and (b) is incomplete or incorrect for trivial-ctor cases.

 

In particular, untyped storage (such as returned from malloc) should never be considered of any type T until after the possibly-trivial T::T has run, such as via placement new. Note I include “possibly-trivial” there on purpose. If you don’t call a T ctor, you don’t have a T object – that has to be true. Otherwise don’t we have multiple contradictions in the standard?

 

“It’s not a T until the T ctor runs” has to be part of the rules somewhere. It is there for non-trivially-constructible types.

 

Do we have a defect here that we’re missing that rule for trivially-constructible types?


I agree that something is missing from the rules. I'm not convinced that "it's not a T until the T ctor runs" is the right fix. Mostly it's just that I don't understand what the consequences of that rule would be, and the standard would have to say something about those consequences. So consider the following snippet:
  struct T { int x; };
  T x{17}; // 1
  void* pv = malloc(sizeof(T)); // 2
  T* p = static_cast<T*>(pv); // 3
  *p = x;  // 4

I'm trying to understand what your rule would imply for that snippet. Does your rule imply that line 4 is undefined behavior (since, according to your proposed rule, *p isn't an object of type T)? Or does your rule imply that *p is an object of type T after line 4 (maybe an assignment might count as the moral equivalent of a constructor)? Or does your rule imply that this code snippet is well defined, and that *p may be used to do (some of) the things you can do with an object of type T but that it is not in fact an object of type T?

I'm afraid I'm just not sure this is a fruitful line of reasoning. All three of those possible followups to your rule have odd consequences. Maybe there's a fourth option I'm not seeing, or maybe it means we need a different fix.

                         --Matt