Constexpr in VS2015 Update 1

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