Why is the move constructor neither declared nor deleted with clang?
Asked Answered
J

2

17

Consider the following classes.

struct with_copy {
    with_copy() = default;
    with_copy(with_copy const&) {}
    with_copy& operator=(with_copy const&) { return *this; }
};

struct foo {
    with_copy c;
    std::unique_ptr<int> p;
};
  • Does with_copy have a copy constructor? Yes. It was explicitly defined.
  • Does with_copy have a move constructor? No. The explicit copy constructor prevents it from being generated.
  • Does with_copy have a deleted move constructor? No. Not having a move constructor is not the same as having a deleted one. A deleted move constructor would make an attempt to move ill-formed instead of degenerating to a copy.
  • Is with_copy copyable? Yes. Its copy constructor is used for copies.
  • Is with_copy movable? Yes. Its copy constructor is used for moves.

... and now the tricky ones.

  • Does foo have a copy constructor? Yes. It has a deleted one, as its defaulted definition would be ill-formed due to invoking unique_ptr's deleted copy constructor.
  • Does foo have a move constructor? GCC says yes, clang says no.
  • Does foo have a deleted move constructor? Both GCC and clang say no.
  • Is foo copyable? No. Its copy constructor is deleted.
  • Is foo movable? GCC says yes, clang says no.

(The behaviour is similar when one considers assignment instead of construction.)

As far as I can see, GCC is correct. foo should have a move constructor that performs a move on each member, which in with_copy's case degenerates to a copy. Clang's behaviour seems quite ridiculous: I have an aggregate with two movable members, and yet my aggregate is an immovable brick.

Who's right?

Jemina answered 16/12, 2013 at 10:33 Comment(7)
technically, I think the Standardese is that the with_copy copy constructor is user-providedHarriot
In n3485, [class.copy]/11 says the move ctor is deleted if "for the move constructor, a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable." And with_copy is not trivially copyable.Crimea
@DyP no compiler makes a deleted move constructor though :S (And ffs those rules are ridiculous)Jemina
Because an implicitly deleted move constructor is not declared, /9.Crimea
Btw, this changes in C++1y. The recent draft in the github repo says foo has a move-constructor, as far as I understand it.Crimea
@DyP can you collect your findings as an answer?Jemina
DR1402Crimea
C
8

C++11, or rather n3485, [class.copy]/9:

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if

  • X does not have a user-declared copy constructor,
  • X does not have a user-declared copy assignment operator,
  • X does not have a user-declared move assignment operator,
  • X does not have a user-declared destructor, and
  • the move constructor would not be implicitly defined as deleted.

and /11:

An implicitly-declared copy/move constructor is an inline public member of its class. A defaulted copy/ move constructor for a class X is defined as deleted (8.4.3) if X has:

  • [...]
  • for the copy constructor, a non-static data member of rvalue reference type, or
  • for the move constructor, a non-static data member or direct or virtual base class with a type that does not have a move constructor and is not trivially copyable.

As with_copy is not trivially copyable, foo will have no move-constructor (it would be defined as deleted, therefore it won't be implicitly declared).


C++1y, or rather github repo commit e31867c0 from 2013-11-12; incorporating DR1402:

/9:

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if

  • X does not have a user-declared copy constructor,
  • X does not have a user-declared copy assignment operator,
  • X does not have a user-declared move assignment operator, and
  • X does not have a user-declared destructor.

and /11:

An implicitly-declared copy/move constructor is an inline public member of its class. A defaulted copy/ move constructor for a class X is defined as deleted (8.4.3) if X has:

  • [...]
  • for the copy constructor, a non-static data member of rvalue reference type.

A defaulted move constructor that is defined as deleted is ignored by overload resolution (13.3, 13.4).

Here, foo will have a move-constructor.

Crimea answered 16/12, 2013 at 11:2 Comment(2)
std::is_move_constructible<T> is defined as is_constructible<T, T&&> which in turn just checks if T(declval<T>()) is well-formed. So a type with no declared move ctor (or a move ctor that is ignored during overload resolution) is still "movable" in the sense of std::is_move_constructible.Crimea
CWG 1402 is (imho) the most important bug fix to C++11 there is. Tip-of-trunk clang implements it, even in -std=c++11 mode.Wainwright
S
7

I'm not quite sure what you tested but it foo is surely both move assignable and move constructible. Admittedly, this doesn't say anything about a move constructor or a move assignment being accessible, just that construction or assignment from an rvalue works. Both clang (clang version 3.5 (trunk 196718)) and gcc (gcc version 4.9.0 20131031 (experimental) (GCC)) agree with this assessment. This is the complete source I tried:

#include <iostream>
#include <type_traits>
#include <memory>

struct with_copy {
    with_copy() = default;
    with_copy(with_copy const&) {}
    with_copy& operator=(with_copy const&) { return *this; }
};

struct foo {
    with_copy c;
    std::unique_ptr<int> p;
};

int main()
{
    std::cout << "move constructible: "
              << std::is_move_constructible<foo>::value << '\n';
    std::cout << "move assignable: "
              << std::is_move_assignable<foo>::value << '\n';
    foo f0;
    foo f1 = std::move(f0);
    f0 = std::move(f1);
}
Selfregard answered 16/12, 2013 at 10:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.