Tuesday, 17 June 2014

C++11 Features - Constant Expression

As C++ ISO standard committee promises that the future of C++ will be continuing focusing on its performance as one of its most dominant features. Watch what Bjarne Stroustrup said about the future of C++ on this video. Again here in C++11 constant expression is introduced as a feature/enhancement to improve C++ performance. As C++ is a language that writes once and compiles for all, the idea behind this new feature is to move some of work that used to be done at run time back to compiling time. As the time to evaluate the objects/function is spent ahead, sure for some sense it will enhance the run-time performance.

An extra keyword is introduced as constexpr. It can be used with build-int types, objects, functions, constructors and etc.

1. Motivation
Some of existing problems in C++03 have served well as the motivations of this new feature. Problems like some functions in <limits.h> not regarded const expression (not evaluated at compiling time), surprise of "dynamic initialization" of const expressions that are based on other const expression, and so on. In [2] a very good explanation and deep extension is provided.

2. constexpr and const
constexpr is introduced as a new feature/keyword in C++11, however const has been there for ages and it will continue exist to serve its purpose. Bear in mind that constepxr is not introduced to replace const.

constexpr serves different purpose. It tells the compiler that this piece of code can be evaluated at compile time. If objects or functions with constepxr specifier can be evaluated at compiling time, compiler will do their best to optimize this piece of code, for instance put the const objects into const data section and emit when code needs them.

const servers a different purpose and known as logical constant in C++. Except serving very limited functionality to tell compiler that this is const literal type and can be optimized better (for instance put the data into const data section and emit when code needs them), its main purpose is to be used in API definition. For instance pass const variable/reference/pointer as arguments. Simply it tells people that logically these arguments should stay untouched after calling the API functions. Another good example is to define const functions that tells what exactly functions a const object can invoke. (As I said const in C++ only works as logical constant, because the const-ness can be cast away and then object can be modified. Read Casting in C++.)

Now both of them are existing in C++11. Some of their functionality is overlapped for instance declaring/defining constant variables/objects to be evaluated at compiling time, but other functionality of const will stand as it is, for instance logical const-ness and API definition.

3. constexpr variables/objects
constexpr can be used with definition of vriables/object, and declaration/definition of functions and constructors. But it can't be used as a specifier with class types declaration and arguments. Actually both const and constexpr can't used with declaration of class type and only const specifier can be used with function/constructor arguments.

// Example 1: constexpr variables
//********************************************************************************
constexpr int Increment2(int x); // Ok function declaration
constexpr int Increment3(int x) { return x+3;} //  Ok - function definition
constexpr int x{2}; // Ok - constexpr variable

constexpr struct Foo {}; // Error - can't used with class type declaration
struct Bar {
    int x;
    int y;
    constexpr Bar(int a) : x(Increment3(a)), y(Increment3(a)) {} // Ok  - constructor definition
    constexpr Bar(int a, int b) : x(Increment2(a), y(Increment3(b)) {} // Ok - constructor definiton
}

constexpr Bar myBar1(1); // Ok - const object
constexpr Bar myBar2(1, 2); // Error - Increment2 not defined yet

constexpr int Increment2(int x) { return x+2;} // Ok - function definition
constexpr Bar myBar3(1, 2); // Ok - const object with all definition needed
//********************************************************************************

When constexpr A is used by constexpr B. constexpr A has to be declared and defined before constexpr B is evaluated. As shown in Example 1, myBar2 vs. myBar3.

In this case (variables/objects are declared with constexpr specifier) these variables/objects are declared const. This mean that constexpr specifier guarantees the const-ness and promise better code optimization. For the user-defined class types the {}-list has to be explicitly defined when defining a constexpr class types, as shown in Example 2.

// Example 2:
//********************************************************************************
struct Foo {
    int x;
    int y;
};

constexpr Foo cfoo1 = {1, 2}; // Ok, {}-list is explicit
constexpr Foo cfoo2; // Error
//********************************************************************************

4. constexpr functions
constepxr specifier can be used together with functions and constructors. The functions can be either standalone functions or member functions. constexpr functions and constructors are implicitly inline functions. It means that potentially production code with constexpr specifier can be better optimized and this is another performance enhancement features.

constexpr standalone functions
Because constexpr function can be evaluated at compiling time, therefore only some functions with certain features can be qualified as constexpr functions. For instance definitely functions with feature only at dynamic time are not qualified as constexpr functions. Here is the list of requirements,
    - Non-virtual function
    - Must not return void and must return a value of literal type
    - All its arguments must be literal types as well
Besides the requirements on the function declaration there are requirements on it function body as well. For instance only including null statement, typedef and so on. Please refer to [1] and [5] for more details. They are quite obscure descriptions. In my understanding the function body must not have operator/function invocation that is to change value of existing variables, such arguments or automatic variables declared inside,. For instance pre-increment and post-increment cannot be used in the constexpr function body because they change the value of existing variables. And the value passed as arguments and returned as result must be constexpr too and can be evaluated at compiling time.

One more thing to keep in mind. constexpr is acting like cv-qualifier. Functions with/without constexpr do not change their function signature, as shown in Example 3.

// Example 3
//********************************************************************************
constexpr int Increment2(int x) {reurn x+2; }
int Increment2(intx) {x+=2; return x;} // Error: redefinition
//********************************************************************************

constexpr constructors
Exactly the same as constexpr standalone functions constexpr constructor is to be evaluated at compiler time as well. Therefore any dynamic feature can't be applied and at the same time there are some same sort of requirements applied to its function body as well. Here is the requirements,
    - No virtual function
    - Can have base class that does not have virtual functions and apply recursively
    - All its member variables must be literal types.
    - All its member variables can be evaluated at compiling time and it must have constexpr constructor as well if they are class types
     - No function-try-block
The same requirements on standalone function body apply to constexpr constructor as well. Please refere to [1] and [5].
   
constexpr member functions
constexpr specifier can be used to decorate non-static member functions. And it guarantees the const-ness of this function. And only literal  class type can have constexpr non-static member functions, otherwise the program is ill-formed as shown in Example 4.

// Example 4
//********************************************************************************
struct Foo {
    explicit Foo(int val);
    constexpr std::string GetName() {return "Foo";} // Ok
    constexpr int GetValue() {return m_val;} // Error: m_val is not a constexpr

    int m_val;
};
//********************************************************************************

5. constexpr function evaluation
constexpr specified function can be evaluated at compiling time. If the argument passed through are constexpr expression, then this function will be evaluated at compiling time. Otherwise run time.

Function invocation substitution
Function invocation substitution happens when  a constexpr function is called in another constexpr function. The callee functions will be substituted with its function body or its result after evaluation in the caller function when the caller function is evaluated by compiler, as shown in Example 5. In this sense the callee constexpr functions must be declared and defined before the caller constexpr functions.

// Example 5
//********************************************************************************
constexpr int Foo() {return 0}
constexpr int Bar() {return Foo();} // substituted as exactly as Foo()

namespace name1 {
    constexpr int five = 5;
    constexpr int ReturnFieve() {return five;}
}

constexpr int ReturnFive() {return name1::ReturnFive();} // function body is substituted as "5".
//********************************************************************************

As function invocation substitution happens, the variable/object is initialized via {}-list by the return value of constexpr functions and the functions will be replaced by the deepest constexpr function body or its result, after evaluation by compiler.

constexpr vs. non-constexpr functions
Given a well-formed constexpr function, it can be called as a non-constexpr function or a constexpr function. The situation depends on what the arguments passed into the function to invoke it. If all the arguments are passed as constexpr expression, then this function will be invoked as constexpr function and evaluated at compile time, otherwise it will be called as a normal function. Comparing with non-constexpr function, constexpr functions provide opportunities to evaluate some of invocations at compiling time and better code optimization. However non-constexpr compiler will invoke them all at run time even thought they can be evaluated at compiling time as shown in Example 6.

// Example 6
//********************************************************************************
constexpr int factorial1(int n) {
    return n >= 1? 1 : (n*factorial(n-1));
}

int factorial2(int n) {
    return n >= 1? 1 : (n*factorial(n-1));
}

int myArray[factorial1(5)]; // Ok - array with size of 5! (factorial1(5) evaluated at compiling time)
int myArray[factorial2(5)]; // Error - array have to be declared with const size (known at compiling time)

constexpr int n = 6;
int m = 7;
factorial1(n); // evaluated at compiling time
factorial1(m); // evaluated at run time
factorial2(n); // evaluated at run time
factorial2(m); // evaluated at run time
//********************************************************************************

6. Summary
Definitely it will be good practice to use constexpr specifier to replace const specifier to declare constant literal types variables/object in order to prevent  surprising "dynamic initialization" as stated in Section 1. Use constexpr function for simple/arithmetic functions (easier for inline as well). For constructor and member functions C++11 is still very strict about user-defined types. It has to be literal types (please refer to [1]) that is very strict. So use constexpr constructor and member function only on simple data structure (like POD, refer to Plain Old Data (POD) on C++11). Google C++ Style Guide provides good suggestions.

Bibliography:
[1] C++11 standard
[2] [N2235=07-0095] Gabriel Dos Reis, Bjarne Stroustrup, and Jens Maurer: Generalized Constant Expressions -- Revision 5
[3] http://www.stroustrup.com/C++11FAQ.html
[4] http://en.wikipedia.org/wiki/C%2B%2B11
[5] http://en.cppreference.com/w/cpp/language/constexpr
[6] http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Use_of_constexpr

No comments:

Post a Comment