非静态成员作为非静态成员函数的默认参数
struct X
{
X():mem(42){}
void f(int param = mem) //ERROR
{
//do something
}
private:
int mem;
};
谁能给我一个理由来说明为什么这在 C++ 中是非法的?!也就是说,我知道这是一个错误,我知道这个错误意味着什么,我只是不明白为什么这是非法的!
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!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
正如 DeadMG 上面提到的,
出于同样的原因,类似的东西也是非法的。然而,我认为这不仅仅是一个愚蠢的限制。为了允许这样的构造,我们需要限制函数参数的求值顺序(因为我们需要在 this->mem 之前计算它),但 C++ 标准明确拒绝对求值顺序的任何假设。
As DeadMG has mentioned above, somethig like
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.
重复问题中接受的答案是为什么,但标准也明确说明了为什么会这样:
8.3.6/9:
“
示例:以下示例中的 X::mem1() 声明格式不正确,因为没有为用作初始值设定项的非静态成员 X::a 提供对象。
然而,X::mem2() 的声明是有意义的,因为不需要任何对象来访问静态成员 X::b。类、对象和成员在第 9 节中描述。
"
...并且由于此时不存在语法来提供解析
X::a
值所需的对象,因此实际上不可能使用非静态成员变量作为默认参数的初始值设定项。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.
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++ 第 8.3.6/9 节
另请查看该部分中给出的示例。
ISO C++ section 8.3.6/9
Also check out the example given in that section.
原因之一是,
f
是公共的,但mem
是私有的。因此,像这样的代码:...将涉及检索 X 的私有数据的外部代码。
除此之外,它还会(或至少可能)使代码生成变得有点棘手。通常,如果编译器要使用默认参数,它会获取将作为函数声明的一部分传递的值。生成代码以将该值作为参数传递是很简单的。当您可能传递对象的成员(可能任意深度嵌套),然后添加诸如它可能是模板中的从属名称之类的内容时,这可能(例如)通过转换为正确的目标来命名另一个对象类型,并且您有一个使代码生成变得相当困难的秘诀。我不确定,但我怀疑有人考虑过类似的事情,并认为最好保持保守,并且可能稍后开放,如果有充分的理由这样做的话。考虑到我已经多次看到它出现问题,我猜它会在很长一段时间内保持这种状态,因为它很少引起问题。
For one reason, because
f
is public, butmem
is private. As such, code like this:...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.
由于所有其他答案都只是讨论这个问题,我想我会发布一个解决方案。
正如在没有默认参数的其他语言中使用的那样(例如 C# 4.0 之前的版本),
只需使用重载即可提供相同的结果:
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:
默认参数在不同的上下文中通过两个不同的步骤进行评估。
首先,在声明的上下文中执行默认参数的名称查找。
其次,默认参数的计算是在实际函数调用的上下文中执行的。
为了防止实现变得过于复杂,对可用作默认参数的表达式应用了一些限制。
this->
限定,而该限定通常无法在调用站点进行评估。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.
this->
qualification, which can typically not be evaluated at the call site.您的代码(简化):
您想要使用非静态成员数据作为成员函数参数的默认值。我想到的第一个问题是:默认值
mem
属于类的哪个特定实例?您的答案可能是
100
,因为f()
是在具有mem = 100
的对象x1
上调用的。如果是这样,那么它需要实现将f()
实现为:这又要求在初始化其他参数之前首先初始化第一个参数。但 C++ 标准没有指定函数参数的任何初始化顺序。因此这是不允许的。出于同样的原因,C++ 标准甚至不允许这样做:
事实上,§8.3.6/9 明确指出,
本节的其余部分读起来很有趣。
与“默认”参数相关的有趣主题(尽管与本主题无关):
Your code (simplified):
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?Your answer might be
100
asf()
is invoked on the objectx1
which hasmem = 100
. If so, then it requires the implementation to implementf()
as: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:
In fact, §8.3.6/9 explicitly says,
And rest of the section is an interesting read.
An interesting topic related to "default" arguments (not related to this topic though):
默认参数必须在编译时已知。当您谈论函数调用之类的内容时,即使返回值不是,该函数在编译时也是已知的,因此编译器可以生成该代码,但是当您默认为成员变量时,编译器不会生成该代码。不知道在编译时在哪里找到该实例,这意味着它实际上必须传递一个参数(
this
)来查找内存。请注意,您不能执行类似void func(int i, int f = g(i));
的操作,并且这两者实际上是相同的限制。我也认为这个限制很愚蠢。但是,C++ 充满了愚蠢的限制。
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 likevoid 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.