C++11 Tidbits: Introducing generalized constant expressions and constexpr

Consider the following code snippet:

template<int M> struct F { };
F<std::numeric_limits<int>::max()> f; // Error!

Of course the problem is that numeric_limits<>::max is a function and in C++03 its return value cannot be used to instantiate the class template F. Thus the C-style way of using limits, via macros like INT_MAX, is still unavoidable in C++, at least when templates are involved. Also, code like

const int z = numeric_limits<int>::max();

is legal in C++03 but z is dynamically (ie, at run-time), rather than statically initialized. However, the above max function can be considered 'constant', since its return value is certainly known at compile-time. The rationale for generalized constant expressions, that is 'sufficiently simple' functions generalizing constant expressions, should be now pretty clear. Among the goals of the new idea are also improved type-safety (eg, no macros) and portability for code using compile- time evaluation; improved support for system programming, library building, and generic programming. The old paper N2235 summarizes pretty well these issues, and is still recommended reading even if in the meanwhile quite a few technical details have changed (see N3225 and other recent papers).

Thus, looking inside the current C++ runtime library in GCC reveals that functions like max above are decorated with the new constexpr keyword, i.e., simplifying irrelevant details:

static constexpr
int max()
{ return __INT_MAX__; }

that is, max is declared as a constexpr function. Only sufficiently simple functions (eg, the body must consist of a single return statement, no iteration, no changes to the arguments, etc.) can be declared as such but then (assuming the arguments are in turn constant expressions) the function is completely computed and the return value inlined at each call site at compile-time.

The following are other examples:

constexpr int
square(int x)
{ return x * x; }

constexpr int
abs(int x)
{ return x < 0 ? -x : x; }

constexpr int
fac(int x)
{ return x > 2 ? x * fac(x - 1) : 1; }

float array[square(9)]; // Ok (not C99 VLA)

std::bitset<abs(-87)> s; // Ok

enum { Max = fac(5) }; // Ok

Note that, per the latest specifications, recursion is also allowed. Another clarification, code like:

extern const int medium;
const int high = square(medium); // Ok, dynamic init

is also legal but the call to square boils down to a normal function call, thus high is initialized at run-time because at compile-time the value of medium is not known - it isn't a C++03 constant expression, in other terms.

In C++1x there is also the concept of constant expression data:

constexpr int s = square(5);    // Ok
constexpr int high = square(medium); // error!

And of constant expression constructor:

struct complex
{
  constexpr complex(double r, double I): re(r), im(i) {}
  constexpr double real() { return re; }
  constexpr double imag() { return im; }
private:
  double re; double im;
};

constexpr complex I(0, 1);     // Ok
constexpr double i = I.imag(); // Ok

In GCC 4.6 generalized constant expressions work already pretty well (library bits included) but remember that this is still an uncharted territory, thus, please help testing, file bugs in the GCC Bugzilla, provide feedback, hopefully these introductory notes are enough to get you interested.

Comments:

Post a Comment:
  • HTML Syntax: NOT allowed
About

C++ enthusiasts only, please! ;)

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today