1

I'm new to SO so let me know if I need to change anything. I did my best to be as thorough and provide example code. I know that many similar questions have been asked, I was unable to find one matching my specific problem though.

Furthermore, I'm aware that what I'm doing is not something one would do in 'real' code, I'm just trying to get a better understanding of r/l/p/x..values.

I have a base and a derived class, both having the default, copy, and move constructors. Now I want to have the copy constructor of the derived class calling the move constructor of the base class.

class Base
{
public:
    Base(){ std::cout << "default base constructor called.\n"; }

    Base(Base const &other) { std::cout << "copy base constructor called.\n"; }

    Base(Base &&tmp) { std::cout << "move base constructor called.\n"; }
};

And basically the same for the derived class:

class Derived : public Base
{
public:
    Derived(){ std::cout << "default derived constructor called.\n";}

    Derived(Derived const &other)
    :
        Base(std::move(other))   // here I want to call Base(Base &&tmp)
    {
        std::cout << "copy derived constructor called.\n";
    }

    Derived(Derived &&tmp)
    :
        Base(std::move(tmp))     // correctly calls Base(Base &&tmp)!
    {
        std::cout << "move derived constructor called.\n";
    }
};

So in my main function, I now want to call the copy constructor, which then calls the move constructor of the base class.

int main()
{
    Derived der{};
    Derived der_copy{ der };
    Derived der_move{ std::move(der) };
}

The output I would get is this:

default base constructor called.
default derived constructor called.
copy base constructor called.         <-- why not move?
copy derived constructor called.
move base constructor called.
move derived constructor called.

I was expecting the following:

default base constructor called.
default derived constructor called.
move base constructor called.
copy derived constructor called.
move base constructor called.
move derived constructor called.

So when I use std::move(tmp) in the derived move constructor (so on Base &&tmp) that the base move constructor is called, but when I use std::move(other) in the derived copy constructor (so on Base const &other) that the base copy constructor is called?

Tbh, this seems so strange that I'm afraid that I just made a mistake in my code, I checked everything multiple times but I can't seem to get the move base constructor called in the case above...

Thanks for your help!

Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55
picklepick
  • 1,370
  • 1
  • 11
  • 24
  • you're using std::move on a const ref so it will return const ref but your move constructor takes non const object so compile recast it back to const reference and call copy constructor – unknown.prince Dec 08 '19 at 14:11
  • ahh ok, so what would be the best way to handle this if I do not want to change the signature of the constructors? – picklepick Dec 08 '19 at 14:14
  • `Derived der_copy{ der };` why should it call move constructor? – macroland Dec 08 '19 at 14:17
  • He is thinking so maybe because he calls std::move in copy constructor. – unknown.prince Dec 08 '19 at 14:18
  • That's the 3rd call in main, which works as expected with move constructors. – macroland Dec 08 '19 at 14:22
  • @Evg, like I stated I'm aware that this is *not* how it *should* be, however, I would like to simply improve my understanding of what happens *if* it's done. – picklepick Dec 08 '19 at 14:30
  • @rezi, it will potentially destroy the original object if it passed not by const-ref, but by ref. – Evg Dec 08 '19 at 14:31

2 Answers2

3

In the copy constructor

Derived(const Derived& other)

std::move(other) will result in an xvalue expression of type const Derived&&.

This is a legal but somewhat weird type: std::move(other) is a temporary object, but you can't move from it, because it is constant. Such references have a limited number of use cases. See the declarations of std::as_const and std::ref for one particular example.

const Derived&& cannot bind to Base&&, that's why during the overload resolution between

Base(const Base&)
Base(Base&&)

the former is chosen by the compiler.

At the risk of getting undefined behaviour, you can cast constness away and write

Derived(const Derived& other) : Base(std::move(const_cast<Derived&>(other))) {}

to call the move constructor of Base. But don't do it in real code.

Evg
  • 25,259
  • 5
  • 41
  • 83
1

You need to change your Base class like this:

Base(const Base &&tmp) { std::cout << "move base constructor called.\n"; }

bhristov
  • 3,137
  • 2
  • 10
  • 26
  • How can it be const if you are moving it? – macroland Dec 08 '19 at 14:29
  • So if I do not want to change the signature, what would be the best way to change the rest of the code? Could I use a cast or something in Derived(Base &&tmp)? – picklepick Dec 08 '19 at 14:31
  • 1
    @macroland, const rvalue references `const&&` are allowed. They are not very useful, that's why they are rarely seen in practice. You can't move from `const&&`, so `Base(const Base &&tmp)` is not really a move constructor. – Evg Dec 08 '19 at 14:38
  • 2
    @rezi, you can `Base(std::move(const_cast(other)))` at the risk of getting undefined behaviour. – Evg Dec 08 '19 at 14:41
  • @Evg thank you! I'll do some more research but this part makes sense now – picklepick Dec 08 '19 at 14:46
  • https://stackoverflow.com/questions/10770181/should-a-move-constructor-take-a-const-or-non-const-rvalue-reference – macroland Dec 08 '19 at 15:42