Nonstatic member as a default argument of a nonstatic member function [duplicate]
Asked Answered
L

9

36
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!

Leadsman answered 27/12, 2010 at 14:42 Comment(7)
@marcog: Although I may agree this is somewhat related, but I believe this is not a duplicate at all...Leadsman
@Armen The accepted answer there answers your question somewhat: the compiler doesn't know about the instance when parsing the default argument.Glia
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.Potiche
@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.Monatomic
(note for the reader: the above comment refers to the now (unfortunately) deleted question - #6363374 ). 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(); }.Monatomic
Update: @user396672 provides an insightful language-wise reason.Monatomic
Same question for C++17: Using a non static value as default argument in a function.Followup
Q
49

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):

Quechuan answered 15/6, 2011 at 20:7 Comment(4)
Accepted for tidy and logical explanation of @user396672's answerLeadsman
@Armen: I added one link to another topic. :DQuechuan
But it doesn't have anything to do with the question in question, does it? :)Leadsman
If you turn the implementation (which is just an possible implementation) to void f(X* this = self, int param = self->mem);, evaluation order is not important.Alchemy
R
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.

Romine answered 27/12, 2010 at 15:5 Comment(11)
+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 timeStarve
-1 for "Default arguments have to be known at compile-time". This is not true.Quechuan
Oops. I can't give downvote, as I already have given my upvote in December (2010). I was wrong then!Quechuan
@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))Leadsman
@Armen: I don't think that is real answer either.Quechuan
@Nawaz: Please feel free to provide a more convincing answer and I'll happily accept itLeadsman
I think @Starve has convinced me. It would require evaluating the object expression before any default arguments.Monatomic
@Armen: Ohh.. I read that again, now the complete answer. I agree the reasoning. I was also thinking the same. Upvoted that.Quechuan
@Nawaz: I think you'll find that I explicitly stated that "compile-time" did not mean "constexpr", and even gave an exampleRomine
@DeadMG: What does that mean?Quechuan
@Armen: I posted my answer anyway, since I had already written half of it. So thought its better to post than to erase it.Quechuan
S
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.

Starve answered 27/12, 2010 at 16:45 Comment(0)
Y
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.

Yoko answered 27/12, 2010 at 18:25 Comment(0)
A
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.

Aversion answered 27/12, 2010 at 14:50 Comment(1)
No, default parameters may not be known compile time. For example, void f(int x = g()) where g is a global function is OKLeadsman
I
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.

Incandescent answered 27/12, 2010 at 15:0 Comment(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..." <- searched for after checking my particular compiler.Yoko
J
1

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

Jacquenette answered 27/12, 2010 at 15:0 Comment(2)
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.Starve
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.Jacquenette
P
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;
};
Paphos answered 9/1, 2012 at 1:54 Comment(0)
A
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.
Adaptive answered 27/12, 2010 at 17:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.