VS2015 Update 1中的常量表达式

[原文发表地址]Constexpr in VS2015 Update 1

[原文发表时间] 2015/12/2 1:44 PM

VS2015 RTM已集成支持C++11标准中指定的常量表达式。此版本收到了来自用户和C++社区很多较好的反馈。基于这样的反馈,在VS2015 Update1 中我们一直致力于完善新特性的实现工作。我们在VS2015 Update1中的目标是完成C++11常量表达式最后一个重大功能和提高我们所并健全所实现的特性。这篇博客将会提供一些注释去解释我们在VS2015 Update 1中集成重点以及在哪些地方支持常量表达式。
静态初始化器VS 2015引进一个警告,在对常量表达式进行评估时,它会指示编译器检测和并使用初始值,但是并不能静态地释放这些初始值。这也就是说,虽然编译器可以从已编译的可执行文件加载足够的信息来释放完全实例化的类型,但它并没有释放。这些类型在运行时被实例化和构造,正如大多数传统的C++对象一样。
好消息是,VS2015 Update1现在支持释放这些静态初始值。这些类型在加载到内存时将被完全实例化,而不是在运行时初始化它们。这是所需要实现的最后一个C++11常量表达式功能,我们很高兴在Update 1中集成了这一功能。
我们应该将荣誉归于Tanveer Gani,为了在Update 1中集成该功能,他完成了很多艰巨工作。基于他的工作,在Update1中已完全支持释放常量表达式对象的静态初始值。并且部分支持含有常量表达式构造函数的非文本类型对象的初始化(详见C++标准文档3.6.2节) 。需要特别要指出,依然不支持虚函数类型。
静态初始化器是实现std::once_flag的重要部分,它被用于实现std::call_once。Stephan在他的博客中描述了STL在VS2015 RTM版本中的改进。
VS2015 Update 1中在运行时的执行时期生成代码的减少量是相当惊人的。我想花些时间用一些例子去探讨这一变化。
例1 :常量表达式对象的初始化我们先从一个简单的例子开始——一个简单的含有常量表达式构造函数的结构体。
struct Point {
constexpr Point(int x1, int y1)
: x(x1), y(y1)
{}
int x;
   int y;
};
constexpr Point p1(10, 11);

首先,在VS 2015 RTM 中生成的该代码程序集(与Update 1相比较):
;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

请注意,VS 2015 Update 1中生成的程序集没有初始化代码。在VS 2015 Update 1的调试器下运行代码将不会执行Point的构造函数。
例2 :常量表达式对象数组的初始化接着上边继续定义Point,我们创建一个Point数组:
; 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

例3 :初始化指针和引用常量表达式对象的成员这段代码初始化一个常量表达式对象的指针和引用一个常量表达式的全局变量。
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 };
此示例实际上在VS 2015 RTM中将导致内部编译错误,但是在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
例4:初始化基类构造函数常量表达式类
尽管类中有复杂的继承关系(非虚拟),但依然可以被静态初始化。我并不会列出VS2015 RTM中所有的这种情况,因为这是令人望而却步的, 但是你可以加编译选项/FAsc 编译下面的程序片段来查看COD文件。

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;

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

例5:初始化非文本类型

如上文所述,使用常量表达式初始化的非文本类型可以被静态初始化。在下面的示例中,初始化的常量表达式构造函数是一个常量,所以Update 1中可以静态初始化它。请注意此类型具有析构函数,这使该类型为非文本类型:
extern "C"int puts(const char*);
struct NonLiteralType {
constchar *p;
constexpr NonLiteralType(constchar *pp)
:p(pp)
{}
~NonLiteralType() {
puts("~NonLiteralType()");
}
};
NonLiteralType nlt("qwerty");
int main(){}

Update 1上生成的程序集在常量区并没有存放对象,因为没有声明为常量表达式。

; VS 2015 Update 1 asm
CONST SEGMENT
$SG2669 DB 'qwerty', 00H
CONST ENDS
_DATA SEGMENT
?nlt@@3UNonLiteralType@@A DD FLAT:$SG2669    ; nlt
_DATA ENDS

本类型对象的销毁伴随着注册终止函数“atexit”而告终:
; 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
改进质量伴随着静态初始化的工作,我们已经修复了45个与常量表达式使用相关的bug。这些bug大部分来自于客户。因为我们会优先处理客户的问题,所以当你编写常量表达式代码时应该会看到我们改进的版图而不是局限在特定的区域。下表是我们修复的bug,同时感谢每一提交bug的人。

标题

联系用户

联系ID

[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

   

展望未来尽管已将C++11常量表达式的改进加入Update 1,但任然有一些细节需要我们去完善。这方面我们仍积压有30个bug,还有很多与常量表达式成员指针相关的问题。数组和字符串的别名上还有一些工作需要做,虽然Tanveer已在静态初始化方面打下坚实的基础,但是我们计划对新来的一定数量的bug作一些相关改变。
本质上,这意味着在很长一段时间内,我们还会一直致力于C++11常量表达式,但是尚未解决的问题是可以处理的。我们的目标是在VS下一个Update中集成所有的工作。这之后将会是全力对C++14常量表达式的支持。
Cody Miller
Visual C++ 项目组