Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does a constexpr move constructor ever make sense?

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?

like image 237
Danra Avatar asked Feb 20 '17 15:02

Danra


People also ask

What is the point of a move constructor?

A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying.

Can a constructor be constexpr?

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.

Are move constructors automatically generated?

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.

Why constexpr instead of define?

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


2 Answers

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.

like image 55
T.C. Avatar answered Oct 03 '22 19:10

T.C.


This doesn't compile, since despite calling std::move on c1, 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.

like image 22
Barry Avatar answered Oct 03 '22 19:10

Barry