Rakete1111
Rakete1111

Reputation: 49018

Why can't fold expressions appear in a constant expression?

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?

Upvotes: 16

Views: 2867

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275800

Your problem is unrelated to ....

template<class T0, class T1>
constexpr int sum(T0 t0, T1 t1) {
  return foo<(t0+t1)>;
}

this also fails in the same way.

Your problem is, in essence, that a constexpr function must be callable with non-constexpr arguments.

It is a common misunderstanding of what constexpr means: it doesn't mean "always constexpr".

There are complex standard clauses saying what goes wrong here, but the essence is that within a constexpr function, the function arguments themselves are not considered constexpr. The result of the function can be if the inputs are, but within the function the code must be valid even if the arguments are not constexpr.

You can still work around this: user define a literal ""_k that parses the integer and generates an integral_constant.

static_assert(sum(10_k, 1_k) == 11);

would compile and run, because + on integral constants doesn't depend on the variables being constexpr. Or you can take the values as non-type template parameters. static_assert(sum<10, 1>() == 11);

Upvotes: 3

dfrib
dfrib

Reputation: 73206

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);
}

Upvotes: 5

Brian Bi
Brian Bi

Reputation: 119457

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.

Upvotes: 12

Related Questions