Will Consteval Functions Allow Template Parameters Dependent on Function Arguments

Will consteval functions allow template parameters dependent on function arguments?

No.

Whatever changes the paper will entail, which is little at this point, it cannot change the fact that a non-template function definition is only typed once. Moreover, if your proposed code would be legal, we could presumably find a way to declare a variable of type std::integral_constant<int, i>, which feels very prohibitive in terms of the ODR.

The paper also indicates that parameters are not intended to be treated as core constant expressions in one of its examples;

consteval int sqrsqr(int n) {
return sqr(sqr(n)); // Not a constant-expression at this point,
} // but that's okay.

In short, function parameters will never be constant expressions, due to possible typing discrepancy.

Keeping consteval-ness of function arguments

In {fmt} 8.0 and later you can do this by using the format_string template that, as the name suggests, represents a format string (https://godbolt.org/z/bqvvMMnjG):

struct my_exception : std::runtime_error {
template <typename... T>
my_exception(fmt::format_string<T...> fmt, T&&... args)
: std::runtime_error(fmt::format(fmt, std::forward<T>(args)...)) {}
};

Why can't we use compile-time 'variables' in consteval functions as template parameters?

It doesn't matter that i is guaranteed to be evaluated only at compile-time when its value is known in an abstract sense.

It also doesn't matter whether the function is consteval or constexpr or none of these.

The language is still statically typed and nth_type_t<i, Ts...>; must in any given instantiation of the function refer to exactly one type. If i can change in the for loop, that is not possible to guarantee.

The language requires that the expression i when used as template argument is by itself a constant expression, independently of whether the whole function body can only be evaluated as part of a larger constant expression. But i is neither declared constexpr, nor declared const with constant initializer.

consteval with templates possible?

As pointed out by, @cigien this is indeed a clang bug. It works fine with gcc.

Nested call of consteval functions with a reference argument

This is a clang bug. gcc and msvc are correct to accept it.

There are two relevant rules in question:

All immediate invocations must be constant expressions. This comes from [expr.const]/13:

An expression or conversion is in an immediate function context if it is potentially evaluated and either:

  • its innermost enclosing non-block scope is a function parameter scope of an immediate function, or
  • its enclosing statement is enclosed ([stmt.pre]) by the compound-statement of a consteval if statement ([stmt.if]).

An expression or conversion is an immediate invocation if it is a potentially-evaluated explicit or implicit invocation of an immediate function and is not in an immediate function context. An immediate invocation shall be a constant expression.

And touching an unknown reference is not allowed in constant expressions (this is [expr.const]/5.13):

An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following: [...]

  • an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
    • it is usable in constant expressions or
    • its lifetime began within the evaluation of E;

For more on this latter rule, see my post on the the constexpr array size problem and my proposal to resolve this (hopefully for C++23).


Okay, back to the problem. foo is obviously fine, it doesn't do anything.

In bar, we call foo(t). This is not a constant expression (because t is an unknown reference), but we are in an immediate function context (because bar is consteval), so it doesn't matter that foo(t) is not a constant expression. All that matters is that bar("abc") is a constant expression (since that is an immediate invocation), and there's no rule we're violating there. It is pretty subtle, but the reference t here does have its lifetime begin within the evaluation of E -- since E here is the call bar("abc"), not the call foo(t).

If you mark bar constexpr instead of consteval, then the foo(t) call inside of it becomes an immediate invocation, and now the fact that it is not a constant expression is relevant. All three compilers correctly reject in this case.

when to use template non-type classes or plain arguments in constexpr functions

The short version: Use non-type template parameter to set non-type template arguments (more general everywhere, where you need a constant expression) and normal arguments for everything else.

The thing about constexpr functions you always have to keep in mind is that they can also be called at runtime. So every normal argument is not necessarily a constant expression. Hence you cannot use it to provide a non-type template argument (as the I in std::get<I>).

Of course one could argue that when called to calculate a constexpr variable the passed arguments are always constant expressions and could be used as such also inside the function. But it would be unexpected if a constexpr function works at compile time but not anymore at runtime.

One could expect that with the new consteval keyword in C++20, one could use normal arguments to consteval functions in constant expressions, since we know that these arguments have to be constant expressions. But this does not seem to be the case: https://godbolt.org/z/guz7FQ Why this is the case I do not know. But in general I like the seperation between normal variables and non-type template arguments.

consteval function returning object with non-trivial constexpr destructor

I think this code is fine.

The salient aspect of consteval is [expr.const]/12:

An expression or conversion is in an immediate function context if it is potentially evaluated and its innermost non-block scope is a function parameter scope of an immediate function. An expression or conversion is an immediate invocation if it is an explicit or implicit invocation of an immediate function and is not in an immediate function context. An immediate invocation shall be a constant expression.

In

void test() {
A a;
a = f(); // <-- here
}

f() is an immediate invocation (it is an explicit invocation of an immediate function that is not in an immediate function context). So the requirement is that f() has to be a constant expression.

Note: just f(), not a = f();, there is no requirement that the assignment operator here is a constant expression.

Everything that f() invokes is perfectly fine. All of A's special member functions are invocable during constant evaluation time. The result of f() is a permitted result of a constant expression ([expr.const]/10) likewise because it does not trigger any of the restrictions there either (A has no pointer or reference members, so trivially none of them refer to objects without static storage duration).

Ultimately, the question of constant evaluation boils down to going through all the list of restrictions and seeing if anything breaks. I don't think we violate any of the rules, so this should be fine.

C++20 consteval functions and constexpr variables - are they guaranteed to be evaluated at compilation time?

The standard does not have the concept of "compile time". There is only "constant evaluation" and a "constant expression". Implementations may implement "constant expressions" such that they are evaluated at compile time.

But there is no C++ standard-defined behavior you can point to in the actual C++ program that makes a "constant expression" equivalent to compile-time compilation. It is 100% legal for a compiler to emit code for constant expressions, and it always has been.

From [expr.const]/13:

"An immediate invocation [ie: calling a consteval function] shall be a constant expression

So calling a consteval function is a constant expression. Does this mean that the compiler certainly will not emit actual assembly for this function that gets called at runtime? No; that's a matter of the quality of the implementation.

However, certain uses of a constant expression do inform various compiler decisions. This means that, while the compiler does not in general have to evaluate them at compile time, if the value is used in a place that affects the basic act of compilation, the compiler must in that instance evaluate the expression right then.

For example:

std::is_same_v<array<int, some_constexpr_func(3, 4)>, array<int, 7>>

Whether this (constant) expression evaluates to true or false depends on what some_constexpr_func(3, 4) returns. If it returns the sum of its parameters, it is true. And the compiler needs to know that, because it needs to emit the code for the std::array<int, S> type, which as part of its type includes its template parameters. So it needs to know what they are.

Outside of cases where constant evaluation affects compiler decisions, whether any constant evaluation happens at compile time is up to the quality of your compiler.



Related Topics



Leave a reply



Submit