Does it ever make sense to have a constexpr
move constructor?
For example, consider the following:
#include <array>
class C
{
public:
constexpr C(std::array<int, 3> ar) : m_ar{ar} {}
constexpr C(C&& other) : m_ar{std::move(other.m_ar)} { }
private:
std::array<int, 3> m_ar;
};
int main()
{
constexpr C c1 {{{1, 2, 3}}};
constexpr C c2{std::move(c1)};
return 0;
}
This doesn't compile, since despite calling std::move
on c1
, the compiler deduces it needs to use the (implicitly deleted) copy constructor, not the move constructor. I'm not sure why.
But if I remove the constexpr
from c1
, then it becomes unusable by the constexpr
move constructor.
Is there any way to get this to work? Or is this a bad example for a constexpr
move constructor, but there are good examples? Or, is it just always wrong to have a constexpr
move constructor?
A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying.
A constexpr function must accept and return only literal types. A constexpr function can be recursive. It can't be virtual. A constructor can't be defined as constexpr when the enclosing class has any virtual base classes.
If a copy constructor, copy-assignment operator, move constructor, move-assignment operator, or destructor is explicitly declared, then: No move constructor is automatically generated. No move-assignment operator is automatically generated.
#define directives create macro substitution, while constexpr variables are special type of variables. They literally have nothing in common beside the fact that before constexpr (or even const ) variables were available, macros were sometimes used when currently constexpr variable can be used.
In principle, a move constructor can be used with a non-const
object whose lifetime started during the evaluation of the constant expression:
// C++14
constexpr int f() {
C a(/* ... */);
C b = std::move(a);
return 0;
}
constexpr int i = f();
Similar things can be done in C++11, e.g.
constexpr C foo(C&& c) {
return std::move(c);
}
constexpr int f() {
return foo(C()), 0;
}
That said, since everything used in a constant expression is required to be trivially destructible, the usefulness of a move constructor is rather limited.
This doesn't compile, since despite calling
std::move
onc1
, the compiler deduces it needs to use the (implicitly deleted) copy constructor
c1
is of type C const
. When you move()
it, that's really a cast to rvalue reference, so you get a C const&&
. Note that it's still const
. When we perform overload resolution, there are three constructors:
C(std::array<int, 3> ); // not viable
C(C&& ); // not viable
C(C const& ) = delete; // viable!
C const&&
can't bind to C&&
for the same reason that C const&
can't bind to C&
. We're left with just the copy constructor, which is implicitly deleted.
Having a constexpr move constructor could make sense - but the object you're "moving" from can't really be moved from, because it's presumably const
. You could add a const
move constructor:
constexpr C(C const&& other) : m_ar(other.m_ar) { }
This is a glorified copy constructor, but it allows the syntax you want. May as well just allow copying.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With