Nonstatic member as a default argument of a nonstatic member function [duplicate] - c++

This question already has answers here:
How to use a member variable as a default argument in C++?
(4 answers)
Closed 1 year ago.
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!

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):
Default argument in the middle of parameter list?

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.

Related

Can a friend function in C++ have a default argument whose type has a private destructor?

In the next example the class U with private destructor has a friend function foo. And this friend function has argument of type U with default value U{}:
class U{ ~U(); friend void foo(U); };
void foo(U = {});
Clang and MSVC accept this code, but GCC rejects it with the error
error: 'U::~U()' is private within this context
2 | void foo(U = {});
| ^
Demo: https://gcc.godbolt.org/z/eGxYGdzj3
Which compiler is right here, and does friendship extend on default arguments in C++?
C++20 [class.access]/8 provides as follows:
The names in a default argument (9.3.3.6) are bound at the point of declaration, and access is checked at that point rather than at any points of use of the default argument. Access checking for default arguments in function templates and in member functions of class templates is performed as described in 13.9.1.
However, [expr.call]/8 says:
... The initialization and destruction of each parameter
occurs within the context of the calling function. [Example: The access of the constructor, conversion functions or destructor is checked at the point of call in the calling function. ...
While the "Example" text is not normative, I believe it reflects the intent; therefore, in order to read these two provisions harmoniously, we should understand that the destructor of the type of the default argument is (in my opinion, at least) not a name "in a default argument". Instead, we should view the call to the friend function as occurring in the following stages:
The default argument initializer is evaluated. Due to [class.access]/8, access control during this step is done from the context of the declaration.
The parameter is copy-initialized from the result of step 1. Due to [expr.call]/8, access control during this step is done from the context of the calling function.
The function body is evaluated.
The parameter is destroyed. Again, access control is done from the context of the calling function (irrelevant note: when exactly the destruction happens is not completely specified).
GCC shouldn't be rejecting the declaration void foo(U = {}) as there is no actual use of the destructor yet; and indeed, it is possible that foo might be called only from contexts that have access to U::~U. But if foo is called from a context that doesn't have access to U::~U, the program should be ill-formed. In such cases, I think that Clang and MSVC are wrong, because they still accept the code.
However, there is also the issue of [dcl.fct.default]/5 which states:
The default argument has the same semantic constraints as the initializer in a declaration of a variable of the parameter type, using the copy-initialization semantics (9.4). The names in the default argument are bound, and the semantic constraints are checked, at the point where the default argument appears. ...
The standard never defines what it means by "semantic constraints"; if it's assumed to include access control for both the initialization and destruction, then that might explain why Clang and MSVC seem to allow calls to foo from contexts that ought not to have access to U::~U.
But thinking about this more, I feel that this doesn't make too much sense, because it would imply that default arguments are "special" in a way that I don't think was intended. To wit, consider:
class U {
public:
U() = default;
U(const U&) = default;
private:
~U() = default;
friend void foo(U);
};
void foo(U = {}) {}
int main() {
auto p = new U();
foo(*p); // line 1
foo(); // line 2
}
Here, MSVC accepts both lines 1 and 2; it seems clearly wrong to accept line 1, considering how [expr.call]/8 requires the destructor to be accessible from main. But Clang accepts line 2 and rejects line 1, which also seems absurd to me: I don't feel that the intent of the standard was that choosing to use the default argument (as opposed to providing the argument yourself) would exempt the caller from having to have access to the destructor of the parameter type.
If [dcl.fct.default]/5 appears to require Clang's behaviour, then I believe that it should be considered defective.

Can a C++ default argument be initialized with another argument? [duplicate]

This question already has answers here:
Can I set a default argument from a previous argument?
(7 answers)
Closed 5 years ago.
For a default argument in C++, does the value need to be a constant or will another argument do?
That is, can the following work?
RateLimiter(unsigned double rateInPermitsPerSecond,
unsigned int maxAccumulatedPermits = rateInPermitsPerSecond);
Currently I am getting an error:
RateLimiter.h:13: error: ‘rateInPermitsPerSecond’ was not declared in this scope
Another argument cannot be used as the default value. The standard states:
8.3.6 Default arguments...
9 A default argument is evaluated each time the function is called with no argument for the corresponding
parameter. The order of evaluation of function arguments is unspecified. Consequently, parameters of a
function shall not be used in a default argument, even if they are not evaluated.
and illustrates it with the following sample:
int f(int a, int b = a); // error: parameter a
// used as default argument
No, that cannot work because the evaluation of function arguments is not sequenced. It also does not work because the standard does not allow it, but I guess that was obvious.
Use an overload instead:
void fun(int, int) {}
void fun(int i) {
fun(i, i);
}
I was looking for an logical explanation for why it is not allowed
This is actually a good question. The reason is that C++ does not mandate the order of evaluation of arguments.
So let's imagine a slightly more complex scenario:
int f(int a, int b = ++a);
... followed by ...
int a = 1;
f(a);
C++ does not mandate the order of evaluation of arguments, remember?
So what should be the value of b?
f(a) can evaluate to either:
f(1, 2), or
f(2, 2), depending on the order of evaluation of the arguments.
Thus the behaviour would be undefined (and even undefinable).
Further, consider what might happen when a and b were complex objects whose constructors and copy operators had side-effects.
The order of those side-effects would be undefined.
You cannot do things like that because the standard does not allow it. However since default arguments effectively just define new function overloads, you can get the desired effect by explicitly defining such an overload:
void RateLimiter(unsigned int rateInPermitsPerSecond,
unsigned int maxAccumulatedPermits);
inline void RateLimiter(unsigned int rateInPermitsPerSecond)
{ return RateLimiter(rateInPermitsPerSecond,rateInPermitsPerSecond); }
This shows that the standard forbidding this is half-hearted, as suggested by the language ("Consequently... shall not..."). They just did not want to go through the hassle of making this well defined with the same effect as what the explicit overload declaration would do: if desired, they could have specified that defaulted arguments are evaluated after explicitly provided ones, and from left to right. This would not have any influence on the rule that the evaluation order of argument expressions in a function call is unspecified (because default arguments do not correspond to such expressions; they are entirely separate and not even in the same lexical scope). On the other hand if (as they did) they preferred to disallow this, they could have just said "shall not" without need to justify themselves from some other rule (but maybe with explanatory footnote).
For a default argument in C++, does the value need to be a constant or will another argument do?
The default value of an argument cannot be another argument. However, that does not mean it has to be a constant. It can be the return value of a function call.
int getNextDefaultID()
{
static int id = 0;
return ++id;
}
struct Foo
{
Foo(int data, int id = getNextDefaultID()) : data_(data), id_(id) {}
int data_;
int id_;
};
int main()
{
Foo f1(10); // Gets the next default ID.
Foo f2(20, 999); // ID is specified.
}

C++ static const access through a NULL pointer [duplicate]

This question already has answers here:
c++ access static members using null pointer
(5 answers)
Closed 8 years ago.
class Foo {
public:
static const int kType = 42;
};
void Func() {
Foo *bar = NULL;
int x = bar->kType;
putc(x, stderr);
}
Is this defined behavior? I read through the C++ standard but couldn't find anything about accessing a static const value like this... I've examined the assembly produced by GCC 4.2, Clang++, and Visual Studio 2010 and none of them perform a dereference of the NULL pointer, but I'd like to be sure.
You can use a pointer (or other expression) to access a static member; however, doing so through a NULL pointer unfortunately is officially undefined behavior. From 9.4/2 "Static members":
A static member s of class X may be
referred to using the qualified-id
expression X::s; it is not necessary
to use the class member access syntax
(5.2.5) to refer to a static member. A
static member may be referred to using
the class member access syntax, in
which case the object-expression is
evaluated.
Based on the example that follows:
class process {
public:
static void reschedule();
};
process& g();
void f()
{
process::reschedule(); // OK: no object necessary
g().reschedule(); // g() is called
}
The intent is to allow you to ensure that functions will be called in this scenario.
I believe that the actual value of the type is not used at all when calling
bar->kType
since kType is static, and bar is of type Foo it is the same as calling
Foo::kType
which you should really be doing anyway for clarity.
Calling bar->kType gives a compiler warning on most platforms for this reason.
Apart from the issue about accessing through the NULL pointer, there is another subtle issue in the code
$9.4.2/2 - "The declaration of a static data member in its class definition is not a definition and may be of an incomplete type other than cv-qualified void. The definition for a static data member shall appear in a namespace scope enclosing the member’s class definition."
$9.4.2/4- "If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19). In that case, the member can appear in integral constant expressions. The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer."
class Foo {
public:
static const int kType = 42;
};
int const Foo::kType;
void Func() {
Foo *bar = NULL;
int x = bar->kType;
putc(x, stderr);
}
So, yet one more reason for UB in the OP code.
Even if it worked it is awful code.
In serious programming you code not only for yourself, but also for others who will maintain your code.
Playing tricks like this must be avoided, because you respect your colleagues.
One consequence of this code: whether the pointer is NULL or not is even not at question, but it implies that this member kType may not be a plain non-static member of the class. Sometimes classes are big (this is evil too) and one cannot always recheck the definition of each and every variable.
Be rigorous. And call all your static members only this way:
Foo::kType
Another possibility is to follow a coding convention that let know that the member is static, for example, a s_ prefix for all classes static members:
Foo::s_kType
There is a higher rule so to speak which basically says - don't even think about compiling things that are provably not used. Advanced template programming depends on this a lot, so even if it might be a bit gray-zonish when a compiler clearly sees that the result of a construct is not used it's just going to eliminate it. Especially when it's provably safe like in this case.
You may want to try a few variants if you want - like making pointer a param of a function, result of a function, leaving a pointer uninitialized (best chance for triggering compiler complaint), doing a straight cast of 0 (best chance of being conplaint-free).

C++ enum not properly recognized by compiler

Can anyone explain why the following code does not compile (on g++ (GCC) 3.2.3 20030502 (Red Hat Linux 3.2.3-49))?
struct X {
public:
enum State { A, B, C };
X(State s) {}
};
int main()
{
X(X::A);
}
The message I get is:
jjj.cpp: In function 'int main()':
jjj.cpp:10: 'X X::A' is not a static member of 'struct X'
jjj.cpp:10: no matching function for call to 'X::X()'
jjj.cpp:1: candidates are: X::X(const X&)
jjj.cpp:5: X::X(X::State)`
Is this bad code or a compiler bug?
Problem solved by Neil+Konrad. See the comments to Neil's answer below.
You've forgot the variable name in your definition:
int main()
{
X my_x(X::A);
}
Your code confuses the compiler because syntactically it can't distinguish this from a function declaration (returning X and passing X::A as an argument). When in doubt, the C++ compiler always disambiguates in favour of a declaration.
The solution is to introduce redundant parentheses around the X since the compiler forbids parentheses around types (as opposed to constructo calls etc.):
(X(X::A));
X(X::A);
is being seen a s a function declaration. If you really want this code, use:
(X)(X::A);
Just to make it crystal clear what happens. Look at this example
int main() {
float a = 0;
{
int(a); // no-op?
a = 1;
}
cout << a;
}
What will it output? Well, it will output 0. The int(a) of above can be parsed in two different ways:
Cast to int and discard the result
Declare a variable called a. But ignore the parentheses around the identifier.
The compiler, when such a situation appears where a function-style cast is used in a statement and it looks like a declaration too, will always take it as a declaration. When it can't syntactically be a declaration (the compiler will look at the whole line to determine that), it will be taken to be an expression. Thus we are assigning to the inner a above, leaving the outer a at zero.
Now, your case is exactly that. You are trying (accidentally) to declare an identifier called A within a class called X:
X (X::A); // parsed as X X::A;
The compiler then goes on to moan about a not declared default constructor, because the static, as it assumes it to be, is default constructed. But even if you had a default constructor for X, it of course is still wrong because neither A is a static member of X, nor a static of X can be defined/declared at block scope.
You can make it not look like a declaration by doing several things. First, you can paren the whole expression, which makes it not look like a declaration anymore. Or just paren the type that is cast to. Both of these disambiguations have been mentioned in other answers:
(X(X::A)); (X)(X::A)
There is a similar, but distinct ambiguity when you try to actually declare an object. Look at this example:
int main() {
float a = 0;
int b(int(a)); // object or function?
}
Because int(a) can be both the declaration of a parameter called a and the explicit conversion (cast) of the float-variable to an int, the compiler decides again that that is a declaration. Thus, we happen to declare a function called b, which takes an integer argument and returns an integer. There are several possibilities how to disambiguate that, based on the disambiguation of above:
int b((int(a))); int b((int)a);
You should declare an object as
X x(X::A);
Bug in your code.
Either of these two lines work for me:
X obj(X::A);
X obj2 = X(X::A);
As Neil Butterworth points out, X(X::A) is being treated as a function declaration. If you really want an anonymous object, (X)(X::A) will construct an X object and immediately delete it.
You could, of course, just do something like this:
int main()
{
// code
{
X temp(X::A);
}
// more code
}
This would be more readable and basically have the same effect.

C++ - Constructor overloading - private and public

Can you tell me why the following code is giving me the following error - call of overloaded "C(int)" is ambiguous
I would think that since C(char x) is private, only the C(float) ctor is visible from outside and that should be called by converting int to float.
But that's not the case.
class C
{
C(char x)
{
}
public:
C(float t)
{
}
};
int main()
{
C p(0);
}
This is discussed in "Effective C++" by Scott Meyer. The reason this is ambiguous is that they wanted to ensure that merely changing the visibility of a member wouldn't change the meaning of already-existing code elsewhere.
Otherwise, suppose your C class was in a header somewhere. If you had a private C(int) member, the code you present would call C(float). If, for some reason, the C(int) member was made public, the old code would suddenly call that member, even though neither the old code, nor the function it called had changed.
EDIT: More reasons:
Even worse, suppose you had the following 2 functions:
C A::foo()
{
return C(1.0);
}
C B::bar()
{
return C(1.0);
}
These two functions could call different functions depending on whether either foo or bar was declared as a friend of C, or whether A or B inherits from it. Having identical code call different functions is scary.
(That's probably not as well put as Scott Meyer's discussion, but that's the idea.)
0 is an int type. Because it can be implicitly cast to either a float or char equally, the call is ambiguous. Visibility is irrelevant for these purposes.
Either put 0.0, 0., or 0.0f, or get rid of the C(char) constructor entirely.
Edit: Relevant portion of the standard, section 13.3:
3) [...] But, once the candidate functions and argument lists have been identified, the selection of the best function is the same in all cases:
First, a subset of the candidate functions—those that have the proper number of arguments and meet certain other conditions—is selected to form a set of viable functions (13.3.2).
Then the best viable function is selected based on the implicit conversion sequences (13.3.3.1) needed to match each argument to the corresponding parameter of each viable function.
4) If a best viable function exists and is unique, overload resolution succeeds and produces it as the result. Otherwise overload resolution fails and the invocation is ill-formed. When overload resolution succeeds, and the best viable function is not accessible (clause 11) in the context in which it is used, the program is ill-formed.
Note that visibility is not part of the selection process.
I don't think that:
C p(0);
is being converted to:
C(float t)
you probably need to do:
C p(0.0f);