Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested call of consteval functions with a reference argument

The following program

template<class T>
consteval auto foo(const T&) {
   return 0;
}

template<class T>
consteval auto bar(const T& t) {
   auto n = foo(t);
   return n;
}

int main() {
   static_assert(foo("abc") == 0);
   static_assert(bar("abc") == 0);
}

is built fine in GCC, but Clang rejects it with the messages:

error: call to consteval function 'foo<char[4]>' is not a constant expression
note: in instantiation of function template specialization 'bar<char[4]>' requested here
   static_assert(bar("abc") == 0);
note: function parameter 't' with unknown value cannot be used in a constant expression
   auto n = foo(t);

Demo: https://gcc.godbolt.org/z/M6GPnYdqb

Is it some bug in Clang?

like image 384
Fedor Avatar asked Nov 30 '21 06:11

Fedor


People also ask

How do you call a nested function from a function?

On line 9 it calls the nested function within the func () function and then the nested function is executed. So in this manner, the nested function is called every time we call the func () function automatically because it is called inside the func () function.

What is the consteval specifier?

The consteval specifier declares a function or function template to be an immediate function, that is, every potentially evaluated call (i.e. call out of an unevaluated context) to the function must (directly or indirectly) produce a compile time constant expression .

When is the nested function called automatically in Python?

So in this manner, the nested function is called every time we call the func () function automatically because it is called inside the func () function. Python Closures or you can say nested function objects can be used to protect or filter some functionalities inside that function.

How many levels of nested functions can be in a formula?

Nesting level limits A formula can contain up to seven levels of nested functions. When one function (we'll call this Function B) is used as an argument in another function (we'll call this Function A), Function B acts as a second-level function.


Video Answer


1 Answers

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.

like image 76
Barry Avatar answered Oct 26 '22 19:10

Barry