Doubt about constexpr

4

Why not always use the reserved word constexpr when defining methods or functions? If he calculates everything he can in compilation and what does not, he finds it running, without causing error.

    
asked by Jki 09.10.2018 в 22:25
source

2 answers

4

Problem.

The constexpr qualifier does not work intuitively. Contrary to what you say, there are no constexpr methods:

struct Entero
{
    constexpr int uno() { return 1; }
//  ~~~~~~~~~ <--- El retorno es constexpr, no el método.
};

struct Disparate
{
    int disparatado() constexpr { return 1; } // ESTO NO COMPILA!! :(
//                    ~~~~~~~~~ <--- Método constexpr.
};

In the previous code, we could think that the Entero::uno method is constexpr , but in fact it is the int returned, this code:

int main()
{
    Entero e;
    return e.uno();
}

Generate this asm:

main: # @main
  mov eax, 1
  ret

We can think that you are returning 1 (instead of calling the method) because you calculated at compile time that e.uno() was 1 , but we get exactly the same asm without checking Entero::uno as constexpr , that is: it is a compiler optimization, not a programmer optimization.

You can mark an entire object as constexpr but this will make it const implicitly, causing counterintuitive errors:

int main()
{
    constexpr Entero e;
    return e.uno(); // ERROR!! La función uno no es const
}

The same problem we encountered when returning an object as constexpr :

constexpr Entero f() { return {}; }

int main()
{
    return f().uno(); // ERROR!! La función uno no es const
}

To solve the above problem just mark Entero::uno as const :

struct Entero
{
    constexpr int uno() const { return 1; }
    //                  ~~~~~ <--- Método constante, retorno constexpr
};

constexpr Entero f() { return {}; }

int main()
{
    constexpr Entero e;
    return e.uno() + f().uno(); // Correcto!!
}

The previous code generates this asm:

main: # @main
  mov eax, 2
  ret

That will also generate without the presence of constexpr .

Conclusion.

The qualifier constexpr is a tool to help the compiler to make optimizations, but (almost) we will never be smarter than the compiler, using that qualifier can help move some calculations at compile time (which also the compiler can deduce how to do it without our help) so the goal should not be to use constexpr right and left to help the compiler do something he or she would do anyway.

Just like any tool at our disposal constexpr should be used when needed, its use should not be forced. When is it needed? For when we want to ensure an operation in runtime, although the compiler would also do so, adding constexpr denotes the intentionality of the code, which makes it more understandable for us and other people with whom we work.

Do not fall into the trap of gold hammer .

    
answered by 10.10.2018 в 09:58
4

The use of constexpr , applied to the value returned by functions, presents a small problem: limits what we can do within the function .

In C ++ 14 these limitations have been relaxed, but in C ++ 11, within the body of a constexpr function are only supported:

  • Null (a ; ).
  • Declarations static_assert( ) .
  • Declarations typedef and alias that do not define classes or enumerations.
  • Declarations using ;
  • Directives using ;
  • The body can only be and contain 1 only return .

This last point is, for me, the biggest limitation:

constexpr int test( int a, int b ) {
  if( a == b ) return 1;
  return 0;
}

If we try to compile it , we get:

  

In function 'constexpr int test (int, int)':
  error: body of 'constexpr' function 'constexpr int test (int, int)' not a return-statement

The correct way would be:

constexpr int test( int a, int b ) {
  return a == b ? 1 : 0;
}

This is because, explicitly, we are telling the compiler that the result of that function is susceptible to be calculated at compile time; the compiler's capabilities for these cases (compile-time optimizations) are quite limited: they are limited to certain arithmetic operations and some capacity for recursion .

However, declaring a function constexpr has some advantages too:

int test( int a, int b ) {
  return a == b ? 1 : 2;
}

int main( ) {
  char tmp[test( 1, 2 )];

  return 0;
}
  

warning: ISO C ++ forbids variable length array 'tmp': char tmp [test (1, 2)];

However, if we add constexpr :

constexpr int test( int a, int b ) {
  return a == b ? 1 : 2;
}

int main( ) {
  char tmp[test( 1, 2 )];

  return 0;
}

No errors or warnings occur, and compiles correctly.

Now, if we change the values literales ...

constexpr int test( int a, int b ) {
  return a == b ? 1 : 2;
}

int main( ) {
  int a = 1, b = 2;

  char tmp[test( a, b )];

  return 0;
}
  

warning: ISO C ++ forbids variable length array 'tmp': char tmp [test (a, b)];

Which illustrates another limitation of these functions (quite logical): only optimization is performed when the arguments are, in turn, constexpr . With which we are further limited practical use.

For more information, in English: cppreference

To finish: constexpr , more than for the compiler, it is for the programmer , to indicate / remind / limit that certain function wants to be optimized, and force you to write a code that the compiler can optimize more than another, without having a guarantee that such optimization will take place.

    
answered by 10.10.2018 в 10:36