Constexpr in VS2015 Update 1

December 2nd, 2015

Visual Studio 2015 RTM shipped with support for constant expressions as specified in the C++11 language standard. The release received lots of excellent feedback from our users and the C++ community. Using that feedback, we’ve been working on refining our implementation for VS 2015 Update 1. Our goal with VS 2015 Update 1 was to finish up the last significant feature work for C++11 constexpr and improve our implementation’s robustness. This blog post is going to provide some notes to explain where VS 2015 Update 1 puts us and where we’re going with constexpr support.

Static Initializers

VS 2015 shipped with a warning that indicates that the compiler can detect and use initializers for constexpr evaluation but will not statically emit these initializers. That is, although the compiler had enough information to emit fully instantiated types that could be loaded from the compiled executable, it didn’t emit the fully instantiated types. These types were instantiated and constructed at runtime, as most C++ objects traditionally have been.

The great news is that VS 2015 Update 1 now supports emitting static initializers! These types are fully instantiated when they’re loaded into memory, rather than running the code at runtime to initialize them. This was the last feature that we needed to implement for C++11 constexpr support and we’re excited to ship it with Update 1.

We should extend kudos to Tanveer Gani for the herculean work he’s done to make this feature ship with Update 1. Because of his work, Update 1 will be shipping with complete support for emitting static initializers for constexpr objects. It will also ship with partial support for constant initialization of objects of non-literal types that have constexpr constructors (as specified in section 3.6.2 of the C++ language standard). Specifically, types with virtual functions aren’t implemented yet.

Static initializers are an important part of implementing std::once_flag, which is used for std::call_once. Stephan calls this out in his blog post about improvements to the STL in VS 2015 RTM.

The reduction in code generated by VS 2015 Update 1 for runtime execution can be quite dramatic. I’d like to take some time to explore the behavior with some examples. The C++ source is shown first, followed by assembly code illustrating static initialization. The assembly for these code snippets was generated by invoking the C++ compiler with the /FAsc flag.

Example 1: Initialization of a Constexpr Object

We’ll start with a simple example – constructing a simple instance of a type with a constexpr constructor.

struct Point {

constexpr Point(int x1, int y1)

: x(x1), y(y1)

{}

int x;

int y;

};

constexpr Point p1(10, 11);

First, the assembly generated by VS 2015 RTM for this snippet (for comparison):

; VS 2015 RTM asm

PUBLIC ??0Point@@QEAA@HH@Z ; Point::Point

_BSS SEGMENT

?p1@@3UPoint@@B DQ 01H DUP (?) ; p1

_BSS ENDS

text$di SEGMENT

??__Ep1@@YAXXZ PROC ; `dynamic initializer for ‘p1”, COMDAT

; 8 : constexpr Point p1(10, 11);

sub rsp, 40 ; 00000028H

mov r8d, 11

mov edx, 10

lea rcx, OFFSET FLAT:?p1@@3UPoint@@B

call ??0Point@@QEAA@HH@Z ; Point::Point

add rsp, 40 ; 00000028H

ret 0

??__Ep1@@YAXXZ ENDP ; `dynamic initializer for ‘p1”

text$di ENDS

And now the assembly generated by VS 2015 Update 1:

; VS 2015 Update 1 asm

CONST SEGMENT

?p1@@3UPoint@@B

DD 0aH ; p1

DD 0bH

CONST ENDS

Notice that there is no initialization code in the assembly generated by VS 2015 Update 1. Running the C++ code under the Visual Studio debugger in VS 2015 Update 1 will expectedly not execute the constructor for Point.

Example 2: Initialization of Array of Constexpr Objects

Continuing with the definition of Point above, we’ll create an array of Points:

constexpr Point arr[] = { Point(2, 3), Point(5, 7), Point(11, 13) };

The generated assembly from VS 2015 Update 1 is excellent:

; VS 2015 Update 1 asm

CONST SEGMENT

?arr@@3QBUPoint@@B

DD 02H ; arr

DD 03H

DD 05H

DD 07H

DD 0bH

DD 0dH

CONST ENDS

Example 3: Initializing pointer and reference members of a constexpr object

This code snippet initializes a constexpr object with pointers and references to a global constexpr variable.

constexpr int I = 42;

struct A {

const int& ref;

const char *ptr;

const char *&ref2;

constexpr A( const char *p, const int& r)

: ref(r), ptr(p), ref2{ptr}

{}

};

constexpr A a{ "qwerty", I };

This sample actually causes an ICE in VS 2015 RTM, but generates delightfully terse assembly code in VS 2015 Update 1.

; VS 2015 Update 1 asm

CONST SEGMENT

?I@@3HB DD 02aH

?a@@3UA@@B

DD FLAT:?I@@3HB ; a

DD FLAT:$SG2668

DD FLAT:?a@@3UA@@B+4

$SG2668

DB ‘qwerty’, 00H

CONST ENDS

Example 4: Initializing constexpr classes with base constructors

Even classes with complicated (non-virtual) inheritance can be initialized statically. I’m not going to list the VS 2015 RTM as it’s prohibitively long, but you can view the COD file yourself by compiling the snippet below with the /FAsc flag.

struct Empty {};

struct A {

short i;

constexpr A(int ii)

: i(ii)

{}

};

struct B {

double d;

constexpr B(double di)

: d(di)

{}

};

struct C : Empty, A, B {

double x;

constexpr C()

: x(1.0), A(42), B(-1.0)

{}

};

constexpr C c;



And the assembly generated by VS 2015 Update 1:

; VS 2015 Update 1 asm

CONST SEGMENT

?c@@3UC@@B DW 02aH ; c

ORG $+6

DQ 0bff0000000000000r ; -1

DQ 03ff0000000000000r ; 1

CONST ENDS

Example 5: Initializing a non-literal type

As mentioned above, some non-literal types that are initialized with constants can be statically initialized. In the sample below, the initialized supplied to the constexpr constructor is a constant, so Update 1 can statically initialize it. Note that the type has a destructor, which makes the type a non-literal type.

extern "C" int puts( const char *);

struct NonLiteralType {

const char *p;

constexpr NonLiteralType( const char *pp)

: p(pp)

{}

~NonLiteralType() {

puts( "~NonLiteralType()" );

}

};

NonLiteralType nlt( "qwerty" );

int main(){}

The assembly generated in Update 1 does not place the object in the CONST segment, because it was not declared constexpr:

; VS 2015 Update 1 asm

CONST SEGMENT

$SG2669 DB ‘qwerty’, 00H

CONST ENDS

_DATA SEGMENT

?nlt@@3UNonLiteralType@@A DD FLAT:$SG2669 ; nlt

_DATA ENDS

Destruction of the non-literal type object is done with a registered “atexit” function:

; VS 2015 Update 1 asm

CRT$XCU SEGMENT

?nlt$initializer$@@3P6AXXZA DD FLAT:??__Fnlt@@YAXXZ ; nlt$initializer$

CRT$XCU ENDS

CONST SEGMENT

text$yd SEGMENT

??__Fnlt@@YAXXZ

PROC ; `dynamic atexit destructor for ‘nlt”, COMDAT

push ebp

mov ebp, esp

mov ecx, OFFSET ?nlt@@3UNonLiteralType@@A ; nlt

call ??1NonLiteralType@@QAE@XZ ; NonLiteralType::~NonLiteralType

pop ebp

ret 0

??__Fnlt@@YAXXZ ENDP ; `dynamic atexit destructor for ‘nlt”

text$yd ENDS

Quality Improvements

Alongside the static initializer work, we’ve fixed ~45 bugs related to constexpr usage. The majority of these bugs were reported to us by customers. Because we tried to prioritize customer issues, you should see improvements across the board when writing constexpr code rather than in any particular areas. The table below shows the bugs that we fixed. Thanks to everybody who filed bugs!

Title Connect Customer ConnectID [constexpr] Using final on the member variable’s class breaks constexpr Aepaerae 1135313 Error C2131 when creating constexpr std::array Andrey Ashikhmin 1574634 constexpr void pointer variables not treated as constants anthonyw1 1609590 constexpr failure with std::array Brandon Kentel 1604956 Constexpr causes Internal Compiler Error camhusmj38 1573435 Constexpr causes Internal Compiler Error camhusmj38 1570534 Constexpr produces wrong results [compared to LLVM] camhusmj38 1300591 Erroneous error C2131: expression did not evaluate to a constant camhusmj38 1596224 MSVC 2015 believes constexpr member pointer is not constant David Majnemer 1327934 MSVC 2015 crashes on pointer arithmetic in constexpr context David Majnemer 1420558 MSVC 2015 crashes trying to evaluate constexpr constructor

which initializes a reference David Majnemer 1404631 MSVC 2015 crashes trying to evaluate constexpr containing pointer to member function David Majnemer 1327996 MSVC 2015 incorrectly rejects constexpr array of unions access David Majnemer 1323869 MSVC 2015 incorrectly rejects pointer equality in constexpr context David Majnemer 1404624 MSVC 2015 materializes one constant instead of two in constexpr context David Majnemer 1404688 MSVC 2015 rejects initializing constexpr reference to temporary object David Majnemer 1404715 MSVC 2015 rejects lvalue conditional operator of type const int in constexpr context David Majnemer 1404674 MSVC 2015 rejects member pointer comparison in constexpr context David Majnemer 1401241 MSVC2015 rejects valid and accepts invalid constexpr static_cast David Majnemer 1330530 MSVC 2015 will not evaluate function-local static constexpr reference variable to

temporary David Majnemer 1404755 Failure to compile with valid use of ‘constexpr’ dn357 1311469 Compiller Failiure in constexpr statement on std::make_array proposal implementation Felix Petriconi 1494444 `std::integral_constant<>` implicitly-defined default constructor and/or `operator value_type` not

constexpr ildjarn 1497236 Bogus error regarding returning the address of or a reference to a temporary when attempting aggregate initialization inside of a constexpr function ildjarn 1498733 C++ – constexpr does not work with aggregate initialization ildjarn 1572056 C++ – constexpr does not work with delegating constructors ildjarn 1579279 C++ – constexpr static member functions must be fully qualified when called during type definition ildjarn 1579334 C++ – Internal compiler error with constexpr constructor ildjarn 1571950 [constexpr] bug in deducing constexpr of function pointer koosw 1378031 Failed in constexpr lambda workaround mzer0 1673865 VC++2015 RTM – constexpr constructor errors with union members with bitfields Orvid King 1571281 constexpr and recurring template cause fatal error C1001 Pendenaor 1711144 class static constexpr value is 0 pmingkr 1384724 constexpr delegating constructor doesn’t compile Quixotic Labs 1229998 constexpr bug related to “char const*const” parameters Rui Figueira (Cloudgine) 1272743 [constexpr][regression][boost] VC++ internal compiler error for a non-type template instantiation Sasha Sitnikov 1577162 delegating constructor in constexpr ctor won’t compile submitting_bug_reports_is_too_damn_hard 1463556 [Feedback] ICE when compiling this C/C++ code Bogus error C2131 “expression did not evaluate to a constant” triggered by variadic-recursive constexpr constexpr delegating constructors constexpr template function causes compilation failure with erroneous message when called from within struct template constexpr 4607 ICE triggered by “ptr ? 3 : 4” in a constexpr function

Looking Forward

Even with the improvements to C++11 constexpr that are shipping with update 1, we still have some refinement to do on our implementation. There are ~30 bugs remaining on our backlog in this area, many related to pointers-to-members in constant expressions. There’s some quality work to do around array and string aliasing, and although Tanveer’s done a solid job of readying static initializers, we’re planning for some amount of incoming bug reports related to the change.

Essentially, all of this means that we’ll still be working on C++11 constexpr for a while longer, but the outstanding work is manageable. Our goal is to wrap this work up in time for the next Visual Studio update. The plan after that is to immediately dive into C++14 constexpr support.

Cody Miller

Visual C++ Team