Reputation: 5183
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:
constexpr int MaxSize()
{
...
return ...;
}
void foo()
{
int vec[MaxSize()];
}
can be replaced by:
int MaxSize()
{
...
return ...;
}
static const int s_maxSize = MaxSize();
foo()
{
int vec[s_maxSize];
}
Update
The second example is actually not standard ISO C++ (thanks to several users for pointing this out) but certain compilers (e.g. gcc) support it. So it is not const
that makes the program valid, but the fact that gcc supports this non-standard feature. (To my knowledge, this is possible only when the array is defined as local to a function or method, since the size of a global array must still be known at compile time.) If I compile without the options -std=c++98 -pedantic-errors
, even the code
int MaxSize()
{
return 10;
}
void foo()
{
int vec[MaxSize()];
}
will compile with gcc.
So I will try to rephrase my question taking into account the feedback that came so far (and also some further reading I have done in the mean time).
I use the const
keyword heavily. With const
I can define a constant that has a certain value during its whole lifetime. A constant can be initialized with any expression, which is evaluated once, namely when the constant is created. For these cases, I think that constexpr
is pretty useless: it would introduce a very small optimization in that the expression defining the constant value would be computed at compile time instead of run time. Every time I need a run-time constant with a complex initialization I use the keyword const
.
So constexpr
may come in handy in situations where we need to initialize a constant at compile time. One example is a vector definition: the standard does not support defining the size at runtime. Another example is a template with one or more non-type parameters.
In such cases I normally use macros:
#define MAX_SIZE (10)
void foo()
{
int vec[MAX_SIZE];
}
but, if I understand correctly, constexpr
functions are more powerful than macros, since they allow recursive calls of constexpr
functions in their definition. However, I cannot think of any practical application in which I ever wanted to use such a complex computation to define a compile-time constant.
So, even if it may be an interesting feature, I still wonder if it is needed (i.e. how often it can solve situations where macros are not enough). Maybe looking at a few real-life examples that cannot be solved with macros would help me to change this opinion.
Upvotes: 18
Views: 8081
Reputation: 158599
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
Upvotes: 1
Reputation: 27183
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!?
Upvotes: 0
Reputation: 33713
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.
Upvotes: 7
Reputation: 146998
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.
Upvotes: 3
Reputation: 56078
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++.
Upvotes: 22
Reputation: 67447
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.
Upvotes: -4