April 29th, 2015

C++11 Constant Expressions in Visual Studio 2015 RC

In Visual Studio 2015 RC, we have implemented C++11’s Constant Expressions, a feature that has been in development since the November 2013 CTP. This article will describe some neat things that you can do with it, the differences in capabilities between this feature’s current and older implementations, and our future plans for it.

What Are Constant Expressions?

The Constant Expressions feature allows you to create, using the constexpr keyword, computations that can run during compilation and whose results can be considered const. Something can only be declared constexpr if it satisfies all necessary constraints that allow it to be evaluated by the compiler using only the information available at compile time.

What Can They Be Used For?

Type Safety

Prior to the introduction of constant expressions, it was common to use macros to compute const values:      #define LENGTHOF(x) (sizeof(x) / sizeof(x[0]))
     char x[10];
     char y[LENGTHOF(x)]; // y also has 10 elements But macros are not type safe. The following code compiles despite the fact that x is a pointer, which results in the calculation of nonsense:      char *x;
     char y[LENGTHOF(x)]; // No error! Expands to (sizeof(char *) / sizeof(x[0])) To avoid this issue, we can use constant expressions:      template<typename T, size_t length>
     constexpr size_t lengthof(T (&)[length])
     {
         return length;
     } This function works just like the LENGTHOF macro:      char x[10];
     char y[lengthof(x)]; Except that the compiler emits an error if x is not an array, which prevents this function from being used in ways that don’t make sense:      char *x;
     char y[lengthof(x)]; // Error C2784: ‘size_t lengthof(T (&)[N])’: could not deduce template argument for ‘T (&)[N]’ from ‘char *’

Generally speaking, you should use constant expressions instead of macros wherever you can, because the former lets the compiler perform the same type checking that it does for normal C++ code.

Meta-Programming

C++’s template system is actually a full-fledged functional programming language, and as such, it is often used to perform complex compile-time computations. But because it was not designed as a general purpose language, it is usually cumbersome and sometimes difficult to express these computations. For example, if for some reason you need to compute exponentiation at the type level, you’d have to write something like:      #include <type_traits>
     template <int x, int n, typename Cond = void>

     struct Exp
     {
         static const int result = Exp<x * x, n / 2>::result;
     };

     template<int x, int n>
     struct Exp<x, n, std::enable_if_t<n % 2 == 1>>
     {
         static const int result = Exp<x * x, (n – 1) / 2>::result * x;
     };

     template<int x>
     struct Exp<x, 0>
     {
         static const int result = 1;
     }; Strange syntax, spread out cases, and roundabout condition checking hinder one’s understanding of this code, which makes debugging it difficult. Also, since floating point numbers can’t be used as non-type template parameters, this exponentiation “function” will only work for integers, which is a serious limitation. To improve readability and functionality, constant expressions can be used instead:      constexpr float exp( float x, int n)
     {
         return n == 0 ?     1 :
                n % 2 == 0 ? exp(x * x , n / 2) :
                             exp(x * x , (n – 1) / 2) * x;
     }; Because we are now using normal C++ expressions, this function is not only much shorter and easier to read, but also capable of handling floating point arguments.

Known Issues

Currently, implicitly-defined constructors aren’t automatically specified as constexpr even when they would fulfill all the requirements to be so:      struct A
     {
         virtual void f();
     };

     struct B : A
     {
         constexpr B() {}; // error C2134 : ‘A::A’ : call does not result in a constant expression
     };

To work around this, you need to explicitly define a constexpr constructor for that class:      struct A
     {
         virtual void f();
         constexpr A() {};
     };

constexpr constructors also can’t yet initialize array data members:      struct S
     {
         int a[5]; // note: ‘S::a’ was not initialized by the constructor
         constexpr S() : a() { } // error C2476: ‘constexpr’ constructor does not initialize all members
     };

     int main()
     {
         constexpr S b; // error C2127: ‘b’: illegal initialization of ‘constexpr’ entity with a non-constant expression
     } We plan to fix these bugs, among others, in Visual Studio 2015 RTM or Update 1.

Next Steps

C++14’s “generalized” constant expressions feature relaxes some restrictions with the constructs described above. For example, in C++14, constexpr functions can contain statements, which will allow them to make use of loops and modify local variables. We plan to implement C++14 constant expressions in the near future.

Since we are always looking for ways to improve our compiler, please make heavy use of this feature in your code and keep sending us feedback through Microsoft Connect or the Visual Studio feedback tool. You can also contact me directly at kaniu@microsoft.com.

Category
C++

Author

0 comments

Discussion are closed.