Is the following code well-defined in C++? (*)
I'm having a hard time figuring out where to even look in the standard, and searching the web hasn't turned up anything concrete.
struct S;
struct T {
constexpr T() = default;
bool S::* a = nullptr;
int b = 42;
};
const T t{};
// Test. Compiled using: cl /W4 /WX /FAs filename.cpp
#include <stdlib.h>
int main() {
if (t.b != 42) abort();
}
The reason I'm asking is because it works (or seems to) with newer versions of GCC and Clang (x86/x86_64), but fails(**) with Visual Studio 2015 Update 2 and Update 3 RC.
Before reporting a bug I'd like to be sure I'm not relying on undefined behavior or just not searching for the right terms.
I've also tried using /vmg
and /vmb
as mentioned in this post.
(*): I mostly care about C++14 and later, but I don't see any reason the answer shouldn't apply to C++11.
(**): If the code is well-defined it looks like a codegen bug where it isn't allocation room for the pointer. Changing struct S
to struct S{}
seems to make the code "work".
This error usually means that you are trying to follow a pointer to a class, but the compiler did not find the definition of that class (it found a declaration for the class, so it is not considered an unknown symbol, but it did not find a definition, so it is considered an incomplete class).
The “dereferencing pointer to incomplete type” error commonly occurs in C when one tries to dereference a type (usually a struct) that is: not declared at all. declared, but not defined.
The void type is an incomplete type that cannot be completed. To complete an incomplete type, specify the missing information. The following examples show how to create and complete the incomplete types. To create an incomplete structure type, declare a structure type without specifying its members.
Your code is well-defined:
N4594 3.2/5
[...]A class type T must be complete if:
- (5.1) an object of type T is defined (3.1), or
- (5.2) a non-static class data member of type T is declared (9.2), or
- (5.3) T is used as the object type or array element type in a new-expression (5.3.4), or
- (5.4) an lvalue-to-rvalue conversion is applied to a glvalue referring to an object of type T (4.1), or
- (5.5) an expression is converted (either implicitly or explicitly) to type T (Clause 4, 5.2.3, 5.2.7, 5.2.9, 5.4), or
- (5.6) an expression that is not a null pointer constant, and has type other than cv void*, is converted to the type pointer to T or reference to T using a standard conversion (Clause 4), a dynamic_cast (5.2.7) or a static_cast (5.2.9), or
- (5.7) a class member access operator is applied to an expression of type T (5.2.5), or
- (5.8) the typeid operator (5.2.8) or the sizeof operator (5.3.3) is applied to an operand of type T, or
- (5.9) a function with a return type or argument type of type T is defined (3.1) or called (5.2.2), or
- (5.10) a class with a base class of type T is defined (Clause 10), or
- (5.11) an lvalue of type T is assigned to (5.18), or
- (5.12) the type T is the subject of an alignof expression (5.3.6), or
- (5.13) an exception-declaration has type T, reference to T, or pointer to T (15.3).
None of them says that T
needs to be complete in order to declare pointer-to-member of T
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With