Is constexpr really needed? [duplicate]
Asked Answered
G

6

18

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.

Gulden answered 16/5, 2011 at 11:35 Comment(5)
Actually, my example as it is does not work. I have to declare the array within a function or object. I am using this idiom all the time.Gulden
@Giorgio: huh? How does that make it legal? Arrays have to be initialized with a size that is a constant expression. Some compilers (ie. GCC) allow you to use dynamically sized arrays, which is a C99 feature, which is not part of C++. Could you please edit your post to clarify how you'd do that?Noncommittal
If there is an answer here that you consider sufficient it would be good of you to accept it. :-)Dropline
I used constexpr when implementing AES algorithm. You have to calculate a number of elements, they are always constant but the number of elements is different. I chose to calculate 256 numbers. It was taking 1.5 million nanoseconds(1.5 ms), after using constexpr it was executing in 1 processor tick(513 nanoseconds in my case). And there are times when you have to make more complex operations...Diakinesis
I implement a SIMD permutation library where the user specifies the permutation in terms of bit permutations (i.e. a hypercube permutation, or Bit-Permute/Complement permutation), and the SIMD code is generated by template expansion. There is a need to compute an inverse permutation. Without constexpr, the alternative is to implement an entire template metaprogramming library for integer and sequence processing.Hagiolatry
D
22

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++.

Dropline answered 25/6, 2011 at 5:25 Comment(14)
Yes, my example is wrong, I normally use this idiom with the variable declaration (e.g. int vec[]) in a function or class constructor. I use it all the time. Declaring a global array in that way is illegal in C++.Gulden
So you need constexpr to define templates that have non-type parameters, so that you do not have to compute the value of a constant because you can let the compiler compute it. Are there any other examples where a value must be known at compile time and needs a non-trivial computation to be determined?Gulden
Maybe what is meant with constexpr is a pure function: a function that does not depend on any I/O and does not produce any side effect? This means that it only produces a result and that the result only depends on the value of the input parameters. In this case, all kinds of compile-time optimizations could be performed all over the code. But I guess the compiler becomes very complicated. On a first pass it has to compile all the functions in the program. Then it has to go through all the places in which constexpr functions are called, evaluate them, and replace the calls with the results.Gulden
@Gulden - Well, I think another rule is that in order to be considered constexpr functions the full definition has to be available to the compiler when it's called. And compilers already do this with inline functions. It's just that constexpr functions provide another level of hinting that allow a compiler to be even more aggressive. For example, constexpr functions can participate in CSE without the compiler even looking at their definitions.Dropline
@Giorgio: As a practical example, I recently wrote a set of templates for computing integer logrithms and exponents for use in figuring out exactly how many random numbers in a small range could be usefully extracted from a random number with a certain number of bits. Say you want a number from 1-6 and you have a 32 bit random number, you can think of it as a 12 digit number base six and get 12 random digits out of it. That would've been lots easier with constexpr.Dropline
@Omnifarious: Thanks for that gcd code! I have quoted it in my answer here: https://mcmap.net/q/672967/-relatively-prime-numbers/…Lakeshialakey
@Omnifarious: I do not object that it is useful to have the possibility to define the gcd() function as you did. I just wonder how often such cases occur in practice.Gulden
@Giorgio: Probably not very often, but I suspect more often than you think. :-)Dropline
@Giorgio: And also, I can imagine the constexpr integer logrithm and exponent functions being a part of a standard library. Whereas the template constructs are too specialized (since they can't be used to compute things at runtime) to be useful enough for that to be nearly as likely. That would've not only been easier to understand, but saved me a few hours of work.Dropline
@Omnifarious: Do you know if the new C++0x standard defines some library functions as being constexpr? Then these functions must be known to the compiler, I guess. I mean, if the compiler finds a constexpr double log(double), it must know which function is meant here (it should not use the linker to look up the function in a library). It would be interesting to understand how this is implemented.Gulden
@Giorgio: I happen to know that gcc uses built in versions for several functions (like log) that are annotated so the compiler knows they would satisfy the constexpr rules. But I do not know if this is mandated by the standard. IMHO, if it isn't, it should be. And I do not know if gcc's implementation of C++0x annotates them as constexpr specifically so you can use them in constant initialization.Dropline
@Omnifarious: What I mean is that a reference to a function in C / C++ is normally only resolved at link time (by calling the linker with certain options indicating the library search path). With constexpr the compiler might have to load and execute such functions at compile time already. This would mean that (1) the compiler must use the same search path as the linker, to be sure that they resolve constexpr functions in the same way, but that (2) this creates a dependency between two steps of the build process that would otherwise be independent, is this not problematic?Gulden
@Gulden - In fact, this already happens. In g++ on an x86 Linux platform sin(5) is optimized away as a constant expression at -O3. This happens regardless of what you link against.Dropline
@Gulden - And an expression like const int joe = ::std::strlen("Foo"); is allowed and results in joe having the value of 3 with no call to strlen in the assembly output.Dropline
G
7

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.

Gagger answered 24/7, 2011 at 15:23 Comment(4)
Nice. Here is another approach to it: #4583522 . A more tidied-up version: ideone.com/DWCRBBaler
Example use: Named parameters, ideone.com/tgapx . Sadly GCC4.6 still shouts about "sorry, unimplemented: use of 'type_pack_expansion' in template". So I can't test it, but I think it should work. Waiting for clang to implement this...Baler
@Faisal Vali Do you mean you have string literals in the specification of a lexical analyzer and you want to use const_expr functions to reduce these literals at compile time?Gulden
This would also be excellent for string obfuscation in the resulting binary.Kikuyu
P
3
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.

Pliable answered 25/6, 2011 at 11:11 Comment(0)
W
1

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
Williams answered 27/1, 2014 at 3:46 Comment(1)
Given extern int foo[]; the expression foo+3-foo invokes UB if foo has fewer than three elements, but I can't think any way a compiler could be expected to reject any template expansion that would try to compute that.Stedfast
M
0

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!?

Millenarian answered 28/10, 2013 at 21:25 Comment(0)
T
-4

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.

Telephony answered 25/6, 2011 at 5:13 Comment(5)
-1 - This does not really answer the OPs question at all, and is unnecessarily vitriolic besides.Dropline
I have nothing against constants: I use them all the time. What I need is to know that a name has a certain value assigned to it and that the value will not change. If I understand correctly, constexpr means: dear compiler, this looks like an expression that needs to be evaluated at runtime (because it might need input from the user) but it really isn't and can be evaluated at compile time. So this will save a few milliseconds time when the program starts up, because the same expression could have been evaluated at program start up and be assigned to a const variable.Gulden
@Giorgio, basically yes, but it also lets you assign function-valued constants to array sizes for example, since you can't make variable sized arrays in C++. Then there's the whole maintenance aspect.Telephony
@Bindy: Is this needed very often? Maybe macros are sufficient most of the time? I have always used macros for this and never felt the need for anything more powerful. Macros can call each other but, to my knowledge, cannot be used recursively. Is this limitation too strong?Gulden
not sure why this was downvoted quite so far, but while i agree that stating intent and allowing optional optimisations are crucial elements among the rationales for constexpr - they are blatantly not "The point", if we had to pick just one. (and that 'best reason' is surely to enable compile-time things like array dimensions, as already emphasised in other answers.)Penalty

© 2022 - 2024 — McMap. All rights reserved.