Search code examples
c++move-semanticsmove-constructor

Why does moving a const ref return value into another function still move-construct the object?


In the following example the function Table::get_subtable() is returning a const reference to an internal subobject (SubTable). This value is then std::moved and passed into a static factory function from_subtable that takes the subtable by value. I'm honestly very surprised that this worked and even moved the value (check output)!

LiveDemo

#include <iostream>


struct SubTable {

    SubTable() = default;

    SubTable(SubTable&& other) {
        std::cout << "Moved subtable" << std::endl; 
    }

    SubTable(const SubTable&) = default;

   static auto from_subtable(SubTable subtable) -> SubTable; 
};


auto SubTable::from_subtable(SubTable subtable) -> SubTable
{
    return subtable;
}


struct Table {
    SubTable _subtable;

    auto get_subtable() const -> const SubTable&;
};

auto Table::get_subtable() const -> const SubTable& {
    return _subtable;
}



int main()
{
    Table hello;

    auto subtable = SubTable::from_subtable(std::move(hello.get_subtable()));
}

Output:

Moved subtable

I would have assumed that the move constructor couldn't construct objects from const&& (which I suppose is what I passed to from_subtable after the std::move of the return value). My intuition was that this should result in a compiler error since I can't alter a const reference. Where was I mislead?


Solution

  • you are misinterpreting where the move is happening.

    auto SubTable::from_subtable(SubTable subtable) -> SubTable
    {
        return subtable;
    }
    
    auto subtable = SubTable::from_subtable(std::move(hello.get_subtable()));
    

    std::move(hello.get_subtable()) produces a const&& which is used to copy construct the subtable argument.

    Then, as function arguments cannot be RVOed see why here, the subtable argument is moved into the return slot. (the return slot is the subtable variable in main)

    If you delete the copy constructor then the code won't compile.

    godbolt demo showing the copy then the move

    const& can bind to all of &, && and const&& arguments, which is why the code can compile with only a copy constructor but will fail to compile with only a move constructor.