16

Can I declare a constexpr function in C++ before giving its definition?

Consider an example:

constexpr int foo(int);
constexpr int bar() { return foo(42); }
constexpr int foo(int) { return 1; }

static_assert(bar() == 1);

It is actually supported by all compilers, demo: https://gcc.godbolt.org/z/o4PThejso

But if one converts function foo in a template:

constexpr int foo(auto);
constexpr int bar() { return foo(42); }
constexpr int foo(auto) { return 1; }

static_assert(bar() == 1);

then Clang refuses to accept it, saying https://gcc.godbolt.org/z/EG7cG9KTM:

<source>:5:15: error: static_assert expression is not an integral constant expression
static_assert(bar() == 1);
              ^~~~~~~~~~
<source>:2:30: note: undefined function 'foo<int>' cannot be used in a constant expression
constexpr int bar() { return foo(42); }
                             ^
<source>:5:15: note: in call to 'bar()'
static_assert(bar() == 1);

Is it still a valid C++ code or a Clang bug?

  • Side note: This would make parsing C++ incredibly awkward, even compared to how it already is. Consider [this example](https://gcc.godbolt.org/z/91aWo6314). To parse `main` (that is, figure out its structure and create an AST), constant evaluation has to run on `f` in order to even figure out whether that mess is a template instantiation or a less-than 0 and a greater-than the global `x`. That part exists today. However, a forward declaration of `f` means that in order to even parse `main`, the code _below_ `main` must now already be parsed. This is normal for templates, but `main` is not. – chris Aug 01 '21 at 10:02
  • 1
    Interestingly enough, it works for [Clang too](https://gcc.godbolt.org/z/9fanof5YG) if you explicitly mention the argument type in `foo`. – Ruks Aug 01 '21 at 10:12
  • Dup of [Nested \`constexpr\` function calls before definition in a constant-expression context](https://stackoverflow.com/questions/37526366/nested-constexpr-function-calls-before-definition-in-a-constant-expression-con) – Language Lawyer Aug 01 '21 at 16:24
  • Passing the `42` into `bar` as an `int`-parameter leads to divergent experiences... – Deduplicator Aug 01 '21 at 21:03

2 Answers2

13

This is core issue 2166.

Section: 7.7 [expr.const] Status: drafting Submitter: Howard Hinnant Date: 2015-08-05

According to 7.7 [expr.const] bullet 2.3, an expression is a constant expression unless (among other reasons) it would evaluate

an invocation of an undefined constexpr function or an undefined constexpr constructor;

This does not address the question of the point at which a constexpr function must be defined. The intent, in order to allow mutually-recursive constexpr functions, was that the function must be defined prior to the outermost evaluation that eventually results in the invocation, but this is not clearly stated.

In other words, the standard is unclear on this situation, so each compiler is free to accept or reject the code as it sees fit.

In case of Clang, an auto parameter is implemented by turning the function into a template. That makes it more difficult for the compiler to maintain constexpr context. That is not to say it's impossible to implement (after all, GCC has no problem with it), it's just not a bug until 2166 is resolved.

Meanwhile you can get the code to compile by making bar() also a template, or removing auto from foo().

rustyx
  • 80,671
  • 25
  • 200
  • 267
3

This is CWG2166:

2166. Unclear meaning of “undefined constexpr function”

According to 7.7 [expr.const] bullet 2.3, an expression is a constant expression unless (among other reasons) it would evaluate

  • an invocation of an undefined constexpr function or an undefined constexpr constructor;

This does not address the question of the point at which a constexpr function must be defined. The intent, in order to allow mutually-recursive constexpr functions, was that the function must be defined prior to the outermost evaluation that eventually results in the invocation, but this is not clearly stated.

Language Lawyer
  • 3,378
  • 1
  • 12
  • 29