Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call non constexpr from constexpr template function

I stumbled on constexpr template functions calling non constexpr functions: In the following snippet bar fails to compile as expected due to the call of non constexpr set but foo compiles. Can anyone tell me the reason why foo compiles?

template<class T>
void set(T& x){
    x++;
}

template<class T>
constexpr void foo(T& x){
    set<T>(x);
}

constexpr void bar(int& x){
    set<int>(x);
}

void bar(){
    int x = 5;
    foo(x);
    bar(x);
}

The compiler fails to compile with the error:

<source>: In function 'constexpr void bar(int&)':
<source>:12:13: error: call to non-constexpr function 'void set(T&) [with T = int]'
     set<int>(x);
     ~~~~~~~~^~~
Compiler returned: 1

Edit: Appended compiler error and rephrased question. Side effects are not in focus here.

As stated by bolov and Rekete1111 below the template will be evaluated later. When the constexpr restrictions are not met, the constexpr template function becomes some kind of semi constexpr function. The -Og compile result of the next code snippet shows, that the constexpr foo will be optimized out and the common foo2 not while both do not fulfill the requirements of constexpr functions (probably a consequence of the inline implication of constexpr):

template<class T>
void set(volatile T& x){
    x++;
}

template<class T>
constexpr void foo(T& x){
    set<T>(x);
}

template<class T>
void foo2(T& x){
    set<T>(x);
}

void bar(){
    int x = 5;
    foo(x);
    foo2(x);
}

The compile result:

void set<int>(int volatile&):
  ldr r3, [r0]
  add r3, r3, #1
  str r3, [r0]
  bx lr
void foo2<int>(int&):
  push {r4, lr}
  bl void set<int>(int volatile&)
  pop {r4, lr}
  bx lr
bar():
  push {r4, lr}
  sub sp, sp, #8
  add r4, sp, #8
  mov r3, #5
  str r3, [r4, #-4]!
  mov r0, r4
  bl void set<int>(int volatile&)
  mov r0, r4
  bl void foo2<int>(int&)
  add sp, sp, #8
  pop {r4, lr}
  bx lr
like image 773
Alex R. Avatar asked Jan 31 '18 11:01

Alex R.


People also ask

Can constexpr functions call non constexpr functions?

A call to a constexpr function produces the same result as a call to an equivalent non- constexpr function , except that a call to a constexpr function can appear in a constant expression. The main function cannot be declared with the constexpr specifier.

Can constexpr call non constexpr?

constexpr function can call only other constexpr function not simple function.

Can a constexpr function throw?

Even though try blocks and inline assembly are allowed in constexpr functions, throwing exceptions or executing the assembly is still disallowed in a constant expression.


1 Answers

It's because foo is a function template and bar is a function.

For a function (e.g. bar) to be constexpr it must meet all of the constexpr rules (which change from standard to standard) and that is checked at the definition of the function. You get an error if those rules aren't met.

For a function template because you have only a template to generate functions you can't enforce the rules for constexpr. E.g. in your example at the point of the template definition you don't know if set<T>(x) is constexpr because you might have some template instantiations of set who are constexpr and some other template instantiations for set which are not. So you can't check that foo meets the requirements for constexpr. You can only check specific instantiations of foo if are constexpr e.g. foo<int> or foo<char> etc.

C++ handles this situation by allowing constexpr for a function template indiscriminately (sort of). However if a instantiations of the template doesn't meet the requirements for constexpr then that is allowed, but the specialization is not allowed in a a constant expression.

You can see this with a slightly modified code from your example:

auto set(int a) { return a; }
constexpr auto set(char a) { return a; }

template<class T>
constexpr auto foo(T x){
    return set(x);
}

auto test()
{
    auto x = foo(24); // foo<int>  OK, no error
    //constexpr auto cx = foo(24) // foo<int> compiler error

    auto y = foo('a'); // foo<char> OK, no erro
    constexpr auto y = foo('a'); // foo<char> OK
}

§7.1.5 [dcl.constexpr]

  1. If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is still a constexpr function or constexpr constructor, even though a call to such a function cannot appear in a constant expression. If no specialization of the template would satisfy the requirements for a constexpr function or constexpr constructor when considered as a non-template function or constructor, the template is ill-formed; no diagnostic required.
like image 174
bolov Avatar answered Sep 21 '22 03:09

bolov