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
0 comments