Giorgio
Giorgio

Reputation: 5183

Is constexpr really needed?

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

Answers (6)

Shafik Yaghmour
Shafik Yaghmour

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

Aaron McDaid
Aaron McDaid

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

Faisal Vali
Faisal Vali

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

Puppy
Puppy

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

Omnifarious
Omnifarious

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

Blindy
Blindy

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

Related Questions