Expanding the Scope of C++!
In last month’s installment, you covered the context of C++ standards development, TR1, and the rationale for expanding the scope of C++. The next C++ standard, code-named “C++00X”, is heading down the home stretch towards ratification. Currently, it is expected to be named C++09 as of the latest report (N2336) released in July 2007. In this article, you peek further into what C++00X means to the average C++ programmer in the trenches. Because there are dozens of WG21 proposals on the table as of this writing, I will focus this article solely on proposals that were greenlighted at the July 2007 meeting, rather than discussing features that have been integrated in past months’ or years’ work.
Generalized Constant Expressions
Recently integrated into the working paper, Generalized Constant Expression represents the fourth iteration of attempts to normalize the working of constants in the C++ language. Although constants are everywhere in C++ programs (including const and enum), their participation in expressions has a certain degree of ambiguity under current rules. N2335 addresses all of these concerns in four specific categories:
- Improving type-safety and portability for code requiring compile time evaluation
- Improving support for systems programming, library building, generic programming
- Simplifying the language definition in the area of constant expression to match existing practice
- Removing embarassments from existing Standard Library components (namely #include limits)
Fixing the Bitmask
One problem this fixes is the bitmask type when it’s implemented by an enum with overloaded operators. This is just one of three allowable implementations by the compiler; when it’s used, the bitmask is no longer constant in the sense that it can be evaluated at compile time. The new constexpr keyword addresses this and many other things that allow for compile-time optimization of things that formerly were deferred until execution time.
When const Isn’t
It is possible to be surprised by expressions that (to someone) “look const” but are not. For example, consider
struct S { static const int size; }; const int limit = 2 * S::size; // dynamic initialization const int S::size = 256; // dynamic initialization const int z = numeric_limits<int>::max();
Here, S::size is indeed initialized with a constant expression, but that initialization comes “too late” to make S::size a constant expression; consequently, limit may be dynamically initialized. Again, constexpr will come to the rescue.
Constant-Expression Functions
The new distinction Constant-Expression Function marries the best attributes of #define macro functions with the type safety, predictability of const, and guaranteed compile-time evaluation. In particular, a function is a constant-expression function if and only if it satisfies these criteria:
- It returns a value (in other words, not void);
- Its body consists only of a single statement of the form.
return expr;
and every single thing that combines to produce expr is also constant.
Interestingly, a constant-expression function may be called with non-constant expressions (for example, variables). In that case, there is no requirement that the resulting value be evaluated at compile-time.
Here are some examples:
constexpr int square(int x) { return x * x; } // legal constexpr int abs(int x) { return x < 0 ? -x : x; } // legal constexpr int next(int x) { return ++x; } // error: increment cannot be evaluated at // compile time constexpr int g(int n) // error: body not just ''return expr'' { int r = n; while (--n > 1) r *= n; return r; } constexpr int fac(int x) { return x > 2 ? x * fac(x - 1) : 1; } // error: fac() not // defined before use
C99 Compatibility : __func__ and Predeclared Identifiers
The 1999 revision of the C standard introduced the concept of ‘a predeclared identifier’. N1970 carries that concept forward into C++ in the name of upward compatability, an attribute that the standards committee holds dear. In the past, the role of these diagnostic aids was a part of the macro preprocessor, a mechanism that the standards committee tries to relegate as much as possible. Basically, these predeclared identifiers allow you to construct intelligent error messages that point out the line number, file name, and function name where a problem is taking place. __func__ is the equivalent of a local declaration like:
static const char __func__[] = "function-name";
Traditionally, the double_underscore (“__”) denotes a compiler vendor-specific identifier, although it is now a universal identifier in this particular case. Here’s a typical usage:
include <cstdio> namespace example { void myfunc() { std::printf("Error in function %sn", __func__); /* ... */ } }
WG21 proposal N2220 provides further clarification of how __func__ changes over the course of the runtime environment:
namespace N { void f(); } void N::f() { } // __func__ is "f" struct S { S() : s(__func__) { } // okay, s points to "S" ~S() { } // __func__ is "~S" operator int() { } // __func__ is "conversion operator" template<class T> int g(); const char *s; }; S operator +(S,S) { } // __func__ is "operator+" template<> int S::g<int>() { } // __func__ is "g"