Copying parameter invokes deleted constructor when that constructor shouldn't be called

2.1k views Asked by At
#include <memory>

template <typename T>
class Wrapper {
public:
    Wrapper() = delete;
    Wrapper(const Wrapper&) = delete;
    Wrapper(Wrapper&&) = delete;

    ~Wrapper() = default;

    Wrapper(const T&) = delete;
    Wrapper(T&& in) : instance{std::move(in)} {}

    T instance;
};

void foo(Wrapper<std::shared_ptr<int>>) {}

int main() {
    auto ptr = std::make_shared<int>(1);
    foo(std::move(ptr));
}

This has been working in C++17 so I never gave it thought but why does this code try and invoke the move constructor in C++14? Shouldn't it be constructed in place in the function argument? This seems to not be a problem with c++17 but is not compiling with c++14.

The only workaround I see is to make the foo parameter an rvalue, but is there anything I can do to make this work without making the parameter in foo an rvalue in C++14?


My first thought would be that a temporary would have to be constructor in order to be passed to the function but what is even more surprising is that even with -fno-elide-constructors and undeleting the move constructors and copy constructors those do not seem to be called! Is this a bug in gcc and clang both?

See https://wandbox.org/permlink/f6sa5Rm3NxZLy5P1 for the error And see for the strange behavior https://wandbox.org/permlink/Kh6CG4OVbUAjvEZz

1

There are 1 answers

9
NathanOliver On BEST ANSWER

When you call foo(std::move(ptr)); you are not giving it a Wrapper<std::shared_ptr<int>>. So, the compiler generates a temporary and uses that to construct foo's parameter. Now, this can be elided out and we can directly construct a Wrapper<std::shared_ptr<int>> but the move/copy constructor still needs to be accessible, even if it is never called.

With C++17 this no longer happens. We have guaranteed copy elision which means no temporary is ever materialized and instead the parameter is directly constructed.