Question

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!

Was it helpful?

Solution

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

OTHER TIPS

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.

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.

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.

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.

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.

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

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;
};

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.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top