Constructor overloading resolution and compilation - c++11

Having a structure TB with 3 constructors, the code below compiles and the copy constructor - "Constructor A" is called twice. Commenting out "constructor C" causes compilation problem.
What is the reason for that?
Why uncommenting the const in constructor B influences the compilation result?
Is this related to overriding the default copy assignment operator?
What are the resolution rules which apply here?
int doIgetHere = 0;
struct TA{
int a;
};
struct TB{
int b;
//Constructor A
TB(TA &ta){
b = ta.a;
}
//Constructor B
TB(/*const*/ TB &tb){
doIgetHere++;
b = tb.b;
}
// Constructor C === removing this constructor cause compilation problem, why ?
TB(TB && mtb){
doIgetHere++;
b = mtb.b;
}
};
int main(){
TA ta;
TB tb1(ta); // using constructor A
TB tb2 = ta; // using constructor A
std::cout<<doIgetHere<<"\n"; // result is 0, as expected
}
Compilation log:
34:5: error: no viable constructor copying variable of type 'TB'
TB tb2 = ta; // using constructor A
^ ~~
18:2: note: candidate constructor not viable: expects an l-value for 1st argument
TB(/*const*/ TB &tb){
^ 1 error generated.

When you do
TB tb2 = ta;
it is actually equal to
TB tb2 = TB(ta);
In other words, a temporary object is created from ta which is then used in the copy-constructor (your constructor B).
The problem here is that non-const references can not bind to temporary objects. The solution is simply to make the argument a constant reference (which you commented out for some reason).
With your non-standard move-constructor (your constructor C) you can take temporary objects as arguments, that part of how rvalue references works.

Related

C++ default move operation in "Effective Modern C++"

Item 17: Understand special member function generation.
Move operations are generated only for classed lacking
explicitly declared move operation, copy operations,
or a destructor.
Now, when I refer to a move operation move-constructing
or move-assigning a data member or base class, there
is no guarantee that a move will actually take place.
"Memberwise moves" are, in reality, more like memberwise
move requests, because types that aren't move-enabled(...)
will be "moved" via their copy operations.
However, I can not verify them on my environment.
// compiled
#include <iostream>
using namespace std;
class Base {
public:
~Base() {}
};
int main() {
Base a, c;
Base b(move(a));
c = move(b);
// explicitly destructor does not disable
// default move constuctor and move assignment operator
return 0;
}
class Base {
public:
Base() {}
Base(Base& b) {}
~Base() {}
};
class Num {
private:
Base b;
};
int main() {
Num a, c;
c = move(a); // passed
Num b(move(c)); // error
// explicitly Base::Base(Base& b) disable default move
// move conctructor.
// Num's default move constructor can not find any move
// constructor for member object Base b, which lead to an
// error. Num's default move constructor does not "moved"
// Base b via their copy operations which is declared.
return 0;
}
The first assertion might be vary from different environments, but the second one is almost wrong.
I am very confusing about it.
Please help me out.
class Base {
public:
~Base() {}
};
Because Base has a user-declared destructor, it does not have a move constructor or move assignment operator at all. The lines
Base b(move(a));
c = move(b);
actually call the copy constructor and copy assignment operator, respectively.
class Base {
public:
Base() {}
Base(Base& b) {}
~Base() {}
};
class Num {
private:
Base b;
};
Again, Base has no move constructor at all. However, Num does have an implicitly-declared move constructor, since Num itself does not declare any special member functions. However, it is implicitly defined as deleted, since the default definition would be ill-formed:
Num::Num(Num&& n) : b(std::move(n.b)) {}
// Cannot convert rvalue of type `Base` to `Base&`
// for the copy constructor to be called.
Note that the "move" of b does attempt to use the copy constructor, but it can't.

Additional move constructor in pass by value function

I have a simple struct, that has all constructors defined.
It has an int variable, each constructor and assign operator prints address of *this, current value of int and a new value of int.
Move and copy assign operators and constructors also print adress of passed value.
#include <iostream>
struct X
{
int val;
void out(const std::string& s, int nv, const X* from = nullptr)
{
std::cout<<this<<"->"<<s<<": "<<val<<" ("<<nv<<")";
if (from)
std::cout<<", from: ["<<from<<"]";
std::cout<<"\n";
}
X(){out("simple ctor X()",0); val = 0;}
X(int v){out("int ctor X(int)", v);val = v; }
X(const X& x){out("copy ctor X(X&)", x.val, &x);val = x.val; };
X&operator = (const X& x){out("copy X::operator=()", x.val, &x); val = x.val; return *this;}
~X(){out("dtor ~X", 0);}
X&operator = (X&& x){out("move X::operator(&&)", x.val, &x); val = x.val; return *this;}
X(X&& x){out("move ctor X(&&x)", x.val, &x);val = x.val;}
};
X copy(X a){return a;}
int main(int argc, const char * argv[]) {
X loc{4};
X loc2;
std::cout<<"before copy\n";
loc2 = copy(loc);
std::cout<<"copy finish\n";
}
output:
0xffdf7278->int ctor X(int): 134523184 (4)
0xffdf727c->simple ctor X(): 134514433 (0)
before copy
0xffdf7280->copy ctor X(X&): 1433459488 (4), from: [0xffdf7278]
0xffdf7284->move ctor X(&&x): 1433437824 (4), from: [0xffdf7280]
0xffdf727c->move X::operator(&&): 0 (4), from: [0xffdf7284]
0xffdf7284->dtor ~X: 4 (0)
0xffdf7280->dtor ~X: 4 (0)
copy finish
0xffdf727c->dtor ~X: 4 (0)
0xffdf7278->dtor ~X: 4 (0)
What's the purpose of creating an additional object with (in this example) address 0xffdf7284?
If you look at the copy elision rules from cppreference.com, you can notice that there are two case where the compilers are required to omit the copy- and move- constructors of class objects even if copy/move constructor and the destructor have observable side-effects (which yours do, due to the printouts). The first is clearly irrelevant to this case. The second is
In a function call, if the operand of a return statement is a prvalue and the return type of the function is the same as the type of that prvalue.
With the example given of:
T f() { return T{}; }
T x = f();
This seems more relevant, however, note that in your case, the operand of the return statement is not a prvalue. So in this case, no mandatory elision applies.
The set of steps, when callingloc2 = copy(loc);, is as follows:
a is copy-constructed from loc.
The return value of the function is move-constructed from a.
loc2 is move-assigned from the return value.
Logically, a person could look at the code and deduce that fewer operations need to be done (in particular, when looking at copy, it's obvious that, logically, an assignment from loc to loc2 is enough), but the compiler doesn't know that the purpose of your code isn't to generate the side effects (the printouts), and it is not breaking any rules here.

Preferred way of class member initialization?

class A { public: int x[100]; };
Declaring A a will not initialize the object (to be seen by garbage values in the field x).
The following will trigger initialization: A a{} or auto a = A() or auto a = A{}.
Should any particular one of the three be preferred?
Next, let us make it a member of another class:
class B { public: A a; };
The default constructor of B appears to take care of initialization of a.
However, if using a custom constructor, I have to take care of it.
The following two options work:
class B { public: A a; B() : a() { } };
or:
class B { public: A a{}; B() { } };
Should any particular one of the two be preferred?
Initialization
class A { public: int x[100]; };
Declaring A a will not initialize the object (to be seen by garbage
values in the field x).
Correct A a is defined without an initializer and does not fulfill any of the requirements for default initialization.
1) The following will trigger initialization:
A a{};
Yes;
a{} performs list initialization which
becomes value initialization if {} is empty, or could be aggregate initialization if A is an aggregate.
Works even if the default constructor is deleted. e.g. A() = delete; (If 'A' is still considered an aggregate)
Will warn of narrowing conversion.
2) The following will trigger initialization:
auto a = A();
Yes;
This is copy initialization where a prvalue temporary is constructed with direct initialization () which
uses value initialization if the () is empty.
No hope of aggregate initialization.
The prvalue temporary is then used to direct-initialize the object.
Copy elision may be, and normally is employed, to optimize out the copy and construct A in place.
Side effects of skipping copy/move constructors are allowed.
Move constructor may not be deleted. e.g A(A&&) = delete;
If copy constructor is deleted then move constructor must be present. e.g. A(const A&) = delete; A(A&&) = default;
Will not warn of narrowing conversion.
3) The following will trigger initialization:
auto a = A{}
Yes;
This is copy initialization where a prvalue temporary is constructed with list initialization {} which
uses value initialization if {} is empty, or could be aggregate initialization if A is an aggregate.
The prvalue temporary is then used to direct-initialize the object.
Copy elision may be, and normally is employed, to optimize out the copy and construct A in place.
Side effects of skipping copy/move constructors are allowed.
Move constructor may not be deleted. e.g A(A&&) = delete;
If copy constructor is deleted then move constructor must be present. e.g. A(const A&) = delete; A(A&&) = default;
Will warn of narrowing conversion.
Works even if the default constructor is deleted. e.g. A() = delete; (If 'A' is still considered an aggregate)
Should any particular one of the three be preferred?
Clearly you should prefer A a{}.
Member Initialization
Next, let us make it a member of another class:
class B { public: A a; };
The default constructor of B appears to take care of initialization
of a.
No this is not correct.
the implicitly-defined default constructor of 'B' will call the default constructor of A, but will not initialize the members. No direct or list initialization will be triggered. Statement B b; for this example will call the default constructor, but leaves indeterminate values of A's array.
1) However, if using a custom constructor, I have to take care of it. The
following two options work:
class B { public: A a; B() : a() { } };
This will work;
: a() is a constructor initializer and a() is a member initializer as part of the member initializer list.
Uses direct initialization () or, if () is empty, value initialization.
No hope of using aggregate initialization.
Will not warn of narrowing conversion.
2) or:
class B { public: A a{}; B() { } };
This will work;
a now has a non-static data member initializer, which may require a constructor to initialize it if you are using aggregate initialization and the compiler is not fully C++14 compliant.
The member initializer uses list initialization {} which
may become either value initialization if {} is empty or aggregate initialization if A is an aggregate.
If a is the only member then the default constructor does not have to be defined and the default constructor will be implicitly defined.
Clearly you should prefer the second option.
Personally, I prefer using braces everywhere, with some exceptions for auto and cases where a constructor could mistake it for std::initializer_list:
class B { public: A a{}; };
A std::vector constructor will behave differently for std::vector<int> v1(5,10) and std::vector<int> v1{5,10}. with (5,10) you get 5 elements with the value 10 in each one, but with {5,10} you get two elements containing 5 and 10 respectively because std::initializer_list is strongly preferred if you use braces. This is explained very nicely in item 7 of Effective Modern C++ by Scott Meyers.
Specifically for member initializer lists, two formats may be considered:
Direct initialization a() which becomes value initialization if the () is empty.
List initialization a{} which also becomes value initialization if {} is empty.
In member initializer lists, fortunately, there is no risk of the most vexing parse. Outside of the initializer list, as a statement on its own, A a() would have declared a function vs. A a{} which would have been clear. Also, list initialization has the benefit of preventing narrowing conversions.
So, in summary the answer to this question is that it depends on what you want to be sure of and that will determine the form you select. For empty initializers the rules are more forgiving.

copy constructor and overloaded '=' operator not working

The copy constructor and overloaded '=' operator are not being called when assigned with result of sum of two class objects. There are working properly when initialized and assigned with single object. the error says "no match for ‘operator=’ (operand types are ‘comp’ and ‘comp’)". Important code snippets are
class comp
{
int a,b;
public:
comp()
{
a=b=1;
}
comp(int,int);
comp(comp &);
comp operator+(comp &);
operator int();
void show()
{
cout<<"a= "<<a<<"b= "<<b<<endl;
}
comp& operator=(comp &);
friend ostream &operator<<(ostream &out, comp &c);
};
comp::comp(comp & c)//copy constructor
{
a=c.a,b=c.b;
cout<<"copy constructor called"<<endl;
}
comp comp::operator+(comp & c1)// overloaded '+' opreator
{
comp c;
c.a=a+c1.a;
c.b=b+c1.b;
return c;
}
comp & comp::operator =(comp & c)// I tried with return type as void also
{
cout<<"in operator ="<<endl;
a=c.a,b=c.b;
return *this;
}
int main()
{
comp c1,c2(2,3),c3;
c3=c2+c1;
cout<<c3;
comp c4=c3+c1;
cout<<c4;
int i=c4;
cout<<i;
return 0;
}
Lets take this line
comp c4=c3+c1;
The c3+c1 operation returns a temporary object. However, non-constant references can't bind to temporary objects, and your copy-constructor takes its argument as a non-constant reference.
The fix is simple, change the copy-constructor and copy-assignment operator to take their arguments as constant references instead, e.g.
comp(const comp& c);
Note that using a non-constant reference argument in e.g. a copy-constructor still makes it possible to use it, you just have to pass actual non-temporary objects to it, like
comp c1;
comp c2 = c1; // Should work with non-constant reference
By default some copy operations are optimized. To make sure that no copy optimization is used you should use proper compiler flag. For gcc try to use: -no-eligible-constructors

Initialization of member array objects avoiding move constructor

I'm trying to create and initialize a class that contains a member array of a non-trivial class, which contains some state and (around some corners) std::atomic_flag. As of C++11 one should be able to initialize member arrays.
The code (stripped down to minimum) looks like this:
class spinlock
{
std::atomic_flag flag;
bool try_lock() { return !flag.test_and_set(std::memory_order_acquire); }
public:
spinlock() : flag(ATOMIC_FLAG_INIT){};
void lock() { while(!try_lock()) ; }
void unlock() { flag.clear(std::memory_order_release); }
};
class foo
{
spinlock lock;
unsigned int state;
public:
foo(unsigned int in) : state(in) {}
};
class bar
{
foo x[4] = {1,2,3,4}; // want each foo to have different state
public:
//...
};
If I understand the compiler output correctly, this seems not to construct the member array, but to construct temporaries and invoke the move/copy constructor, which subsequently calls move constructors in sub-classes, and that one happens to be deleted in std::atomic_flag. The compiler output that I get (gcc 4.8.1) is:
[...] error: use of deleted function 'foo::foo(foo&&)'
note: 'foo::foo(foo&&)' is implicitly deleted because the default definition would be ill-formed
error: use of deleted function 'spinlock::spinlock(spinlock&&)'
note: 'spinlock::spinlock(spinlock&&)' is implicitly deleted because [...]
error: use of deleted function 'std::atomic_flag::atomic_flag(const std::atomic_flag&)'
In file included from [...]/i686-w64-mingw32/4.8.1/include/c++/atomic:41:0
[etc]
If I remove the array and instead just put a single foo member inside bar, I can properly initialize it using standard constructor initializers, or using the new in-declaration initialization, no problem whatsoever. Doing the same thing with a member array fails with the above error, no matter what I try.
I don't really mind that array elements are apparently constructed as temporaries and then moved rather than directly constructed, but the fact that it doesn't compile is obviously somewhat of a showstopper.
Is there a way I either force the compiler to construct (not move) the array elements, or a way I can work around this?
Here's a minimal example exposing the problem:
struct noncopyable
{
noncopyable(int) {};
noncopyable(noncopyable const&) = delete;
};
int main()
{
noncopyable f0 = {1};
noncopyable f1 = 1;
}
Although the two initializations of f0 and f1 have the same form (are both copy-initialization), f0 uses list-initialization which directly calls a constructor, whereas the initialization of f1 is essentially equivalent to foo f1 = foo(1); (create a temporary and copy it to f1).
This slight difference also manifests in the array case:
noncopyable f0[] = {{1}, {2}, {3}, {4}};
noncopyable f1[] = {1, 2, 3, 4};
Aggregate-initialization is defined as copy-initialization of the members [dcl.init.aggr]/2
Each member is copy-initialized from the corresponding initializer-clause.
Therefore, f1 essentially says f1[0] = 1, f1[1] = 2, .. (this notation shall describe the initializations of the array elements), which has the same problem as above. OTOH, f0[0] = {1} (as an initialization) uses list-initialization again, which directly calls the constructor and does not (semantically) create a temporary.
You could make your converting constructors explicit ;) this could avoid some confusion.
Edit: Won't work, copy-initialization from a braced-init-list may not use an explicit constructor. That is, for
struct expl
{
explicit expl(int) {};
};
the initialization expl e = {1}; is ill-formed. For the same reason, expl e[] = {{1}}; is ill-formed. expl e {1}; is well-formed, still.
Gcc refuses to compile list-initialization of arrays of objects with virtual destructor up to version 10.2. In 10.3 that was fixed.
E.g. if noncopyable from #dyp answer had a virtual destructor, gcc fails to compile line:
noncopyable f0[] = {{1}, {2}, {3}, {4}};
arguing to deleted copy and move c-rs. But successfully compiles under 10.3 and higher.

Resources