34
struct X
{
   X():mem(42){}
   void f(int param = mem) //ERROR
   {
      //do something
   }
private: 
   int mem;
};

Can anyone give me just one reason as to why this is illegal in C++?! That is to say, I know that it is an error, I know what the error means, I just can't understand why would this be illegal!

Bo Persson
  • 86,087
  • 31
  • 138
  • 198
Armen Tsirunyan
  • 120,726
  • 52
  • 304
  • 418
  • possible duplicate of [c++ publicly inherited class member cannot be used as default argument](http://stackoverflow.com/questions/2159538/c-publicly-inherited-class-member-cannot-be-used-as-default-argument) – moinudin Dec 27 '10 at 14:48
  • 1
    @marcog: Although I may agree this is somewhat related, but I believe this is not a duplicate at all... – Armen Tsirunyan Dec 27 '10 at 14:49
  • @Armen The accepted answer there answers your question somewhat: the compiler doesn't know about the instance when parsing the default argument. – moinudin Dec 27 '10 at 14:54
  • Doesn't work for static member functions, horribly ambiguous for instance methods since data members can't be virtual. And the workaround is trivial with an overload. – Hans Passant Dec 27 '10 at 16:19
  • @Armen I think it's reasonable to expect it to work, and I can't see a language-wise reason. "Default arguments need to be known at compile time" isn't a reason in my opinion. In the above code, the default argument *is* known at compile time - it is an invocation of `.size()` of class `std::string`, of the member `some_member_variable`. This is all that is needed. Overload resolution is done without taking default arguments into account (otherwise we would have circular dependency). So by the time we substitute the default argument, I think we know what object we need to touch the member of. – Johannes Schaub - litb Jun 15 '11 at 19:11
  • (note for the reader: the above comment refers to the now (unfortunately) deleted question - http://stackoverflow.com/questions/6362874/why-cant-i-use-a-non-static-class-member-as-default-argument-to-a-member-funct ). It was the same question, just a different example code. Note that default arguments are not read until the call - for example, the following calls `f` with default arguments `0` and `1` respectively: `int i; void f(int j = i) { } void g() { i = 0; f(); i = 1; f(); }`. – Johannes Schaub - litb Jun 15 '11 at 19:13
  • 1
    Update: @user396672 provides an insightful language-wise reason. – Johannes Schaub - litb Jun 15 '11 at 19:32
  • Same question for C++17: [Using a non static value as default argument in a function](https://stackoverflow.com/questions/57125504/using-a-non-static-value-as-default-argument-in-a-function). – gsamaras Jul 20 '19 at 14:29

9 Answers9

46

Your code (simplified):

struct X
{
   int mem;
   void f(int param = mem); //ERROR
};

You want to use a non-static member data as default value for a parameter of a member function. The first question which comes to mind is this : which specific instance of the class the default value mem belongs to?

X x1 = {100};  //mem = 100
X x2 = {200};  //mem = 200

x1.f(); //param is 100 or 200? or something else?

Your answer might be 100 as f() is invoked on the object x1 which has mem = 100. If so, then it requires the implementation to implement f() as:

void f(X* this, int param = this->mem);

which in turn requires the first argument to be initialized first before initialization of other argument. But the C++ standard doesn't specify any initialization order of the function arguments. Hence that isn't allowed. Its for the same reason that C++ Standard doesn't allow even this:

int f(int a, int b = a); //§8.3.6/9

In fact, §8.3.6/9 explicitly says,

Default arguments are evaluated each time the function is called. The order of evaluation of function arguments is unspecified. Consequently, parameters of a function shall not be used in default argument expressions, even if they are not evaluated.

And rest of the section is an interesting read.


An interesting topic related to "default" arguments (not related to this topic though):

Community
  • 1
  • 1
Nawaz
  • 327,095
  • 105
  • 629
  • 812
7

Default arguments have to be known at compile-time. When you talk about something like a function invocation, then the function is known at compile-time, even if the return value isn't, so the compiler can generate that code, but when you default to a member variable, the compiler doesn't know where to find that instance at compile-time, meaning that it would effectively have to pass a parameter (this) to find mem. Notice that you can't do something like void func(int i, int f = g(i)); and the two are effectively the same restriction.

I also think that this restriction is silly. But then, C++ is full of silly restrictions.

Puppy
  • 138,897
  • 33
  • 232
  • 446
  • +1 for the remark that the question is not actually class- or even OO-related. However,I suppose the restriction concerns evaluation context rather than evaluation time – user396672 Dec 27 '10 at 17:22
  • 3
    -1 for `"Default arguments have to be known at compile-time"`. This is not true. – Nawaz Jun 15 '11 at 19:09
  • Oops. I can't give downvote, as I already have given my upvote in December (2010). I was wrong then! – Nawaz Jun 15 '11 at 19:11
  • @Nawaz: Actually, I don't see why I accepted THIS answer... The real answer is by user396672. Maybe because DeadMG pointed to a relevant example of `void f(int i , int j = g(i))` – Armen Tsirunyan Jun 15 '11 at 19:12
  • @Armen: I don't think that is real answer either. – Nawaz Jun 15 '11 at 19:14
  • @Nawaz: Please feel free to provide a more convincing answer and I'll happily accept it – Armen Tsirunyan Jun 15 '11 at 19:15
  • 1
    I think @user396672 has convinced me. It would require evaluating the object expression before any default arguments. – Johannes Schaub - litb Jun 15 '11 at 19:30
  • 1
    @Armen: Ohh.. I read that again, now the complete answer. I agree the reasoning. I was also thinking the same. Upvoted that. – Nawaz Jun 15 '11 at 19:36
  • @Nawaz: I think you'll find that I explicitly stated that "compile-time" did not mean "constexpr", and even gave an example – Puppy Jun 15 '11 at 19:53
  • @DeadMG: What does that mean? – Nawaz Jun 15 '11 at 19:58
  • @Armen: I posted my answer anyway, since I had already written half of it. So thought its better to post than to erase it. – Nawaz Jun 15 '11 at 20:08
5

As DeadMG has mentioned above, somethig like

void func(int i, int f = g(i))

is illegal for the same reason. i suppose, however, that it is not simply a silly restriction. To allow such a construction, we need to restrict evaluation order for function parameters (as we need to calculate this before this->mem), but the c++ standard explicitly declines any assumptions on the evaluation order.

user396672
  • 2,986
  • 16
  • 29
2

The accepted answer in the duplicate question is why, but the standard also explicitly states why this is so:

8.3.6/9:

" Example: the declaration of X::mem1() in the following example is ill-formed because no object is supplied for the nonstatic member X::a used as an initializer.

int b;
class X
  int a;
  int mem1(int i = a);    // error: nonstatic member a
                          // used as default argument
  int mem2(int i = b);    // OK: use X::b
  static int b;
};

The declaration of X::mem2() is meaningful, however, since no object is needed to access the static member X::b. Classes, objects and members are described in clause 9. "

... and since there exists no syntax to supply the object necessary to resolve the value of X::a at that point, it's effectively impossible to use non-static member variables as initializers for default arguments.

Edward Strange
  • 38,861
  • 7
  • 65
  • 123
1

ISO C++ section 8.3.6/9

a nonstatic member shall not be used in a default argument expression, even if it is not evaluated, unless it appears as the id-expression of a class member access expression (5.2.5) or unless it is used to form a pointer to member (5.3.1).

Also check out the example given in that section.

Prasoon Saurav
  • 85,400
  • 43
  • 231
  • 337
1

For one reason, because f is public, but mem is private. As such, code like this:

int main() { 
    X x;
    x.f();
    return 0;
}

...would involve outside code retrieving X's private data.

Aside from that, it would (or at least could) also make code generation a bit tricky. Normally, if the compiler is going to use a default argument, it gets the value it's going to pass as part of the function declaration. Generating code to pass that value as a parameter is trivial. When you might be passing a member of an object (possibly nested arbitrarily deeply) and then add in things like the possibility of it being a dependent name in a template, that might (for example) name another object with a conversion to the correct target type, and you have a recipe for making code generation pretty difficult. I don't know for sure, but I suspect somebody thought about things like that, and decided it was better to stay conservative, and possibly open thins up later, if a good reason was found to do so. Given the number of times I've seen problems arise from it, I'd guess it'll stay the way it is for a long time, simply because it rarely causes problems.

Jerry Coffin
  • 437,173
  • 71
  • 570
  • 1,035
  • 1
    This is wrong. 11/7 - "The names in a default argument expression (8.3.6) are bound at the point of declaration, and access is checked at that point rather than at any points of use..." – Edward Strange Dec 27 '10 at 18:12
1

Compiler has to know addresses to maintain default values at compile time. Addresses of non-static member variables are unknown at compile time.

Vladimir
  • 1,707
  • 12
  • 12
  • 1
    int add_random(int k, int m=rand()){ return k+ m;} is legal although neither random value nor it address is known at compile time. Enum default value may not have address at all. – user396672 Dec 27 '10 at 17:40
  • 1
    Are you sure? I mean, every time when I start an executable, parameter m (which you defined as: m = rand() ) has the same value on my machine. It seems like it is defined once (at compile time), and it doesn't change the value at all. Enums are constants - their values are known at compile time. – Vladimir Dec 27 '10 at 21:52
1

As all the other answers just discuss the problem, I thought I would post a solution.

As used in other languages without default arguments (Eg C# pre 4.0)

Simply use overloading to provide the same result:

struct X
{
   X():mem(42){}
   void f(int param)
   {
      //do something
   }
   void f()
   {
      f(mem);
   }
private: 
   int mem;
};
Spencer Rose
  • 1,120
  • 11
  • 21
0

Default arguments are evaluated in two distinct steps, in different contexts.
First, the name lookup for the default argument is performed in the context of the declaration.
Secondly, the evaluation of the default argument is performed in the context of the actual function call.

To keep the implementation from becoming overly complicated, some restrictions are applied to the expressions that can be used as default arguments.

  • Variables with non-static lifetime can't be used, because they might not exist at the time of the call.
  • Non-static member variables can't be used, because they need an (implicit) this-> qualification, which can typically not be evaluated at the call site.
Bart van Ingen Schenau
  • 14,464
  • 4
  • 29
  • 40