7

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".

Community
  • 1
  • 1
user786653
  • 29,780
  • 4
  • 43
  • 53
  • 1
    What exactly are you expecting the code to do? Is `T` supposed to contain a pointer to `S`, and if so, why isn't it just written `S * a = nullptr;`? – Xirema Jun 23 '16 at 19:04
  • Compiles [here](http://rextester.com/NFYP31324) and on my own MSVS 2015. I have version 14.0.23107.0 D14REL – NathanOliver Jun 23 '16 at 19:04
  • 1
    @Xirema: `T::a` is a [pointer to a (bool) data member](http://en.cppreference.com/w/cpp/language/pointer#Pointers_to_data_members) in the incomplete type `S`. @NathanOliver: It compiles for me as well, but it `abort`s when executed. – user786653 Jun 23 '16 at 19:07
  • @user786653 Can you use a template for `T` ? If yes, you can probably pass the data member pointer as the template parameter. There would be no ambiguity regarding platform conformity. – Arunmu Jun 23 '16 at 19:24
  • @Arunmu: Where the code is actually used it is possible to supply the complete class definition (thus avoiding the issue all together), but I'd like to avoid it (it's complicated, but basically to speed up compilation). – user786653 Jun 23 '16 at 19:28

1 Answers1

5

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.

PcAF
  • 1,975
  • 12
  • 20
  • Thanks! I'll accept this answer in a day or so unless somebody can find opposing statements elsewhere in the standard. – user786653 Jun 23 '16 at 19:39