Consider the following code:
template<int value>
constexpr int foo = value;
template<typename... Ts>
constexpr int sum(Ts... args) {
return foo<(args + ...)>;
}
int main() {
static_assert(sum(10, 1) == 11);
}
clang 4.0.1 gives me the following error:
main.cpp:6:17: error: non-type template argument is not a constant expression
return foo<(args + ...)>;
^~~~
This surprised me. Every argument is known at compile time, sum
is marked as constexpr
, so I see no reason why the fold expression can't be evaluated at compile time.
Naturally, this also fails with the same error message:
constexpr int result = (args + ...); // in sum
[expr.prim.fold]
isn't very helpful, it's very short and only describes the syntax allowed.
Trying out newer versions of clang also gives the same result, as does gcc.
Are they actually allowed or not?
A constant expression is allowed to contain a fold expression. It is not allowed to use the value of a function parameter, unless the function call is itself part of the entire constant expression. By way of example:
constexpr int foo(int x) {
// bar<x>(); // ill-formed
return x; // ok
}
constexpr int y = foo(42);
The variable y
needs to be initialized with a constant expression. foo(42)
is an acceptable constant expression because even though calling foo(42)
involves performing an lvalue-to-rvalue conversion on the parameter x
in order to return its value, that parameter was created within the entire constant expression foo(42)
so its value is statically known. But x
itself is not a constant expression within foo
. An expression which is not a constant expression in the context where it occurs can nevertheless be part of a larger constant expression.
The argument to a non-type template parameter must be a constant expression in and of itself, but x
is not. So the commented-out line is ill-formed.
Likewise your (args + ...)
fails to be a constant expression (and hence cannot be used as a template argument) since it performs lvalue-to-rvalue conversion on the parameters of sum
. However, if the function sum
is called with constant expression arguments, the function call as a whole can be a constant expression even if (args + ...)
appears within it.
Some readers of this question might be interested in knowing how OP:s example could be modified in order to compile and run as expected, hence I'm including this addendum to the Brian:s excellent accepted answer.
As Brian describes, the value of the variadic function parameter is not a constant expression within sum
(but will not cause foo
to not be a constant expression as long as the parameter doesn't "escape" the scope of foo
; as it has been created within the constant expression foo(42)
).
To apply this knowledge to OP:s example, instead of using a variadic function parameter that will not be treated as a constexpr
when escaping the constexpr
immediate scope of sum
, we may migrate the variadic function parameter to be a variadic non-type template parameter.
template<auto value>
constexpr auto foo = value;
template<auto... args>
constexpr auto sum() {
return foo<(args + ...)>;
}
int main() {
static_assert(sum<10, 1, 3>() == 14);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With