Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

new of incomplete type compiles when wrapped in template

Consider this code, with an obvious compile error: (1)

struct A;
struct B {
  B() { new A(); } // error: allocation of incomplete type 'A'
};

Using a unique_ptr will not help either: (2)

struct A;
struct B {
  B() { std::make_unique<A>(); } // error: due to ~unique_ptr()
};

Then (to my great surprise) I found out, that this will compile: (3)

struct A;
struct B {
  B() { std::make_unique<A>(); }
};
struct A {}; // OK, when a definition is added **below**

Then I checked, whether this helps with new as well - nope: (4)

struct A;
struct B {
  B() { new A(); } // error: allocation of incomplete type 'A'
};
struct A {};

I figured it has something to do with templates and in fact: wrapping new inside a template does compile: (5)

template <typename T> 
T* my_new() { return new T(); } // OK, when wrapped in template
struct A;
struct B {
  B() { my_new<A>(); }
};
struct A {};

And just for the sake of completeness, removing the definition of A raises an error again: (6)

template <typename T> 
T* my_new() { return new T(); } // error: allocation of incomplete type 'A'
struct A;
struct B {
  B() { my_new<A>(); }
}; 
// do note: definition of A removed

What's going on here? As far as I understood, the compiler must know the size/definition of A to allocate it, thus merely declaring it, is not sufficient. In addition I believed, that the definition must precede the allocation.

This seems to be correct, when using new directly (1,4). But when new is wrapped, it is apparent that I am mistaken (2,3,5,6).

Possible explanations I found so far are:

  • The check for completed types is delayed until the template instantiation occurs. I think this is correct, but in my case the direct use of new A() and the call to my_new<A>() occur virtually on the same position. So this cannot be the reason. Right?
  • Using incomplete types as template Arguments might be undefined behavior. Is this really true? Even when enabling all warnings, the compiler will not complain. Also comparing 5 and 6 seems to suggest, that the compiler is smart enough to figure out, that the definition follows below (thus virtually making the type complete).

Why 4 is considered to be incorrect, whilst 5 compiles (or is 5 just spuriously compiling undefined behavior [but then 3 must be flawed as well, right?])?

btw: tested with clang++-3.5.0 and g++-4.9.2

like image 285
h4ssi Avatar asked Jan 24 '15 15:01

h4ssi


1 Answers

§14.6.4.1 [temp.point]/p1,8, emphasis mine:

1 For a function template specialization, a member function template specialization, or a specialization for a member function or static data member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization and the context from which it is referenced depends on a template parameter, the point of instantiation of the specialization is the point of instantiation of the enclosing specialization. Otherwise, the point of instantiation for such a specialization immediately follows the namespace scope declaration or definition that refers to the specialization.

8 A specialization for a function template, a member function template, or of a member function or static data member of a class template may have multiple points of instantiations within a translation unit, and in addition to the points of instantiation described above, for any such specialization that has a point of instantiation within the translation unit, the end of the translation unit is also considered a point of instantiation. A specialization for a class template has at most one point of instantiation within a translation unit. A specialization for any template may have points of instantiation in multiple translation units. If two different points of instantiation give a template specialization different meanings according to the one definition rule (3.2), the program is ill-formed, no diagnostic required.

There are two points of instantiation of my_new<A>, one at the end of the definition of B, and one at the end of the translation unit. Since those two points will result in different meanings (for snippets 3 and 5), the program is ill-formed NDR (i.e., it has undefined behavior).

like image 151
T.C. Avatar answered Sep 27 '22 23:09

T.C.