Does the default argument for a function parameter is considered as an initializer for that parameter?
Asked Answered
Y

1

3

Suppose I have function declarations like these:

static const int R = 0;
static const int I = 0;

void f(const int& r = R);
void g(int i = I);

Per [dcl.fct.default]/1:

If an initializer-clause is specified in a parameter-declaration this initializer-clause is used as a default argument [..]

and per the grammar constructs, an initializer can comprise an initializer-clause. Right?

So I concluded that R is an initializer for the parameter r, and I is also an initializer for the parameter i.

Now per [const.expr]/2:

A variable or temporary object o is constant-initialized if

  • (2.1) either it has an initializer [..] and
  • (2.2) the full-expression of its initialization is a constant expression [..]

So both parameters have an initializer and also the full-expression of their initializations is a constant expression.

So, Are both parameters r and i considered constant-initialized?

Yellowgreen answered 21/9, 2022 at 12:53 Comment(3)
g()is equivalent to g(I) and so g(0).Radtke
@Radtke - What you're trying to say?Yellowgreen
Not sure what is your question... function parameters are never constexpr, and the there is the above equivalence.Radtke
E
1

No, because an initializer-clause is not necessarily part of an initializer. The grammar item in question here is this version of the parameter-declaration:

attribute-specifier-seqopt thisopt decl-specifier-seq declarator = initializer-clause

Applying just the right amount of hair-splitting, this means that the form of a parameter declaration with a default argument is similar to an initialization, but it is still subtly different. The most obvious difference being that the default argument is ignored if an actual argument is used. Less obvious is that a parameter cannot actually be constant-initialized, even if its default argument is a constant expression. This remains true even if the function is only evaluated at compile time. Here is some code that shows the subtle differences in meaning:

#include <random>

// R may be evaluated at compile time and must be constant-initialized
static constexpr int R = 0;

// f may be evaluated at compile time
constexpr int f(const int& r = R) { return r + 42; }

// I must be constant- *or* zero-initialized, even if only evaluated at runtime
constinit const int I = f();

// g is an "immediate function": it may not be evaluated at runtime
consteval int g(int i = I) { return i - 42; }

int main() {
    // A variable that may not appear in constant expressions,
    // because it is not constant-initialized
    const int not_const = std::rand();

    static int x1 = f();                // OK, constant initialization
    // constexpr int x2 = f(not_const); // error, not constant-initialized
    int x3 = f();                       // OK, f() evaluated at compile time
    int x4 = f(not_const);              // OK, evaluated at runtime

    static int y1 = g();                // OK
    // constexpr int y2 = g(not_const); // error
    int y3 = g();                       // OK
    // int y4 = g(not_const);           // error
}

As you can see here, this generates only one runtime call to f(int const&) and none to g(int). Even though the parameters for the other evaluations were effectively compile-time constants, you can neither use nor specify them as such:

constexpr int f(const int& r = R) {
    // constexpr int x = r; // error, not a constant expression
    return r + 42;
}

consteval int g(int i = I) {
    // constexpr int y = i; // still not a constant expression!
    return i - 42; 
}

// Also not allowed:
// consteval void h(constexpr int x = 0, constinit int y = 1); 
Emden answered 24/9, 2022 at 14:23 Comment(6)
Upon posting this answer, I got confused about the exact meaning of "constant initialization", which I've always associated with static/thread_local variables. The present wording does seem to indicate that x3 and y3 (but not x4) are constant-initialized, though this initialization does not occur at compile time. Their initializers are still evaluated at compile time, however.Emden
static int x2 = f(not_const); is valid... did you mean constexpr int x2 = f(not_const);?Radtke
@Radtke Yes I did!Emden
BTW, x1, x3 are similar, and comment is wrong, unless there are also constexpr.Radtke
So, the answer for Are both parameters r and i considered constant-initialized? is No?Yellowgreen
@std Indeed, because they do not have initializers in the proper sense, thereby failing to meet the first requirement of the quoted section of [expr.const].Emden

© 2022 - 2024 — McMap. All rights reserved.