Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

c++17

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?

like image 859
Rakete1111 Avatar asked Aug 01 '17 06:08

Rakete1111


2 Answers

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.

like image 159
Brian Bi Avatar answered Oct 05 '22 11:10

Brian Bi


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);
}
like image 44
dfrib Avatar answered Oct 05 '22 10:10

dfrib