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

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

Related

Multiple functions with the same name but their parameters are either constant or received by value or by reference

The title is a bit lengthy, but it's best explained by an example:
Suppose we have the following functions in C++:
void SomeFunction(int num) { //1
}
void SomeFunction(int& num) { //2
}
void SomeFunction(const int& num) { //3
}
void SomeFunction(const int num) { //4
}
All of these are called the same way:
SomeFunction(5);
or
int x = 5;
SomeFunction(x);
When I tried to compile the code, it rightfully says more than one instance of overloaded function "SomeFunction" matches the argument
My question is: Is there a way to tell the compiler which function I meant to call?
I asked my lecturer if it was possible, and she tried something along
SomeFunction< /*some text which I don't remember*/ >(x);
But it didn't work and she asked me to find out and tell her.
I also encounter this post:
How to define two functions with the same name and parameters, if one of them has a reference?
And it seems that 1 and 2 can't be written together, but what about 3 and 4? Can either one of those be called specifically?
1 and 4 have the same signature, so you'll need to drop one of those.
The other functions cannot be called directly, but you could add a template function that allows you to specify the desired parameter type:
template<class Arg>
void Call(void f(Arg), Arg arg)
{
f(arg);
}
// Driver Program to test above functions
int main()
{
int i;
Call<int>(SomeFunction, 1);
Call<int&>(SomeFunction, i);
Call<const int&>(SomeFunction, 1);
}
Alternatively you could use a function pointer to choose the signature.
int i;
static_cast<void(*)(int)>(&SomeFunction)(1);
static_cast<void(*)(int&)>(&SomeFunction)(i);
static_cast<void(*)(const int&)>(&SomeFunction)(1);
It would be preferrable to avoid this scenario though and only define overloads for either references or the signature void SomeFunction(int).
Note:
SomeFunction<some text which I don't remember>(x);
only works for template functions and SomeFunction is not a template function, so this is not an option here.
You can HACK it, and I mean it - it's not a good solution to your problem, by static casting your function explicitly:
static_cast<void(*)(int)>(SomeFunction)(i);
static_cast<void(*)(int&)>(SomeFunction)(i);
static_cast<void(*)(const int&)>(SomeFunction)(i);
Demo
It will work for first 3 overloads. 4th one is equivalent to 1st: quote from the standard [over.load]:
Parameter declarations that differ only in the presence or absence of const and/or volatile are
equivalent. That is, the const and volatile type-specifiers for each parameter type are ignored when
determining which function is being declared, defined, or called
and there is an example:
int f (int);
int f (const int); // redeclaration of f(int)
Also note that you cannot call 2nd overload with rvalue (temporary).
The only way I see this working the way your lecturer tried is if SomeFunction is a template and these four overloads are specializations.
template<typename T>
void SomeFunction(T num);
template<>
void SomeFunction<int>(int num) {}
template<>
void SomeFunction<int&>(int& num) {}
template<>
void SomeFunction<const int&>(const int& num) {}
template<>
void SomeFunction<const int>(const int num) {}
Then you can call it as follows.
SomeFunction<int>(x);
SomeFunction<int&>(x);
SomeFunction<const int&>(x);
SomeFunction<const int>(x);
Demo
However, this is incredibly stupid in this context. There are a lot of things wrong with the original overloads.
In the 4th one, the const is completely useless from the caller's perspective, because you can call it the same way you can call the 1st, and the argument is a copy anyway. The const only makes it so the argument is constant inside the function. Moreover, the 1st and 4th overloads cannot both be defined at the same time: the const is actually ignored in the prototype and it leads to a redefinition.
The 3rd overload is also useless because a const int& argument provides no benefit over a int argument. In fact, the compiler probably optimizes that away. The only difference is in the scenario I describe at the end. Of course, if the argument type is more complex (not just int or some other fundamental type), it often makes sense.
The 2nd overload is the only one that can modify the variable you pass as argument. However, if the 1st (or 4th, since it's really the same) overload is present as well, you cannot call the 2nd directly because the call would be ambiguous. You could still call the 1st with an rvalue (basically a literal or an expression like std::move(x)).
If the 2nd and 3rd overloads are the only ones present, then there is no ambiguity and you can call the 2nd with non-const lvalues and the 3rd with const lvalues or rvalues.
Demo

why can't default argument depend on non-default argument?

Consider the following constructor:
class MyClass {
MyClass(unsigned int dimension, std::vector vector=unitaryVector(dimension));
};
where unitaryVector(d) is a function that returns a random std::vector in d dimensions.
This gives the following compiler error:
error: default argument references parameter 'dimension'
MyClass(unsigned int dimension, std::vector vector=unitaryVector(dimension));
Why isn't this idiom valid in C++11? It seems to be pretty obvious: if vector argument is provided, init vector as a copy of the argument, otherwise, call the function and init it as a copy of the return value. Why can't a compiler understand this?
The C++ standard forbids it.
dcl.fct.default
9 default argument is evaluated each time the function is called with
no argument for the corresponding parameter. A parameter shall not
appear as a potentially-evaluated expression in a default argument.
Parameters of a function declared before a default argument are in
scope and can hide namespace and class member names.
[ Example:
int a;
int f(int a, int b = a); // error: parameter a
// used as default argument
typedef int I;
int g(float I, int b = I(2)); // error: parameter I found
int h(int a, int b = sizeof(a)); // OK, unevaluated operand
— end example ]
Note that default arguments are replaced at the call site if not provided
Intro.execution (emphasis mine)
11: [ Note: The evaluation of a full-expression can include the evaluation of subexpressions that are not lexically part of the
full-expression. For example, subexpressions involved in evaluating
default arguments ([dcl.fct.default]) are considered to be created
in the expression that calls the function, not the expression that
defines the default argument. — end note ]
You can simply overload the constructor and delegate it:
class MyClass {
explicit MyClass(unsigned int dimension)
: MyClass(dimension, unitaryVector(dimension)) //delegation
{ }
MyClass(unsigned int dimension, std::vector vector);
};
Footnote: Its a good thing to make single argument constructors explicit
One alternative is to use
class MyClass {
MyClass(unsigned int dimension, std::vector const& vector) :
dimension(dimension), vector(vector) {}
MyClass(unsigned int dimension) :
MyClass(dimension, unitaryVector(dimension)) {}
};
(this of course when you want to store dimension and vector in the class).
Because the default argument must be complete in itself so that compiler can simply replace it if not provided by call. The (local) variable dimension isn't created yet and you are trying to use it, and hence the error. This would work, however:
int _def_dim=10;
class MyClass {
MyClass(unsigned int dimension, std::vector vector=unitaryVector(_def_dim));
};
I am not sure what standard says, but for compiler implementation is would be tricky to handle such corner cases.
EDIT (for completeness), grabbed from this answer:
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.

How does constructor conversion work in C++?

How does constructor conversion work?
#include <iostream>
using namespace::std;
class One {
public:
One() { cout<<"One"<<endl;}
};
class Two {
public:
Two(const One&) {cout<<"Two(const One&)"<<endl;}
};
void f(Two) {cout<<"f(Two)"<<endl;}
int main() {
One one;
f(one);
}
produces the output
One
Two(const One&)
f(Two)
Any constructor that can be called with a single argument is considered an implicit conversion constructor. This includes simple 1-argument cases, and usage of default arguments.
This conversion is considered in any context that wants X and provided Y, and Y has such implicit conversion possibility. Note that a plenty of other, built-in conversions also play as a mix (like adjusting const-ness, integral and fp promotions, conversions, etc.) The rule is that at most one "user defined" implicit conversion is allowed in the mix.
In some cases it may be quite surprising, so the general advice is to make any such ctors explicit. That keyword makes the conversion possible but not implicitly: you must use T() syntax to force it.
As an example consider std::vector that has a ctor taking size_t, setting the initial size. It is explicit -- otherwise your foo(vector<double> const& ) function could be mistakenly called with foo(42).
It's right result. Since constructor is not explicit - implicit conversion works (here One is implicitly converted to Two).
one is created, then when passed to f converted to Two.
What the Two(const One&) {cout<<"Two(const One&)"<<endl;} constructor means is that you're allowed to construct a Two value at any time - implicitly - from a One. When you call f(one) it wants a Two parameter, it's given a One, so the compiler puts 2 and 2 together and says "I'll make a temporary Two from the One and complete the call to f()"... everyone will be happy. Hurray!
Compiler has to create copy of Two instance on stack. When you call f() with argument which is object of class One (or any other) compiler looks to definition of class Two and tries to find constructor which takes One(or any other) object(or reference) as an argument. When such constructor has been found it constructs object using it. It's called implicit because compiler do it without your interference.
class Foo {
public:
Foo(int number) {cout<<"Foo(int number)"<<endl;}
};
void f(Foo) {cout<<"f(Foo)"<<endl;}
int main() {
f(24);
} ///:~
Output will be:
Foo(int number)
f(Foo)

Why existing function arguments cannot be used to evaluate other default arguments?

I was writing a function foo() which takes 2 const char*s as arguments, pBegin and pEnd. foo() is passed a null terminated string. By default pEnd points to \0 (last character) of the string.
void foo (const char *pBegin,
const char *pEnd = strchr(pBegin, 0)) // <--- Error
{
...
}
However, I get an error at above line as:
error: local variable ‘pBegin’ may not appear in this context
Why compiler doesn't allow such operation ? What's the potential problem ?
The standard not only explicitly disallows the use of other parameters in a default argument expression, but also explains why and gives an example:
ISO/IEC 14882:2003(E) - 8.3.6 Default arguments [dcl.fct.default]
9. 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. Parameters of a
function declared before a default argument expression are in scope
and can hide namespace and class member names. [Example:
int a;
int f(int a, int b = a); // error: parameter a
// used as default argument
typedef int I;
int g(float I, int b = I(2)); // error: parameter I found
int h(int a, int b = sizeof(a)); // error, parameter a used
// in default argument
—end example] ...
The language still offers a way to do what you want - use overloaded functions:
void foo (const char *pBegin, const char *pEnd)
{
//...
}
void foo (const char *pBegin)
{ foo(pBegin, strchr(pBegin, 0)); }
When the function is called the default arguments are evaluated, but the order they are evaluated is not defined by the C++ standard. That means that you can't reference other parameters in a default argument because they may not have a known value yet.
You can't use a local variable in a default argument value.
Quote from here:
The standard tells us that the default argument is simply an expression.
There are some things that are no allowed (using local variables, using
the keyword 'this') but pretty much anything else can serve as a default
argument.

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

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.