1

I guess that this question has been asked but I simply haven't found similar answers yet.

Let's see a contrived example.

#include <iostream>
#include <string>
#include <cassert>

#define LOG \
  std::cout << __PRETTY_FUNCTION__ << ' ' << str_ << '\t' << this << std::endl;

    class Test {
     public:
      Test(std::string const &str) : str_(str) { LOG; }
      Test(Test const &rhs) : str_(rhs.str_) { LOG; }
      // Test(Test &&rhs) = delete;
      Test(Test &&rhs) : str_(std::move(rhs.str_)) { LOG; }
      // Test &operator=(Test const &rhs) {
      // if (this == &rhs) return this;
      //   str_ = rhs.str_;
      //   LOG;
      //   return *this;
      // }
      // Test &operator=(Test &&rhs) = delete;
      // Test &operator=(Test &&rhs) {
      //   assert(this != &rhs);
      //   str_.swap(rhs.str_);
      //   LOG;
      //   return *this;
      // }
      ~Test() { LOG; }
      static Test gen() {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        return Test("DUMMY");
      }

     private:
      std::string str_;
    };

    int main(void) {
      {
        Test test = Test("test");
        Test t(test);
      }
      std::cout << std::endl;
      { Test t0(Test("t0")); }
      std::cout << std::endl;
      {
        Test t1 = Test{"t1"};
        /// t1 = Test("t2");
      }
      std::cout << std::endl;
      { Test t(Test::gen()); }
      return 0;
    }

When I explicitly Test(Test &&test) = delete; and compiled this segment with clang-3.4 it emit errors like below:

simple.cc:29:12: error: call to deleted constructor of 'Test'
    return Test("DUMMY");
           ^~~~~~~~~~~~~
simple.cc:12:3: note: function has been explicitly marked deleted here
  Test(Test &&rhs) = delete;

When I customize the move constructor like the one in the src, it compiles. However

However NO move constructor is even called(here is the result):

Test::Test(const std::string &) test    0x7fff2e513448
Test::Test(const Test &) test   0x7fff2e513430
Test::~Test() test  0x7fff2e513430
Test::~Test() test  0x7fff2e513448

Test::Test(const std::string &) t0  0x7fff2e513428
Test::~Test() t0    0x7fff2e513428

Test::Test(const std::string &) t1  0x7fff2e513410
Test::~Test() t1    0x7fff2e513410

static Test Test::gen()
Test::Test(const std::string &) DUMMY   0x7fff2e5133f8
Test::~Test() DUMMY 0x7fff2e5133f8

Later I found it may result from return value optimization so I compiled again with -fno-elide-constructors. This time the result is as below:

Test::Test(const std::string &) test    0x7fff9590cd90
Test::Test(Test &&) test    0x7fff9590cd98
Test::~Test()   0x7fff9590cd90
Test::Test(const Test &) test   0x7fff9590cd78
Test::~Test() test  0x7fff9590cd78
Test::~Test() test  0x7fff9590cd98

Test::Test(const std::string &) t0  0x7fff9590cd68
Test::Test(Test &&) t0  0x7fff9590cd70
Test::~Test()   0x7fff9590cd68
Test::~Test() t0    0x7fff9590cd70

Test::Test(const std::string &) t1  0x7fff9590cd48
Test::Test(Test &&) t1  0x7fff9590cd50
Test::~Test()   0x7fff9590cd48
Test::~Test() t1    0x7fff9590cd50

static Test Test::gen()
Test::Test(const std::string &) DUMMY   0x7fff9590ccf0
Test::Test(Test &&) DUMMY   0x7fff9590cd28
Test::~Test()   0x7fff9590ccf0
Test::Test(Test &&) DUMMY   0x7fff9590cd30
Test::~Test()   0x7fff9590cd28
Test::~Test() DUMMY 0x7fff9590cd30

It invoked move constructors as expected. However explicitly deleting move constructor still causes the program to fail to compile.

my questions:

  • Why it reports errors when the move constructor is deleted? Why DIDN'T it match copy constructor instead(although not exact match as the move constructor did)? C++03 has no rvalue-reference, what was the compilers solutions then? In addition, I read another question and I suppose that in my case since I specified a user-declared copy constructor the move constructor shouldn't be default(thereby it should be deleted?) I also realized that n3376 has similar writes about this. Is clang accord with the standard?

  • What has Return Value Optimization done here? Especially, why Test::Test(const Test &) is called rather than Test::Test(Test &&)?(Sorry I didn't notice that there's only one copy constructor invocation in the RVO version results)

  • I also noticed similar issues for move assignment(if it's deleted, then compiles complains error). So what's going on here?

Community
  • 1
  • 1
Hongxu Chen
  • 5,240
  • 2
  • 45
  • 85
  • 1
    Deleted special member functions participate in overload resolution, and a move or a copy constructor is required, even if RVO elides the copies. – juanchopanza Sep 12 '14 at 15:35
  • 3
    If you provide a copy ctor but no move ctor, the move ctor is *not* deleted. It is instead *not declared at all.* – Angew is no longer proud of SO Sep 12 '14 at 15:37
  • 3
    The program must be valid without the optimisation, regardless of whether the optimisation is performed. – Joseph Mansfield Sep 12 '14 at 15:45
  • @dyp remove that question, sorry for the mistake. – Hongxu Chen Sep 12 '14 at 16:42
  • @Angew But what's the meaning of `not declared`? Will not declared move constructor be invoked? – Hongxu Chen Sep 12 '14 at 16:44
  • 2
    @HongxuChen: No, a function that neither exists nor is said to exist cannot be invoked. – Mooing Duck Sep 12 '14 at 16:48
  • @MooingDuck So `deleted` is not the same as `not declared`? Why so ood?(I was thinking that if a function is not user-defined nor default then it was deleted.) – Hongxu Chen Sep 12 '14 at 16:54
  • @HongxuChen A deleted function is declared, but has no body - it is an error to call it. A not declared function simply does not exist. Big and important difference. – Angew is no longer proud of SO Sep 12 '14 at 17:01
  • @MooingDuck So will `not declared` function be generated in the executable? – Hongxu Chen Sep 12 '14 at 17:06
  • 1
    @HongxuChen: No, a not declared function is never mentioned in the code, and doesn't exist in any way, shape, or form, period. _It does not exist_. a `deleted` function "exists", but reports an error any time you try to use it. (You're right that it's very odd and stupid and I think `deleted` is poorly named.) – Mooing Duck Sep 12 '14 at 17:11
  • Note that the rules changed in C++14: Here, the move constructor will be implicitly declared, implicitly defined as deleted and *not* take part in overload resolution. – dyp Sep 12 '14 at 17:17

1 Answers1

1
  1. Even when the compiler elides a copy or move, the language still requires that the function exist and be accessible. This is to make the program always consistently compile regardless of whether a compiler elides or doesn't elide a particular copy/move.

  2. test has a name so it's automatically an lvalue and cant' be moved from without an explicit std::move. Thus the compiler has to copy test into t.

  3. I don't understand what you expect to happen - I don't see any reason it wouldn't call the move assignment operator.

The short version is, if the move or copy constructor are notionally called before optimization, they have to exist and be accessible. That's just the requirement of the language standard.

Mark B
  • 95,107
  • 10
  • 109
  • 188