Title
Immediate function evaluations in default arguments
Status
open
Section
7.7 [expr.const]
Submitter
Aaron Ballman

Created on 2022-09-16.00:00:00 last changed 5 days ago

Messages

Date: 2022-09-15.00:00:00

Proposed resolution (September, 2022):

  1. Split 9.3.4.7 [dcl.fct.default] paragraph 5 and change as follows:

    The default argument has the same semantic constraints as the initializer in a declaration of a variable of the parameter type, using the copy-initialization semantics (9.4 [dcl.init]).

    A default argument context is

    • the initializer-clause in a parameter-declaration, including any conversions to the parameter type,
    • a subexpression of one of the above that is not a subexpression of a nested unevaluated operand.
    The names in the default argument are looked up, and the semantic constraints are checked, at the point where the default argument appears, except that an immediate invocation (7.7 [expr.const]) in a default argument context is neither evaluated nor checked for whether it is a constant expression at that point. Name lookup and checking of semantic constraints for default arguments of templated functions are performed as described in 13.9.2 [temp.inst].

  2. Change in 13.9.2 [temp.inst] paragraph 11 as follows:

    ... The use of a template specialization in a default argument shall not cause the template to be implicitly instantiated except that a class template or a function template with a deduced return type may be instantiated where its complete type is needed to determine the correctness of the default argument. The use of a default argument in a function call causes specializations in the default argument to be implicitly instantiated.
Date: 2022-09-24.20:18:35

Consider:

consteval int const_div(int a, int b) { return a / b; }
int func(int x = const_div(10, 0));

According to 7.7 [expr.const] paragraph 14:

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.

Subclause 9.3.4.7 [dcl.fct.default] paragraph 5 specifies:

The default argument has the same semantic constraints as the initializer in a declaration of a variable of the parameter type, using the copy-initialization semantics (9.4 [dcl.init]). The names in the default argument are looked up, and the semantic constraints are checked, at the point where the default argument appears. Name lookup and checking of semantic constraints for default arguments of templated functions are performed as described in 13.9.2 [temp.inst].

Checking the semantic constraints of the default argument appears to include a check whether the immediate invocation of const_div is actually a constant expression, even though the default argument's value might never actually be used for any function call in the program.

However, instantiation of a consteval function template to be able to perform the constant evaluation is not permitted per 13.9.2 [temp.inst] paragraph 11:

... The use of a template specialization in a default argument shall not cause the template to be implicitly instantiated except that a class template may be instantiated where its complete type is needed to determine the correctness of the default argument. The use of a default argument in a function call causes specializations in the default argument to be implicitly instantiated.

Example 2:

constexpr int g();
consteval int f() {
  return g();
}

int k(int x = f()) {  // error: constexpr evaluation of undefined function g
  return x;
}

constexpr int g() {
  return 42;
}

int main() {
  return k();
}

Example 3:

#include <source_location>
#include <iostream>

consteval int const_div(int a, int b) {
  return a / b;
}

#line 5
void foo(int x = const_div(1000, std::source_location::current().line() - 15)) {
  std::cout << x << "\n";
}

// Should the definition of `bar` produce errors? (division by zero during constant
// evaluation for constraint checking)
#line 10
void bar(int x = const_div(1000, std::source_location::current().line() - 10)) {
  std::cout << x << "\n";
}

int main() {
  // Should this call produce errors? (division by zero during constant
  // evaluation of the default argument)
  #line 15
  foo();
  #line 20
  bar();
}

Note that source_location::current() is specified to take its value from the location where it is evaluated, if it appears in a default argument (17.9.2.2 [support.srcloc.cons] paragraph 2):

Remarks: Any call to current that appears as a default member initializer (11.4 [class.mem]), or as a subexpression thereof, should correspond to the location of the constructor definition or aggregate initialization that uses the default member initializer. Any call to current that appears as a default argument (9.3.4.7 [dcl.fct.default]), or as a subexpression thereof, should correspond to the location of the invocation of the function that uses the default argument (7.6.1.3 [expr.call]).
History
Date User Action Args
2022-09-24 20:18:35adminsetmessages: + msg6943
2022-09-16 00:00:00admincreate