Why can member variables not be used as defaults for parameters? [duplicate]
Asked Answered
P

4

6

Possible Duplicate:
Nonstatic member as a default argument of a nonstatic member function

Correct me if I am wrong, but the way I think default parameters work is this:

When the compiler sees the function call, it starts pushing the parameters onto the stack. When it runs out of parameters, it will start pushing the defaults onto the stack until all required parameters are filled (I know this is a simplification, since parameters are actually pushed from right to left, so it will start with the defaults, but the idea is the same).

If this is true, why can't member variables be used as defaults? It seems to me that since the compiler is pushing them as usual at the call site, it should be able to resolve them just fine!

EDIT Since it seems by the answers my question was misunderstood, let me clarify. I know this is the case, and I know what is and isn't allowed by the language. My question is why did the language designers choose to not allow this, since it seems to naturally work.

Phonate answered 3/12, 2012 at 7:3 Comment(0)
S
4

The essence of what you are asking can be distilled into this simple example

void foo(int a, int b = a);

This is not allowed in C++. C++ does not allow default arguments to depend on other parameters.

Using class members as default arguments is just a particular case of the above, since class members are accessed through this pointer and this pointer is just another hidden parameter of each non-static member function.

So, the question is really why

void foo(int a, int b = a);

is not allowed.

One obvious potential reason to disallow this is that it would impose additional requirements on the order of argument evaluation. As you know, in C++ the order of function argument evaluation is unspecified - the compiler can evaluate arguments in any order. However, in order to support the above default argument functionality the compiler would have to make sure that a is evaluated before b. This feels like an excessive requirement, which restricts the typical freedom of evaluation order that we are used to seeing in C++.

Note that this

int a;

void foo(int b = a);

is allowed in C++. And, obviously, it does not exhibit the aforementioned order of evaluation issue.

Sollie answered 3/12, 2012 at 9:3 Comment(4)
In your example, this can indeed be a problem. But when a is known to be just an address, there is no problem imposing this, since &var has no side-affects at all. You can even evaluate it twice if you want (once for a and once for b), and then do it in any order you want.Phonate
@baruch: I'm not sure what you mean. If the default argument does not depend on run-time properties of other parameters, it is typically allowed by the language. Address of a member variable depends on the value of this, which is no different from my example.Sollie
I mean that since *this is just a pointer to the instance used to instantiate the call, It is really more like calling bar(&c, &c+4), which is totally legal. The compiler can evaluate in any order it wants. The second argument just happens to depend on the same value sent as the first argument, not on the first argument itself.Phonate
@baruch: Yes, but what if c is not a variable prepared in advance, but rather a result of function call, as in obtain_c_from_somewhere().bar()? Calling the function twice is not acceptable. The only solution in this case is to always make sure that obtain_c_from_somewhere() is called first (to obtain this) and the other arguments are prepared later. And that is exactly the additional requirements on the order of evaluation I'm talking about in my answer.Sollie
S
1

This post lists all ways a default param can be set - Must default function parameters be constant in C++?

It's not difficult to workaround your need.

class A
{
    int a;
    public:
    void f(int i);

    void f()
    {
        f(a);
    }
};

gives you what you want.

Skyline answered 3/12, 2012 at 7:9 Comment(7)
Yes, I know the rules. I am looking for the WHYPhonate
@baruch Updated my answer with the whySkyline
The name lookup happens at the deceleration site? Why? It seems to me (like I explained in my question) that the lookup should be at the call site. I guess this question is already getting to be to much of "why don't they do it my way". There probably is no clear answer except for that is how it is done, since I will probably keep asking "why" for every new level of explanation.Phonate
@baruch - to elaborate - the name lookup happens at compile time. At compile time itself the variable to be used is bound to the function call. The value of the variable is evaluated at runtime. Updated my answer to reflect this.Skyline
Sorry, doesn't make sense. this is very much known at the call site. At CPU level, it's just another parameter. To access this->member, a compiler would need only offset_of(member).Touchback
@Touchback Thinking more - it does make no sense. Will edit that out of my answer.Skyline
@Skyline As MSalters said, since when calling c.func() the compiler turns this into type::func(&c), then &c is known at the call site at compile time, so it should not be a problem to get (&c)->member` for the default parameter.Phonate
T
1

I believe this are the most fitting paragraphs from the standard, especially §9:

8.3.6 Default arguments [dcl.fct.default]

§7 Local variables shall not be used in a default argument

§9 [...] Similarly, a non-static member shall not be used in a default argument, 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).

Testudinal answered 3/12, 2012 at 7:12 Comment(1)
I know it is not allowed. I am trying to understand the logic behind it, since it seems to be going out-of-the-way to make it not work.Phonate
T
1

Summarizing Nawaz excellent answer in the linked question: The call to void Foo::Bar(int a = this->member) really means void Foo__Bar(Foo* this, int a = this->member). Obviously the second argument cannot be evaluated before the first, which violates a C++ axiom that compilers can evaluate arguments in whatever order they like.

Touchback answered 3/12, 2012 at 8:54 Comment(6)
It sounds right, so I am accepting. However, it still seems to not be totally correct. Since the compiler has the address of this at the call site at compile time, it will really be calling Foo:Bar(&c, &c+offset(member)) when calling c.Bar(), and can evaluate them in any order it wishes to.Phonate
Oh, it obviously could be made to work for that simple case. But what if c is not a pointer, but the return value of a function call? You don't want to call it twice.Touchback
Why does the first argument need to be evaluated before the first, can the 2nd argument not be accessed as offsetof(member) like you suggested in my answer?Skyline
@user93353: No, not until the first argument is evaluated (since it's an offset relative to that). And that means you've mandated an order of argument evaluation.Touchback
@Touchback No, it need not be. Assuming the call is c.Bar(), then it can be evaluated as c.(&c, offsetof(member)) - it doesn't matter what order they are done in. The first argument is &c - so the 2nd argument can be evaluated using thatSkyline
@user93353: the second argument under water is not offset_of(member) but *(member_type*)((char*)this + offsetof(member)). Remember, it's only a default and the caller may provide another expression with the same type. The callee has to handle both cases.Touchback

© 2022 - 2024 — McMap. All rights reserved.