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

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.

Related

member initializer lists in constructor with same-name variables [duplicate]

This question already has answers here:
Can I use identical names for fields and constructor parameters?
(6 answers)
Closed 1 year ago.
I figured out that it's possible to initialize the member variables with a constructor argument of the same name as show in the example below.
#include <cstdio>
#include <vector>
class Blah {
std::vector<int> vec;
public:
Blah(std::vector<int> vec): vec(vec)
{}
void printVec() {
for(unsigned int i=0; i<vec.size(); i++)
printf("%i ", vec.at(i));
printf("\n");
}
};
int main() {
std::vector<int> myVector(3);
myVector.at(0) = 1;
myVector.at(1) = 2;
myVector.at(2) = 3;
Blah blah(myVector);
blah.printVec();
return 0;
}
g++ 4.4 with the arguments -Wall -Wextra -pedantic gives no warning and works correctly. It also works with clang++. I wonder what the C++ standard says about it? Is it legal and guaranteed to always work?
I wonder what the C++ standard says about it? Is it legal and guaranteed to always work?
Yes. That is perfectly legal. Fully Standard conformant.
Blah(std::vector<int> vec): vec(vec){}
^ ^
| |
| this is the argument to the constructor
this is your member data
Since you asked for the reference in the Standard, here it is, with an example.
§12.6.2/7
Names in the expression-list of a mem-initializer are evaluated in the scope of the constructor for which the mem-initializer is specified.
[Example:
class X {
int a;
int b;
int i;
int j;
public:
const int& r;
X(int i): r(a), b(i), i(i), j(this->i) {}
//^^^^ note this (added by Nawaz)
};
initializes X::r to refer to X::a,
initializes X::b with the value of the
constructor parameter i, initializes
X::i with the value of the constructor
parameter i, and initializes X::j with
the value of X::i; this takes place
each time an object of class X is
created. ]
[Note: because the
mem-initializer are evaluated in the
scope of the constructor, the this
pointer can be used in the
expression-list of a mem-initializer
to refer to the object being
initialized. ]
As you can see, there're other interesting thing to note in the above example, and the commentary from the Standard itself.
BTW, as side note, why don't you accept the parameter as const reference:
Blah(const std::vector<int> & vec): vec(vec) {}
^^^^const ^reference
It avoids unneccessary copy of the original vector object.
It is guaranteed always to work (I use it quite often). The compiler knows that the initializer list is of the form: member(value), and so it knows that the first vec in vec(vec) must be a member. Now on the argument to initialize the member, both members, arguments to the constructor and other symbols can be used, as in any expression that would be present inside the constructor. At this point it applies the regular lookup rules, and the argument vec hides the member vec.
Section 12.6.2 of the standard deals with initialization and it explains the process with paragraph 2 dealing with lookup for the member and paragraph 7 with the lookup of the argument.
Names in the expression-list of a mem-initializer are evaluated in the scope of the constructor for which the mem-initializer is specified. [Example:
class X {
int a;
int b;
int i;
int j;
public:
const int& r;
X(int i): r(a), b(i), i(i), j(this->i) {}
};
One additional counter argument or perhaps just something to be aware of is the situation in which move construction is used to intialize the member variable.
If the member variable needs to be used within the body of the constructor then the member variable needs to be explcitly referenced through the this pointer, otherwise the moved variable will be used which is in an undefined state.
template<typename B>
class A {
public:
A(B&& b): b(std::forward(b)) {
this->b.test(); // Correct
b.test(); // Undefined behavior
}
private:
B b;
};
As others have already answered: Yes, this is legal. And yes, this is guaranteed by the Standard to work.
And I find it horrible every time I see it, forcing me to pause: "vec(vec)? WTF? Ah yes, vec is a member variable..."
This is one of the reasons why many, including myself, like to use a naming convention which makes it clear that a member variable is a member variable. Conventions I have seen include adding an underscore suffix (vec_) or an m_ prefix (m_vec). Then, the initializer reads: vec_(vec) / m_vec(vec), which is a no-brainer.

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

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.

Initializing member variables using the same name for constructor arguments as for the member variables allowed by the C++ standard? [duplicate]

This question already has answers here:
Can I use identical names for fields and constructor parameters?
(6 answers)
Closed 1 year ago.
I figured out that it's possible to initialize the member variables with a constructor argument of the same name as show in the example below.
#include <cstdio>
#include <vector>
class Blah {
std::vector<int> vec;
public:
Blah(std::vector<int> vec): vec(vec)
{}
void printVec() {
for(unsigned int i=0; i<vec.size(); i++)
printf("%i ", vec.at(i));
printf("\n");
}
};
int main() {
std::vector<int> myVector(3);
myVector.at(0) = 1;
myVector.at(1) = 2;
myVector.at(2) = 3;
Blah blah(myVector);
blah.printVec();
return 0;
}
g++ 4.4 with the arguments -Wall -Wextra -pedantic gives no warning and works correctly. It also works with clang++. I wonder what the C++ standard says about it? Is it legal and guaranteed to always work?
I wonder what the C++ standard says about it? Is it legal and guaranteed to always work?
Yes. That is perfectly legal. Fully Standard conformant.
Blah(std::vector<int> vec): vec(vec){}
^ ^
| |
| this is the argument to the constructor
this is your member data
Since you asked for the reference in the Standard, here it is, with an example.
§12.6.2/7
Names in the expression-list of a mem-initializer are evaluated in the scope of the constructor for which the mem-initializer is specified.
[Example:
class X {
int a;
int b;
int i;
int j;
public:
const int& r;
X(int i): r(a), b(i), i(i), j(this->i) {}
//^^^^ note this (added by Nawaz)
};
initializes X::r to refer to X::a,
initializes X::b with the value of the
constructor parameter i, initializes
X::i with the value of the constructor
parameter i, and initializes X::j with
the value of X::i; this takes place
each time an object of class X is
created. ]
[Note: because the
mem-initializer are evaluated in the
scope of the constructor, the this
pointer can be used in the
expression-list of a mem-initializer
to refer to the object being
initialized. ]
As you can see, there're other interesting thing to note in the above example, and the commentary from the Standard itself.
BTW, as side note, why don't you accept the parameter as const reference:
Blah(const std::vector<int> & vec): vec(vec) {}
^^^^const ^reference
It avoids unneccessary copy of the original vector object.
It is guaranteed always to work (I use it quite often). The compiler knows that the initializer list is of the form: member(value), and so it knows that the first vec in vec(vec) must be a member. Now on the argument to initialize the member, both members, arguments to the constructor and other symbols can be used, as in any expression that would be present inside the constructor. At this point it applies the regular lookup rules, and the argument vec hides the member vec.
Section 12.6.2 of the standard deals with initialization and it explains the process with paragraph 2 dealing with lookup for the member and paragraph 7 with the lookup of the argument.
Names in the expression-list of a mem-initializer are evaluated in the scope of the constructor for which the mem-initializer is specified. [Example:
class X {
int a;
int b;
int i;
int j;
public:
const int& r;
X(int i): r(a), b(i), i(i), j(this->i) {}
};
One additional counter argument or perhaps just something to be aware of is the situation in which move construction is used to intialize the member variable.
If the member variable needs to be used within the body of the constructor then the member variable needs to be explcitly referenced through the this pointer, otherwise the moved variable will be used which is in an undefined state.
template<typename B>
class A {
public:
A(B&& b): b(std::forward(b)) {
this->b.test(); // Correct
b.test(); // Undefined behavior
}
private:
B b;
};
As others have already answered: Yes, this is legal. And yes, this is guaranteed by the Standard to work.
And I find it horrible every time I see it, forcing me to pause: "vec(vec)? WTF? Ah yes, vec is a member variable..."
This is one of the reasons why many, including myself, like to use a naming convention which makes it clear that a member variable is a member variable. Conventions I have seen include adding an underscore suffix (vec_) or an m_ prefix (m_vec). Then, the initializer reads: vec_(vec) / m_vec(vec), which is a no-brainer.

Constructor of type int

Considering the cost, are these cases the same?
// case 1
int a = 5;
// case 2
int a (5);
// case 3
int a;
a = 5
The three syntaxes are different, bear with me while I use a user defined type instead of int, I will go back to int later.
T a(5); // Direct initialization
T b = 5; // Implicit conversion (5->tmp) + copy-initialization
T c; c = 5; // Default initialization + assignment
In the first case the object a is constructed by means of a constructor that takes an int or a type that can be implicitly converted from int.
struct T {
T( int ); // T a(5) will call this directly
};
In the second case, a temporary object of type T is created by an implicit conversion from int, and then that temporary is used to copy construct b. The compiler is allowed to optimize the code away and perform just the implicit conversion in place of the final object (instead of using it to create the temporary. But all restrictions have to be verified:
class T {
T( T const & );
public:
explicit implicit T( int );
};
int main() {
T b = 5; // Error 1: No implicit conversion from int to T.
// Fix: remove the `explicit` from the constructor
// Error 2: Copy constructor is not accessible
}
The third case is default construction followed by assignment. The requirements on the type are that it can be default constructed (there is a constructor with no arguments, or there is no user defined constructor at all and the compiler will implicitly define it). The type must be assignable from int or there must be an implicit conversion from int to a type U that can be assigned to T. As you see, the requirements for the type in the tree cases differ.
Besides the semantics of the different operations, there is other important difference, not all of them can be used in all of the contexts. In particular, in an initialization list in a class you cannot use the implicit convert + copy initialize version, and you can only have the first half of default construct + assign.
// OK // error // ok but different
struct test { struct test { struct test {
T x; T x; T x;
test(int v) : x(v) {} test(int v) : x=5 {} test( int v ) {
x = v;
}
In the first case the attribute x is directly initialized with the value v. The second case is a syntax error. The third case first default initializes and then assigns inside the body of the constructor.
Going back to the int example, all of the requirements are met by the type, so there is almost no difference on the code that the compiler generates for the three cases, but still you cannot use the int b = 5; version inside an initializer list to initialize an integer attribute. Moreover, if a class has a member attribute that is a constant integer, then you cannot use the equivalent of int c; c =5; (third column above) as the member attribute becomes const when it enters the constructor block, that is, x = v; above would be trying to modify a constant and the compiler will complain.
As to the cost that each one has, if they can be used at all, they incur the same cost for an int (for any POD type) but not so for user defined types that have a default constructor, in which case T c; c = 5; will incur the cost of default construction followed by the cost of assignment. In the other two cases, the standard explicitly states that the compiler is allowed to generate the exact same code (once the constraints are checked).
First and second are exactly same as both are initialization. Third one is different, as this is assignment. These differences are as per the C++ Standard. However, the compiler can treat all three as same!
For the first two, there will be no difference.
From Standard docs, 8.5.11,
The form of initialization (using parentheses or =) is generally insignificant, but does matter when the initializer or the
entity being initialized has a class type; see below. A parenthesized initializer can be a list of expressions only when the
entity being initialized has a class type.
The third one is not an initialization but an assignment.
And considering the cost,
In the first two cases, you are creating an integer with a value 5.
In the third case, you are creating an integer with an undefined value and replace it with 5..
If you use an optimizing compiler, they will all compile to the same code. So they all have the same cost.
Yes, they all evaluate to the exact same assembler representation. You can test this e.g. with GCC by writing a dummy function and then producing the assembler output: g++ -S file.cxx -o file.s