2
#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

Curious
  • 20,870
  • 8
  • 61
  • 146

1 Answers1

4

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.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • But then why is the move constructor never called? Even with `-fno-elide-constructors` – Curious Sep 14 '17 at 19:10
  • @Curious cosntructor required by language but call can be eliminated by optimization. – Slava Sep 14 '17 at 19:10
  • @Curious Because even though the compiler is smart enough not to make a temporary the language standard says the appropriate constructor must still be accessible. – NathanOliver Sep 14 '17 at 19:11
  • @Slava the `-fno-elide-constructors` suppresses that optimization – Curious Sep 14 '17 at 19:11
  • 1
    @NathanOliver then is the codepath followed after passing the flag that is meant to suppress elision buggy? Because move elision should not be happening when that flag is passed – Curious Sep 14 '17 at 19:12
  • @Curious Not sure. It calls the move constructor [here](http://coliru.stacked-crooked.com/a/d24c421f3d00c937) – NathanOliver Sep 14 '17 at 19:14
  • Very strange, thanks for the answer and that link! But I will wait for an answer that addresses that since that is the main confusing issue for me here – Curious Sep 14 '17 at 19:15
  • @Curious Even your example link prints the move constructor so I'm not sure what your issue is. – NathanOliver Sep 14 '17 at 19:16
  • @NathanOliver got confused.. I thought I had a print statement in the wrong place, nvm the question was folly... – Curious Sep 14 '17 at 19:17