In the following snippet, why does the line o.margin() = m; compile without fault? It easily deserves a warning, since it will almost invariably be a mistake. I would actually have thought it to be an error since it puts an R-Value on the left side of an assignment.
#include <iostream>
struct Margin
{
Margin(int val=0) : val(val) {};
int val;
};
struct Option
{
Margin m;
int z=0;
Margin margin()const { return m; }
int zoomLevel() { return z; }
};
int main()
{
Option o;
std::cout << "Margin is: "<< o.margin().val << std::endl;
Margin m = { 3 };
// The following line is a no-op, which generates no warning:
o.margin() = m;
// The following line is an error
// GCC 4.9.0: error: lvalue required as left operand of assignment
// clang 3.8: error: expression is not assignable
// MSVC 2015: error C2106: '=': left operand must be l-value
o.zoomLevel() = 2;
std::cout << "Margin is: "<< o.margin().val << std::endl;
return 0;
}
Output:
Margin is: 0
Margin is: 0
You are allowed to modify return types of class type (by calling non const methods on it):
3.10/5 from n4140
5 An lvalue for an object is necessary in order to modify the object
except that an rvalue of class type can also be used to modify its
referent under certain circumstances. [ Example: a member function
called for an object (9.3) can modify the object. —end example ]
your code:
o.margin() = m;
is actually the same as
o.margin().operator=( Margin(m) );
so non const method is called, if you change it to:
o.margin().val = m;
then you will get an error.
on the other hand here:
o.zoomLevel() = 2;
zoomLevel() returns non-class type, so you cannot modify it.
When o is an object of class type, operator= is a member function. The code o.margin() = m; is equivalent to o.margin().operator=(m);.
You are allowed to call members functions of temporary class objects, similar to how you access a member in o.margin().val.
Also, the class' assignment operator can be overridden and not be a no-op at all.
If you want to forbid such uses, since C++11 you can use a reference qualifier on the assignment operator:
Margin& operator=(const Margin&) & = default;
This will generate the following error on GCC 5.1:
error: passing 'Margin' as 'this' argument discards qualifiers [-fpermissive]
You might also want to check this related question.
Option::margin() is a const-accessible member function that returns a mutable Margin object.
Consequently, assigning the temporary is valid because using operator= on Margin is valid. In this case it has no side-effects and basically does nothing. A particular implementation of a C++ compiler may choose to implement semantic analysis and warn you, but it's completely outside the scope of the language.
Related
In C++, to declare an object of a class that has a member variable as const, we must have a user-defined default constructor. The following code illustrates this.
class Some {
int value;
};
int main() {
// error: default initialization of an object of const type 'const Some'
// without a user-provided default constructor
const Some some;
return 0;
}
However, if a member variable owned by a class is qualified as mutable, the compiler will not report any errors. For reference, I compiled using the command clang++ -std=c++17 -stdlib=libc++ helloworld.cpp -o helloworld.out --debug. I wonder if this result is due to a bug in the compiler or according to the syntax defined in the C++ language.
class Some {
mutable int value;
};
int main() {
const Some some;
return 0;
}
Rewriting my comment as an answer, hope it could helps someone.
It makes no sense declaring a const object if it is not initialized in some form.
Consider the following code:
const int x;
clang says: error: default initialization of an object of const type 'const int'.
gcc would say: error: uninitialized const ‘x’ [-fpermissive]
The logic behind this is that there is no sense in this type of declaration.
The value of x can never change, and therefore this code would be unpredictable as x would be mapped to uninitialized memory.
In your example, adding the keyword mutable to value means that although the Some instance is constant when declared as:
const Some some;
It is still possible to change value at a later time.
For example:
some.value = 8;
This means it is possible to use this code in a predictable manner, since value can be set later, and there are no uninitialized constants.
Can anybody explain why this code compiles:
typedef struct longlong
{
unsigned long low;
long high;
}
longlong;
typedef longlong Foo;
struct FooStruct
{
private:
Foo bar;
public:
void SetBar(Foo m)
{
bar = m;
}
Foo GetBar()
{
return bar;
}
};
int main()
{
FooStruct f;
Foo m1 = { 1,1 };
Foo m2 = { 2,2 };
f.SetBar(m1);
f.GetBar() = m2; // Here I'd expect an error such as
// "error: lvalue required as left operand of assignment"
}
I expected the compilation to fail with error: lvalue required as left operand of assignment on line f.GetBar() = m2; because IMO f.GetBar() is not an l-value, but it compiles seemlessly and f.GetBar() = m2; is a NOP.
On the other hand if I replace the typedef longlong Foo; by typedef long Foo;, the line mentioned before won't compile and I get the expected error.
I came along this issue while refactoring some old code. The code in this question has no purpose other than to illustrate this issue.
This works because longlong is a class, and as such = is longlong::operator =, the implicitly-defined assignment operator. And you can call member functions on temporaries as long as they're not qualified with & (the default operator = is unqualified).
To restrict the assignment operator to lvalues, you can explicitly default it with an additional ref-qualifier:
struct longlong
{
longlong &operator = (longlong const &) & = default;
// ^
// ...
};
Using operators on objects of class type means to call a function (either a member function of the left-hand operand, or a free function taking the left-hand operand as first argument). This is known as operator overloading.
It's fine to call functions on rvalues so there is no compilation error.
When implementing the overloaded assignment operator, you can mark it so that it can not be called on an rvalue, but the designer of whatever class you are using chose not to do that.
f.GetBar() = m2; // Here I'd expect an error such as
// "error: lvalue required as left operand of assignment"
Your expectation is in error. This rule only applies to objects of built-in type.
[C++14: 3.10/5]: An lvalue for an object is necessary in order to modify the object except that an rvalue of class type can also be used to modify its referent under certain circumstances. [ Example: a member function called for an object (9.3) can modify the object. —end example ]
Recall that your = here is actually a call to operator=, which is a function.
Reasons for this behavior were described in other answers.
One way to avoid this behavior would be qualifying your return type with const:
struct FooStruct
{
...
const Foo GetBar() {return bar;}
};
You cannot assign values to const objects, so the compiler will complain.
What is the "operator int" function below? What does it do?
class INT
{
int a;
public:
INT(int ix = 0)
{
a = ix;
}
/* Starting here: */
operator int()
{
return a;
}
/* End */
INT operator ++(int)
{
return a++;
}
};
The bolded code is a conversion operator. (AKA cast operator)
It gives you a way to convert from your custom INT type to another type (in this case, int) without having to call a special conversion function explicitly.
For example, with the convert operator, this code will compile:
INT i(1234);
int i_2 = i; // this will implicitly call INT::operator int()
Without the convert operator, the above code won't compile, and you would have to do something else to go from an INT to an int, such as:
INT i(1234);
int i_2 = i.a; // this wont compile because a is private
operator int() is a conversion operator, which allows this class to be used in place of an int. If an object of this type is used in a place where an int (or other numerical type) is expected, then this code will be used to get a value of the correct type.
For example:
int i(1);
INT I(2); // Initialised with constructor; I.a == 2
i = I; // I is converted to an int using `operator int()`, returning 2.
First things first:
$12.3.1/1 - "A constructor declared
without the function-specifier
explicit specifies a conversion from
the types of its parameters to the
type of its class. Such a constructor
is called a converting constructor."
In your example, INT is a User Defined class that has a converting constructor from 'int'.
Therefore the following code is well-formed:
INT i(1024); // direct initialization syntax
This means that you can get an INT object from an integer. However what does one do, if the INT object has to be converted back to an integer? Transitivity?
One can say that the class INT can provide a member function to return the encapsulated integer member
int x = i.geta();
This however is not very intuitive and is not a standardized approach. Also it is not intuitive when it comes to how built-in types work in such situations.
int z = 0;
int y1 = z; // copy initialization or
int y2(z); // direct initialization
double d = (int )z; // explicit cast
Therefor the Standard allows for such standardization and intuitiveness of converting User Defined Types by saying:
$12.3/2 - "A member function of a
class X having no parameters with a
name of the form [...]
operator conversion-type-id
[...]specifies a conversion from X to the
type specified by the
conversion-type-id. Such functions are
called conversion functions. No return
type can be specified. If a conversion
function is a member function, the
type of the conversion function
(8.3.5) is “function taking no
parameter returning
conversion-type-id”.
This makes all of the following well-formed and retains harmony with the way built-in types work is
int y1 = i; // copy initialization or
int y2(i); // direct initialization
double d = (int )i; // explicit cast
It looks like it make an INT class which behaves a little like the regular int, just that some other operators are not yet defined.
Is this a homework problem?
Seems like it's a question from a classroom, so I'll invite you to check the documentation on how to create a class.
class Foo
{
public
Foo() {} // Constructor
Foo operator++ {} // Operation ++ on foo like:
// Foo foo;
// foo++;
};
Why does the first commented line compile correctly, whereas the second doesn't?
Why can a be given itself as a constructor argument, but b can't?
Aren't the two doing the same thing?
class Foo { Foo &operator =(Foo const &); /* Disable assignment */ };
int main()
{
Foo a = a; // OK
Foo b(b); // error C2065: 'b' : undeclared identifier
}
Update
Since it seems like it's compiler-dependent, it seems like the problem is more severe than I thought.
So I guess another part of the question is, is the following code valid or no?
It gives an error in GCC but Visual C++ executes it just fine.
int main()
{
int i = 0;
{ int *i(&i); }
return i;
}
In your first code, both declarations should compile. GCC is right there. Visual C++ Compiler has bug.
And in the second code, the inner declaration should not compile. GCC is right there too, and VC++ is wrong.
GCC is right in both cases.
A code like int a=a+100; and int a(a+100); is fine from syntax point of view. They might invoke undefined behavior depending on whether they're created in static storage duration or automatic storage duration.
int a = a + 100; //well-defined. a is initialized to 100
//a on RHS is statically initialized to 0
//then a on LHS is dynamically initialized to (0+100).
void f()
{
int b = b + 100; //undefined-behavior. b on RHS is uninitialized
int a = a + 50; //which `a` is on the RHS? previously declared one?
//No. `a` on RHS refers to the newly declared one.
//the part `int a` declares a variable, which hides
//any symbol with same name declared in outer scope,
//then `=a+50` is the initializer part.
//since a on RHS is uninitialized, it invokes UB
}
Please read the comments associated with each declaration above.
Note that variables with static storage duration is statically initialized to zero at compile time, and if they've initializer, then they're dynamically initialized also at runtime. But variables of POD types with automatic storage duration are not statically initialized.
For more detail explanation on static initialization vs dynamic initialization, see this:
What is dynamic initialization of object in c++?
In your first example, as you note, the behavior is undefined even though the syntax is okay. A compiler is therefore permitted to refuse the code (the undefined behavior must be guaranteed however; it is here, but it wouldn't be if the invalid initializations were never actually executed).
Your second example has a type error: A declaration is visible as soon as its declarator is seen, and in particular it is visible in its own initializer. MSVC++ delays the visibility: That's a known non-conformance issue in that compiler. E.g., with the EDG compiler (which has a Microsoft mode):
$ ./cfe --strict x.c
"x.c", line 4: error: a value of type "int **" cannot be used to initialize an
entity of type "int *"
{ int *i(&i); }
^
1 error detected in the compilation of "x.c".
$ ./cfe --microsoft x.c
"x.c", line 4: warning: variable "i" was declared but never referenced
{ int *i(&i); }
^
The following code only works when the copy constructor is available.
When I add print statements (via std::cout) and make the copy constructor available it is not used (I assume there is so compiler trick happening to remove the unnecessary copy).
But in both the output operator << and the function plop() below (where I create a temporary object) I don't see the need for the copy constructor. Can somebody explain why the language needs it when I am passing everything by const reference (or what I am doing wrong).
#include <iostream>
class N
{
public:
N(int) {}
private:
N(N const&);
};
std::ostream& operator<<(std::ostream& str,N const& data)
{
return str << "N\n";
}
void plop(std::ostream& str,N const& data)
{
str << "N\n";
}
int main()
{
std::cout << N(1); // Needs copy constructor (line 25)
plop(std::cout,N(1)); // Needs copy constructor
N a(5);
std::cout << a;
plop(std::cout,a);
}
Compiler:
[Alpha:~/X] myork% g++ -v
Using built-in specs.
Target: i686-apple-darwin10
Configured with: /var/tmp/gcc/gcc-5646~6/src/configure --disable-checking --enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.2/ --with-slibdir=/usr/lib --build=i686-apple-darwin10 --with-gxx-include-dir=/include/c++/4.2.1 --program-prefix=i686-apple-darwin10- --host=x86_64-apple-darwin10 --target=i686-apple-darwin10
Thread model: posix
gcc version 4.2.1 (Apple Inc. build 5646)
[Alpha:~/X] myork% g++ t.cpp
t.cpp: In function ‘int main()’:
t.cpp:10: error: ‘N::N(const N&)’ is private
t.cpp:25: error: within this context
t.cpp:10: error: ‘N::N(const N&)’ is private
t.cpp:26: error: within this context
This is a simplified version of some real code.
In the real code I have a class that contains a std::auto_ptr. This means that a copy constructor that takes a const reference is not valid (without some work) and I was getting an error indicating that the copy constructor was not available because of it:
Change the class too:
class N
{
public:
N(int) {}
private:
std::auto_ptr<int> data;
};
The error is then:
t.cpp:25: error: no matching function for call to ‘N::N(N)’
From http://gcc.gnu.org/gcc-3.4/changes.html
When binding an rvalue of class type
to a reference, the copy constructor
of the class must be accessible. For
instance, consider the following code:
class A
{
public:
A();
private:
A(const A&); // private copy ctor
};
A makeA(void);
void foo(const A&);
void bar(void)
{
foo(A()); // error, copy ctor is not accessible
foo(makeA()); // error, copy ctor is not accessible
A a1;
foo(a1); // OK, a1 is a lvalue
}
This might be surprising at first
sight, especially since most popular
compilers do not correctly implement
this rule (further details).
This will be fixed in C++1x by Core Issue 391.
The applicable parts of the standard here are §8.5.3/5, which covers initialization of references and §3.10/6, which tells what's an rvalue and what's an lvalue (not always obvious in C++).
In this case, you initialization expression is: "N(1)", so you're explicitly creating an object using functional notation. According to 3.10/6, that expression is an rvalue.
Then we have to walk through the rules in 8.5.3/5 in order, and use the first that applies. The first possibility is if the expression represents an lvalue, or can be implicitly converted to an lvalue. Your expression is an rvalue, and implicit conversion to an lvalue would require a conversion function that returns a reference, which doesn't seem to exist in this case, so that doesn't seem to apply.
The next rule says the reference must be to a const T (which is the case here). In this case, the expression is an rvalue of class type and is reference-compatible with the reference (i.e. the reference is to the same class, or a base of the class). That means the bullet at the bottom of page 151 (179 of the C++ 2003 PDF) seems to apply. In this case, the compiler is allowed to either bind the reference directly to the object representing the rvalue, OR create a temporary copy of the rvalue, and bind to that temporary copy.
Either way, however, the standard explicitly requires that: "The constructor that would be used to make the copy shall be callable whether or not the copy is actually done."
As such, I believe that gcc is right to give an error message, and the others are technically wrong to accept the code. I simplified your code a bit to the following:
class N {
public:
N(int) {}
private:
N(N const&);
};
void plop(N const& data) { }
int main() {
plop(N(1));
}
When invoked with "--A" (strict errors mode), Comeau gives the following error message:
"plop.cpp", line 12: error: "N::N(const N &)", required for copy that was
eliminated, is inaccessible
plop(N(1));
^
Likewise, when invoked with "/Za" (its "ANSI conforming" mode), VC++ 9 gives:
plop.cpp
plop.cpp(12) : error C2248: 'N::N' : cannot access private member declared in class 'N'
plop.cpp(6) : see declaration of 'N::N'
plop.cpp(2) : see declaration of 'N'
while checking that elided copy-constructor 'N::N(const N &)' is callable
plop.cpp(6) : see declaration of 'N::N'
when converting from 'N' to 'const N &'
My guess is that most of the other compilers do roughly the same. Since they optimize out the call to the copy constructor, they don't normally require that it exist or be accessible. When you ask them to conform to the standard as accurately as they can, they give the error message, because it's technically required even though they don't use it.