11

Imagine that I have a class A that is cheap to move and expensive to copy. It could look like

class A {
  public: 
    [...]

  private:
    HeavyClass m;
};

For this class, I would like an static verification that the class (1) is move constructible and (2) does not simply use the copy constructor for move construction, independently of whether the move constructor was explicitly declared or not.

Is this possible?

As to why I would like this, consider the following example: At first, the class generates the move constructor automatically and behaves as desired. Then, someone changes the class and adds a destructor, which causes the move constructor not to be implicitly generated and the copy constructor to be used instead.

Therefore, a static_assert would be ideal, but it seems that none of is_move_constructible or is_trivially_move_constructible is helpful here.

Also, I know that it is possible to have A(A&&) = default; for all such classes but a solution with a static_assert would be cleaner and allow for doing the check outside of the class definition (f.i. in other projects relying on this class).

EDIT:
I do not want to forbid copy construction, I would just like to make sure that the move constructor is not using it...

oLen
  • 5,177
  • 1
  • 32
  • 48
  • So just the presence of a move constructor is not enough, you actually need it inspected to make sure it really is moving and not copying? – NathanOliver Feb 08 '17 at 14:13
  • 1
    @NathanOliver I would say it is enough if it has the signature `A(A&&)` and does not simply fall back to using `A(const A&)`. It does not need to be inspected regarding what it does exactly. – oLen Feb 08 '17 at 14:23
  • So is what they do [here](http://en.cppreference.com/w/cpp/language/static_assert) in the swap function what you are looking for? – NathanOliver Feb 08 '17 at 14:24
  • @NathanOliver Not exactly, but it is close. The code in your link would require all move constructors not to throw. But replacing `is_nothrow_move_constructible` by `is_move_constructible` does not help in this regard. – oLen Feb 08 '17 at 14:40
  • Can you just delete copy constructor from your class (and declare default move constructor)? – art Feb 08 '17 at 18:35
  • @art I do not want to forbid copy construction, I just want to make sure that move construction is not using it... – oLen Feb 08 '17 at 22:21
  • 1
    I suspect this is impossible since we can't take the address of the move constructor - we can only query what kinds of things we can pass into the constructor. And rvalues can bind to const lvalue references, so we can't differentiate. – Barry Feb 08 '17 at 22:53

3 Answers3

4

If you can change A to have an indirection, you may do the following:

template <bool>
struct MoveOnly {
    MoveOnly() = default;
    ~MoveOnly() = default;
    MoveOnly(const MoveOnly&) = delete;
    MoveOnly(MoveOnly&&) = default;
    MoveOnly& operator=(const MoveOnly&) = delete;
    MoveOnly& operator=(MoveOnly&&) = default;
};

template <> struct MoveOnly<false> {};

template <bool check = false>
class A_Impl : MoveOnly<check> {
public: 
    // ... as ~A_Impl() {}
    // ...
private:
    HeavyClass m;
};

using A = A_Impl<false>; // Normal case

// The check
static_assert(std::is_move_constructible<A_Impl<true>>::value, "");

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I should make my point more clearly in the question: Is there a way not to forbid copy construction, but make sure that move construction is not using it? – oLen Feb 08 '17 at 22:22
  • 2
    @oLen: That's the point of the template. Make move only for the check, and no restriction in normal mode. – Jarod42 Feb 08 '17 at 22:47
  • Oh, I see, clever! a bit complicated, but it appears to do the job! – oLen Feb 08 '17 at 23:08
  • 2
    Probably want to inherit from `MoveOnly` rather than have it as a member. – Barry Feb 08 '17 at 23:12
0

Generation of copy constructor and copy assignment operator is deprecated when destructor is declared. No need for static assert or templates, this is part of modern C++.

The solution is simply to enable deprecation warnings

Also turn warnings into errors if you have not already done so.

This way you don't have to remember to add static asserts all over. Also, you are still allowed to add destructors, have non-moveable members and inherit from non-moveables as long as you do not copy or move any instances of such objects. No point in restrictions when they don't apply.

With this setup you can try to add this code to you class (a typical regression)

virtual ~A() = default;

If you try to move or copy an instance of A now, then compilation will fail.

Example error message from clang-3.9 -Werror -Wdeprecated

main.cpp:13:13: error: definition of implicit copy assignment
operator for 'A' is deprecated because it has a user-declared 
destructor [-Werror,-Wdeprecated]
    virtual ~A() = default;
            ^
main.cpp:21:7: note: implicit copy assignment operator for
'A' first required here
    b = std::move(a);
      ^
1 error generated.

If you create an instance of A and simply pass it around by const reference, then no complaints from you compiler.

Mathias
  • 1,446
  • 2
  • 16
  • 31
  • This is a good approach, but unluckily if I modify the example I stated above and declare the normal copy constructor instead of the destructor, no move constructor will be generated and no warning will be given that it uses the normal copy constructor... – oLen Feb 10 '17 at 08:22
  • Yes doing special member functions correctly is a tricky topic. That is why I am a big advocate of not doing it. That is the Rule of Zero. Having a whole team following that all the time is impossible without a tool. Adding asserts is just replacing an idiomatic rule with a custom one. Still the developer have to remember something. Unfortunately, declaring the destructor as virtual is required sometime. That is why I like this warnings. I fix those warnings like this: http://stackoverflow.com/a/35127609/212010 – Mathias Feb 10 '17 at 08:39
-1

This is a totally generic maintenance problem. Ideally we want to find a generic solution for this. Manually adding asserts is a poor solution. It is error prone, easy to forget, time consuming and makes code less readable.

One generic solution would be to use a static analyzer to enforce the Rule of Zero for all classes.

Link to SO question about this. However, adding compiler options as described in my other answer is a better solution if you compiler suports it.

Community
  • 1
  • 1
Mathias
  • 1,446
  • 2
  • 16
  • 31
  • Here I do not agree. Running a static analyzer manually is at least as error-prone and less cross-platform than adding asserts manually. Furthermore I do not think that asserts are a poor solution as the main idea is to write them for critical classes where you want to emphasize that move semantics must be supported. But I agree that in the long term it would be better if compilers can do it directly. – oLen Feb 10 '17 at 08:35
  • The static analyzer needs to at least be part of continuous integration. You can also integrate the analyzer in the IDE and run it after each build. Else it is of cause just as manual and easy to forget. (An analyzer is almost a must have, spotting use after move, calling virtuals functions in destructor and so on.) – Mathias Feb 10 '17 at 08:42
  • I integrat `clang-tidy` in the IDE and use `clang -Weverything -Wno-xyx` for IDE code model and run `scan-build` in the CI. It catches tons of old-school C++ and other bad patterns. *Core Guidelines* are implemented by *Clang* and *Visual Studio* themes at great speed. – Mathias Feb 10 '17 at 08:52