问题
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
回答1:
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]
- 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.
来源:https://stackoverflow.com/questions/48541044/call-non-constexpr-from-constexpr-template-function