Nested call of consteval functions with a reference argument
Asked Answered
S

1

8

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?

Siddra answered 30/11, 2021 at 6:56 Comment(5)
Note: this compiler variance does not only show up for reference arguments, but also for pointers.Telegram
Surprise, I change consteval auto bar(const T& t) into consteval auto bar(T t) then it compiles for clang. Hope someone can have this clarified.Fluidics
@SHP, this might be related: Why is a constexpr function on a reference not constexpr?Tart
@Evg: Notice that replacing consteval by constexpr works Demo.Calvinism
This is a duplicate of stackoverflow.com/questions/69166564 However, this question is better phrased (clearer title, and clearer code snippet). Also, the answer on that duplicate is not particularly useful, so I'm going to go ahead and use this question as a target once (if) it's answered.Beaird
T
6

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.

Toitoiboid answered 30/11, 2021 at 18:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.