bielu000
bielu000

Reputation: 2241

Class seems to be movable with user defined destructor

I'm trying to figure out why this code does not compile? I've created user defined destructor, so the move ctor and move assignment operator should not be created. So why complication fails, saying that my class does not fulfill the requirements?

#include <iostream>
#include <concepts>
#include <type_traits>

template<class T>  requires (!std::movable<T>)
struct Singleton
{

};

struct Widget 
{
  ~Widget() = default;
};

int main()
{
  auto x = Singleton<Widget>{};
}

Edit. The same goes for this version.

#include <iostream>
#include <concepts>
#include <type_traits>

extern void fun();

template<class T>  requires (!std::movable<T>)
struct Singleton
{

};

struct Widget 
{
  ~Widget() 
  {
    fun();
  } 
};

int main()
{
  auto x = Singleton<Widget>{};
}

Error message

:23:28: note: constraints not satisfied : In substitution of 'template requires !(movable) > struct Singleton [with T = Widget]': :23:28: required from here :8:8: required by the constraints of 'template requires !(movable) struct Singleton' :7:30: note: the expression '!(movable) [with T = Widget]' evaluated to 'false' 7 | template requires (!std::movable)

Compiled used gcc (trunk) on godbolt. The only compilation flag I've used is -std=c++20

https://godbolt.org/z/sb535b1qv

Upvotes: 1

Views: 382

Answers (1)

There is no move constructor in either example. But that's not what std::movable checks. It defers the check you are intesteded in to std::move_constructible. That concept merely checks that an object can be both direct-initialized and copy-initialized from an rvalue.

And a copy constructor was able to initialize from an rvalue since the days of C++98. And it still works with the addition of move operations. That's why the trait is satisfied. Widget a; Widget b = std::move(a); is still valid, even in the absence of a move constructor, because the compiler provided copy constructor makes this possible.

The compiler not generating a move constructor was a design decision made to keep old code working when it made the move to C++11. Such code would have adhered to the rule of three, and so swapping the old copy for a compiler generated move was deemed risky. But old code could still initialize objects from rvalues, so the copy constructor retained its old function.


It is noteworthy that the trait may work as one expects in the future. A defaulted copy constructor being defined here is a deprecated feature:

[class.copy.ctor]

6 If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted ([dcl.fct.def]). The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor ([depr.impldec]).

Upvotes: 5

Related Questions