4
votes

I have code here:

#include <string>
#include <iostream>
#include <initializer_list>

template <typename T>
class Test
{
public:
  Test(std::initializer_list<T> l)
  {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }

  Test(const Test<T>& copy)
  {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
  Test(Test&&) = delete;
  Test() = delete;
};

void f(const Test<Test<std::string>>& x)
{
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void f(const Test<std::string>& x)
{
  std::cout << __PRETTY_FUNCTION__ << std::endl;
  f(Test<Test<std::string>>{x});
}

int main()
{
  Test<std::string> t1 {"lol"};
  f(t1);
  return 0;
}

I try to compile this with GCC 7.3.0 on my linux mint 19 with command:

g++ -std=c++11 -O0 test.cpp -o test -Wall -pedantic

Compilation fails with error that this call:

f(Test<Test<std::string>>{x});

Requires move constructor, but it's deleted. I always thought that move and copy constructors are equivalent in terms of compilation cause rvalue can be bound to const reference, but overload with explicitly defined rvalue reference is just prioritized in overload resolution. This is first time i see that compiler actually requires move constuctor and won't just use copy instead. Why? Am i missing something?

2
A deleted function is declared. It will be included in the overload set during name lookup, so you are actually trying to call the deleted function. If you defined a copy constructor and simply didn't declare a move constructor at all, you would get the behaviour you expect. - BoBTFish
@BoBTFish in that case my class will have move defined cause compiler will generate it for me and i will have one. With delete i forbid compiler to generate one. - toozyfuzzy
If you have a user-defined copy constructor, this inhibits the compiler providing the move constructor. See this table. - BoBTFish

2 Answers

6
votes

You declared the move constructor explicitly and marked it as delete, it'll be selected by overload resolution, and then cause the error.

If both copy and move constructors are provided and no other constructors are viable, overload resolution selects the move constructor if the argument is an rvalue of the same type (an xvalue such as the result of std::move or a prvalue such as a nameless temporary (until C++17)), and selects the copy constructor if the argument is an lvalue (named object or a function/operator returning lvalue reference).

Note that the deleted implicitly-declared move constructor is ignored by overload resolution, but the explicitly-declared one won't.

The deleted implicitly-declared move constructor is ignored by overload resolution (otherwise it would prevent copy-initialization from rvalue). (since C++14)

5
votes

I always thought that move and copy constructors are equivalent in terms of compilation cause rvalue can be bound to const reference, but overload with explicitly defined rvalue reference is just prioritized in overload resolution.

You are correct in this assumption. If you have an overload set that takes a const& and a && the && version will be preferred over the const& if you have an rvalue. The issue here is that your && version is marked as deleted. This means you are explicitly stating you don't want to be constructable from a temporary and the code will fail to compile.

In this case if you don't want the class to be movable then you can just get rid of

Test(Test&&) = delete;

Since the presence of your user defined copy constructor will remove the compilers automatically generated move constructor.