2

Let's look at some trivially move-constructible and (not trivially) copy-constructible (but still copy-constructible) user-defined (class) type A:

struct A
{
    A() = default;
    A(A const &) {}
    A(A &&) = default;
};

Then moving of A (move-construction or move-assignment) literally perfroms the following: a source bitwise copied to a destination, despite of operation's name "moving". During trivial moving right hand side is (formally) not const, but triviality of the whole operation requires (actual) non-mutability of right hand side, isn't it? On my mind it means, that trivial copy-operation and trivial move-operation are exactly the same in their deep nature (in terms of memory, memory-layout, bits etc). Am I right?

If it is so, then I think, if I see trivially move-constructible, but not trivially copy-constructible type in user code, then I evidently see some antipattern. Am I right?

Is there an example of such artificial but usable type, which is not trivially copy-constructible/assignable, but trivially move-constructible/assignable?

Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169
  • 3
    "*On my mind it means, that trivial copy-operation and trivial move-operation are exactly the same in their deep nature (in terms of memory, memory-layout, bits etc). Am I right?*" Yes. – ildjarn Sep 21 '16 at 08:44
  • Uh, that question isn't at all related to this one. – Barry Sep 21 '16 at 08:49
  • 2
    "Edge" case: A type that is not copy-constructible can be trivially move-constructible and not trivially copy-constructible (a deleted copy constructor is (obviously) not trivial). – Holt Sep 21 '16 at 08:52
  • "*In light of above conclusion there is no need to dispatch trivially copy/move-constructible/assignable cases separately*"... What does it matter? "Dispatching" a trivial "copy/move" is *no different* from "dispatching" a non-trivial one. You just copy it: `A a2(a)` works for any type `A` which is copy-constructible. The only question you need to ask is whether it actually is copy-constructible. – Nicol Bolas Sep 22 '16 at 05:00
  • @NicolBolas I mean dispatching like [this](https://github.com/eggs-cpp/variant/blob/master/include/eggs/variant/detail/storage.hpp#L268), but for handling trivial cases. If for any of alternatives `!std::is_trivially_copy/move_*` holds for some special function, then there is a need to provide implementation of corresponding special function instead of defaulting it. – Tomilov Anatoliy Sep 22 '16 at 06:39
  • @Orient: And why do you need to "handle trivial cases" any differently from non-trivial cases? – Nicol Bolas Sep 22 '16 at 12:53
  • @NicolBolas to leave trivial operations which can be trivial – Tomilov Anatoliy Sep 22 '16 at 15:21
  • @Orient: But you have to use SFINAE for that regardless. You have to apply it to each special member function individually. So what does it matter if the SFINAE `enable_if` uses `is_trivially_copyable` or `is_trivially_copy/move_assignable/constructible`? You still have to do it once for each of the 5 special member functions. – Nicol Bolas Sep 22 '16 at 15:23
  • @NicolBolas For `std::variant` implementation you have to use enabler (specialized class template as empty base). You can't make constructor or assignment operator defaulted conditionally another way (such as SFINAE). – Tomilov Anatoliy Sep 22 '16 at 16:49
  • @Orient: Then your question is really whether you need to account for the various combinations of functionality on user types. Then I have one simple question to ask: for what purpose? That is, if you did so correctly, what would that provide to the user of the type? What is the practical benefit of forwarding these things to the user? I kinda have an idea of what that reason would be, but I can't give you an answer unless you actually say it. – Nicol Bolas Sep 22 '16 at 17:06
  • @NicolBolas I see the question sounds as theoretical. Only application I can imagine is C++-way to interpret network packets data. `struct packet { uint8_t address; uint8_t function; union { payload1_t p1; payload2_t p2; }; };` - instead of anonymous `union` I can use trivial variant here. Maybe some operations on packets would require triviality of corresponding special functions. Having overloaded `operator .` for variant it can be even improved. – Tomilov Anatoliy Sep 22 '16 at 17:48
  • @Orient: "*I see the question sounds as theoretical.*" But it only has one practical benefit. And that benefit is spelled "Trivially Copyable." Also, you asked for a "artificial but useful" example rather than just "theoretical". – Nicol Bolas Sep 22 '16 at 18:17
  • @NicolBolas I suspect I can't overcome language barrier. – Tomilov Anatoliy Sep 22 '16 at 18:22
  • @NicolBolas "I kinda have an idea of what that reason would be". Should unary `std::variant< T >` fully mimic `T`? Likely "yes"? If it is so, then why n-ary shouldn't? Also I can use `std::variant< Ts... >` to examine *sum* of `Ts...` types by means of type traits, i.e. to infer "integral" characteristics of them. Very probably such extremally generic variant is not practical and hard(ly) to be implemented. – Tomilov Anatoliy Sep 23 '16 at 03:44
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/124000/discussion-between-nicol-bolas-and-orient). – Nicol Bolas Sep 23 '16 at 03:56

1 Answers1

2

Is there a use case where a type could have a trivial copy constructor without having a trivial move constructor? Sure.

For example, it could be useful to have a pointer wrapper type that will always be empty when moved from. There's no reason for the copy constructor to be non-trivial, but the move constructor would have to set the old value to NULL.

template<typename T>
class empty_on_move
{
  T *ptr_;

public:
  empty_on_move(const empty_on_move&) = default;
  empty_on_move(empty_on_move &&other) : ptr_(other.ptr_) {other.ptr_ = nullptr;}
...
};

empty_on_move doesn't own the object, which is why it's OK to have multiple copies of it. It exists solely to make sure that when you move from it, the pointer is in a well-understood state. As such, is_trivially_copy_constructible<empty_on_move<T>> is true, while is_trivially_move_constructible<empty_on_move<T>> is false.

It would mainly be for use inside of other classes which want to give pointers that particular behavior. That way, you don't have to explicitly write code into their move constructors/assignments to NULL those fields out.


That being said, you're really asking the wrong question. Why? Because the answer doesn't matter.

The only time that the triviality of a copy/move constructor/assignment matters is when you need the type to be Trivially Copyable. It is that property which permits the use of memcpy and such things, not the trivially of the individual operations. The trivially copyable property requires that the copy/move constructor/assignment and destructors all are trivial (in C++14, the requirement is that they can be trivial or deleted, but at least one must be non-deleted).

If you're writing a wrapper around some type (or writing a sum/product type), and you want to expose the properties of that type, you only need concern yourself with exposing Trivial Copyability. That is, if T (or Ts...) is trivially copyable, then your type should be trivially copyable too.

But otherwise, you shouldn't feel the need to have a trivial copy constructor just because T does.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Why to be trivially copyable type should have trivial destructor? Logically it is not required at all. – Tomilov Anatoliy Sep 22 '16 at 19:17
  • 1
    @Orient: A type for which all of those operations are trivial is a type which, as far as C++'s object model is concerned, has no state beyond the value of its bits. As such, bitwise copying to any storage space is a reasonable action. If a destructor is non-trivial, then the destructor may be doing something that makes the object more than just a block of bits. Therefore, bitwise copying is no longer a reasonable action. Consider `lock_guard`; it has deleted copy/move constructors/assignment. But it has a non-trivial destructor. Should you be allowed to bitwise copy it? *Of course not*. – Nicol Bolas Sep 22 '16 at 19:29
  • Prohibition to copy `lock_guard` is clearly expressed in deleted copy/move constructors/assignment operators. Triviality of destructor is not related thing. `!std::is_copy_constructible` impy `!std::is_trivially_copy_constructible`. – Tomilov Anatoliy Sep 22 '16 at 19:37
  • @Orient: "*Prohibition to copy lock_guard is clearly expressed in deleted copy/move constructors/assignment operators.*" Yes, that's true (one of the 4 must not be deleted). But the overall concept is as I explained. This is a [question that has been asked and answered](http://stackoverflow.com/q/22494344/734069). – Nicol Bolas Sep 22 '16 at 20:01