I have been looking at the new constexpr
feature of C++ and I do not fully understand the need for it.
For example, the following code:
Something you can do with constexpr that you can not do with macros or templates is parsing /processing strings at compile time: Compile time string processing (changing case, sorting etc.) with constexpr. As a small excerpt from the preceding link, constexpr allows one to write code such as:
#include "my_constexpr_string.h"
int main()
{
using namespace hel;
#define SDUMP(...) static_assert(__VA_ARGS__, "")
SDUMP(tail("abc") == "bc");
SDUMP( append("abc", "efgh") == "abcefgh" );
SDUMP( prepend("abc", "efgh") == "efghabc" );
SDUMP( extract<1,3>("help") == "el" );
SDUMP( insert<1>("jim", "abc") == "jabcim" );
SDUMP( remove("zabzbcdzaz", 'z') == "abbcdazzzz" );
SDUMP( erase("z12z34z5z", 'z') == "12345" );
SDUMP( map("abc", ToUpper()) == "ABC" );
SDUMP( find("0123456777a", '7') == 7 );
SDUMP( isort("03217645") == "01234567");
}
As an example of when this could be useful, it could facilitate the compile time computation/construction of certain parsers and regular expression finite-state-machines that are specified with literal strings. And the more processing you can push off to compile time, the less processing you do at run time.
int MaxSize() {
...
return ...; }
static const int s_maxSize = MaxSize();
int vec[s_maxSize];
No, it can't. That's not legal C++03. You have a compiler extension that can allocate variable-length arrays.
Another neat trick that constexpr allows if to detect undefined behavior at compile time which looks like a very useful tool. The following example taken from the question I linked uses SFINAE to detect if an addition would cause overflow:
#include <iostream>
#include <limits>
template <typename T1, typename T2>
struct addIsDefined
{
template <T1 t1, T2 t2>
static constexpr bool isDefined()
{
return isDefinedHelper<t1,t2>(0) ;
}
template <T1 t1, T2 t2, decltype( t1 + t2 ) result = t1+t2>
static constexpr bool isDefinedHelper(int)
{
return true ;
}
template <T1 t1, T2 t2>
static constexpr bool isDefinedHelper(...)
{
return false ;
}
};
int main()
{
std::cout << std::boolalpha <<
addIsDefined<int,int>::isDefined<10,10>() << std::endl ;
std::cout << std::boolalpha <<
addIsDefined<int,int>::isDefined<std::numeric_limits<int>::max(),1>() << std::endl ;
std::cout << std::boolalpha <<
addIsDefined<unsigned int,unsigned int>::isDefined<std::numeric_limits<unsigned int>::max(),std::numeric_limits<unsigned int>::max()>() << std::endl ;
}
which results in (see it live):
true
false
true
int vec[s_maxSize];
is actually illegal in the second example, so that is not possible to do in C++. But your first example is perfectly legal C++0x.
So there's your answer. You can't actually do what you propose in C++. It can only be done in C++0x with constexpr
.
I would also like to point out, that this code also works in C++0x. Doing this in C++ would require some really fancy class templates.
constexpr unsigned int gcd(unsigned int const a, unsigned int const b)
{
return (a < b) ? gcd(b, a) : ((a % b == 0) ? b : gcd(b, a % b));
}
char vec[gcd(30, 162)];
Of course, in C++0x you still have to use the ternary operator instead of if statements. But, it works and is still a lot easier to understand than the template version you'd be force to use in C++:
template <unsigned int a, unsigned int b>
class gcdT {
public:
static unsigned int const value = gcdT<b, a % b>::value;
};
template <unsigned int a>
class gcdT<a, 0> {
public:
static unsigned int const value = a;
};
char vec[gcdT<30, 162>::value];
And then, of course, in C++ you'd still have to write the gcd
function if you needed to compute things at runtime because the template can't be used with arguments that vary at runtime. And C++0x would have the additional optimization boost of knowing that the result of the function is completely determined by the passed in arguments, which is a fact that can only be expressed with compiler extensions in C++.
constexpr
allows the following to work:
#include<iostream>
using namespace std;
constexpr int n_constexpr() { return 3; }
int n_NOTconstexpr() { return 3; }
template<size_t n>
struct Array { typedef int type[n]; };
typedef Array<n_constexpr()>::type vec_t1;
typedef Array<n_NOTconstexpr()>::type vec_t2; // fails because it's not a constant-expression
static const int s_maxSize = n_NOTconstexpr();
typedef Array<s_maxSize>::type vec_t3; // fails because it's not a constant-expression
template
arguments really do need to be constant-expressions. The only reason your example works is because of Variable Length Arrays (VLAs) - a feature that is not in standard C++, but might be in many compilers as an extension.
A more interesting question might be: Why not put constexpr
on every (const) function? Does it do any harm!?
By that reasoning you don't need constants in general, not even #define
. No inline functions or anything.
The point of constexpr
, like so many keywords, is to let you express your intent better so the compiler understands exactly what you want instead of just what you're telling it, so it can do better optimizations for you behind the scenes.
In this example, it lets you write maintainable functions to calculate vector sizes instead of just plain text that you copy and paste over and over.