17

I'm trying to write a template that behaves one way if T has a move constructor, and another way if T does not. I tried to look for a type trait that could identify this but have had no such luck and my attempts at writing my own type trait for this have failed.

Any help appreciated.

Kranar
  • 1,136
  • 2
  • 13
  • 24
  • Could you clarify your question a little? Even when a type does not have an explicit move constructor defined, it is still usually possible to construct it as `T t2{ std:: move(t1) };`. That expression will fall back on a copy constructor if one is present. So, what are you asking exactly? Do you want to test specifically if an explicit move constructor has been defined (quite a narrow test)? Or, more broadly, do you want to test whether it can be constructed from an r-value (easy to test, will include almost every type that has a copy constructor)? – Aaron McDaid Jan 09 '15 at 10:56
  • To simplify my question: I guess I'm asking about your words "*... behaves one way if T has a move constructor ...*". Perhaps you mean "*... behaves one way if type T has an explicitly-defined constructor with signature `T :: T(T&&)` ...*" – Aaron McDaid Jan 09 '15 at 10:58

6 Answers6

21

I feel the need to point out a subtle distinction.

While <type_traits> does provide std::is_move_constructible and std::is_move_assignable, those do not exactly detect whether a type has a move constructor (resp. move assignment operator) or not. For instance, std::is_move_constructible<int>::value is true, and consider as well the following case:

struct copy_only {
    copy_only(copy_only const&) {} // note: not defaulted on first declaration
};
static_assert( std::is_move_constructible<copy_only>::value
             , "This won't trip" );

Note that the user-declared copy constructor suppresses the implicit declaration of the move constructor: there is not even a hidden, compiler-generated copy_only(copy_only&&).

The purpose of type traits is to facilitate generic programming, and are thus specified in terms of expressions (for want of concepts). std::is_move_constructible<T>::value is asking the question: is e.g. T t = T{}; valid? It is not asking (assuming T is a class type here) whether there is a T(T&&) (or any other valid form) move constructor declared.

I don't know what you're trying to do and I have no reason not to believe that std::is_move_constructible isn't suitable for your purposes however.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • 3
    While in commenting mood - `is_move_assignable` tells us if `t = std::move(u)` will work, not if the values are moved or copied. For an empty `copy_only` object there will not be much difference anyway. – Bo Persson Aug 14 '11 at 08:50
  • 1
    +1 N3142 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3142.html) was introduced late in the process just to avoid the misunderstanding that Luc does a nice job clarifying with this answer: `has_move_constructor` was renamed to `is_move_contructible` so that the name more closely reflected its true semantics. – Howard Hinnant Aug 14 '11 at 12:31
  • Hi guys, I've posted a new answer to this question, showing how we can force an ambiguity error when a move constructor is present alongside a copy constructor. This can be built into a complete `really_has_a_move_constructor` trait. Any feedback appreciated. I hope it's fully standards compliant, but I really know nothing about overload resolution! – Aaron McDaid Jan 11 '15 at 12:05
  • *"I don't know what you're trying to do and I have no reason not to believe that `std::is_move_constructible` isn't suitable for your purposes however."* I can explain why it's insufficient, since I've run into the same problem. If you're trying to implement a rollback operation, it makes a huge difference whether you're calling the move constructor or the copy constructor. Why? Because rolling back a copy is a no-op and can be skipped entirely (in fact it **should** be skipped, to avoid throwing an exception), whereas rolling back a movement requires a movement back to the original location. – user541686 Dec 15 '20 at 11:06
9

It's called std::is_move_constructable. There is also std::is_move_assignable. They are both in the C++0x <type_traits> header.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
3

After a little discussion, and in full agreement that this may be entirely useless, and with the warning that older compilers may get this wrong, I would nevertheless like to paste a little trait class I rigged up which I believe will give you true only when a class has a move constructor:

#include <type_traits>

template <typename T, bool P> struct is_movecopy_helper;

template <typename T>
struct is_movecopy_helper<T, false>
{
  typedef T type;
};

template <typename T>
struct is_movecopy_helper<T, true>
{
  template <typename U>
  struct Dummy : public U
  {
    Dummy(const Dummy&) = delete;
    Dummy(Dummy&&) = default;
  };
  typedef Dummy<T> type;
};

template <class T>
struct has_move_constructor
 : std::integral_constant<bool, std::is_class<T>::value &&
   std::is_move_constructible<typename is_movecopy_helper<T, std::is_class<T>::value>::type>::value> { };

Usage: has_move_constructor<T>::value

Note that the compiler-trait std::is_move_constructible isn't actually shipped with GCC 4.6.1 and has to be provided separately, see my complete code.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • I can't get this working now. clang-3.3. It's been two years since you posted this answer, so things have moved along, I guess! As far as I can see, given a class that is copy constructible, the only way it can be *non*-move constructible is if it does have a `private` move constructor. Anyway, I guess your idea here was that `default` move constructors can only be created when all superclasses already have move constructors? – Aaron McDaid Nov 02 '13 at 22:34
  • @AaronMcDaid: I think the point of this trait is to detect whether a class has an "genuine" move constructor that is not just falling back to the copy constructor. Anything that's copyable is automatically movable by default. – Kerrek SB Nov 02 '13 at 23:55
  • *"a 'genuine' move constructor that is not just falling back to the copy constructor"*. That's what I was hoping for! But my experiments on your code are not succeeding. I create a class with various constructors, including a copy constructor, but not a move constuctor, and your trait has `value==1`. Do you have an example code that shows it working? The link you had to ideone is dead. – Aaron McDaid Nov 03 '13 at 00:20
  • @AaronMcDaid: Hm, I think what I just said isn't so easily doable. I don't know now, maybe the code I posted is just a cumbersome version of `is_class` and `is_move_constructible`, and doesn't actually tell whether a move constructor has been declared. Shame, but I can't see an obvious way to achieve that just now. – Kerrek SB Nov 03 '13 at 00:38
  • 1
    I've just added [an answer](http://stackoverflow.com/a/19850591/146041) to this question. Even if we have `T(const &)`, it can detect if there exists a `T(T&&)`. – Aaron McDaid Nov 08 '13 at 02:40
  • @AaronMcDaid: Very nice - *two* bases is the way to go :-) – Kerrek SB Nov 08 '13 at 09:32
2

Update 3:: I've added another answer, so ignore this. I'm tempted to delete this as it no longer works for me with newer compilers. But I already have some responses here, so I guess I shouldn't delete this. Also, this particular answer did work on some older compilers, so it might be useful to some people.

This will test if there is a constructor of the form T(T&&). Works on clang-3.3, and g++-4.6.3. But this test on ideone shows that their compiler (g++-???) confuses the copy and move constructors.

Update 2: January 2015. This does not work with newer g++ (4.8.2) and clang (3.5.0). So I guess my answer here is not helpful without first checking that your particular version supports the trick I've used here. Perhaps my trick is not compliant with the standard and has since been removed from g++ and clang++. In my answer below I said "a derived class will only have an implicit move constructor if all its bases have move constructors" - perhaps this is not true or too simplistic?

struct move_not_copy { move_not_copy(move_not_copy &&); };

template<typename T>
struct has_move_constructor {
        struct helper : public move_not_copy,  public T {
        };
        constexpr static bool value =
               std::is_constructible<helper,
               typename std::add_rvalue_reference<helper>::type> :: value;
        constexpr operator bool () const { return value; }
};

More precisely, regardless of whether a class has a copy constructor T(const T&), this trait is still able to detect if the class also has a move constructor T(T&&).

The trick is to derive a very simple class, helper, with two bases and no other methods/constructors. Such a derived class will only have an implicit move constructor if all its bases have move constructors. Similarly for copy constructors. The first base, move_not_copy has no copy constructor, therefore helper will not have a copy constructor. However, helper is still able to pick up an implicitly-defined move constructor if, and only if, T has such a constructor. Therefore, helper will either have zero constructors, or one constructor (a move constructor), depending only on whether T has a move constructor.


Tests. This is the table for four types, showing the desired behaviour. A full program testing it is at ideone, but as I said earlier, it's getting the wrong results on ideone because they're using and old g++.

               Copy is_copy_constructible 1  is_move_constructible 1  has_move_constructor 0
           MoveOnly is_copy_constructible 0  is_move_constructible 1  has_move_constructor 1
               Both is_copy_constructible 1  is_move_constructible 1  has_move_constructor 1
CopyWithDeletedMove is_copy_constructible 1  is_move_constructible 0  has_move_constructor 0

What does the standard have to say on this? I got the idea after reading cppreference, specifically:

The implicitly-declared or defaulted move constructor for class T is defined as deleted if any of the following is true:

...

T has direct or virtual base class that cannot be moved (has deleted, inaccessible, or ambiguous move constructors)

...

and I assume a similar thing applies to copy constructors.

Community
  • 1
  • 1
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • Tried this using coliru with clang 3.5 and did not see the expected behavior there. `struct Foo { int n; ~Foo() {n=0;} Foo& operator=(const Foo& rhs) { n=rhs.n; return *this;} };` I would expect has_move_constructor to be false given its custom destructor and operator=. It returns true though. – Cross_ Jan 04 '15 at 09:06
  • 1
    Agreed. My code worked with clang 3.3, which I used when I answered this question, but with clang 3.5 it no longer works. There has been some change in clang. It also doesn't work with g++-4.8.2. I don't know what is the 'correct' behaviour, according to the standard. Are the compilers becoming more, or less, compliant. I'll put a note at the start of my answer. – Aaron McDaid Jan 05 '15 at 13:00
1

You can introduce an intentional ambiguity error when both a move constructor and a copy constructor are present. This allow us to test for the presence of a move constructor.

Over recent years, as compilers change, different solutions work and then break. This is working with clang 3.5.0. I'm hopeful that it will work on older and newer compilers also - but I'm not an expert on the standard.

Also, This answer requires more work to finish it, but I've tested the fundamental idea.

First, it's easy to tell if there is a copy-constructor. If there is no copy constructor, then it's easy to tell if there is a move constructor. The challenge is, when there is a copy constructor, to test if there is also a move constructor. This is the challenge I will focus on here.

Therefore, it is enough to consider only types that do have a copy constructor and test for the presence of a move constructor. For the remainder of this question I will assume a copy constructor is present.


I test for the move constructor by forcing an ambiguity error when both kinds of constructor are present and then (ab)using SFINAE to test for the presence of this ambiguity.

In other words, our challenge is to test the difference between the following types:

struct CopyOnly { 
    CopyOnly (const CopyOnly&);  // just has a copy constructor
};
struct Both { 
    Both (const Both&);          // has both kinds of constructor
    Both (Both&&);     
};

To do this, first define a Converter<T> class that claims to be able to convert itself into two kinds of reference. (We'll never need to implement these)

template<typename T>
struct Converter { 
    operator T&& ();
    operator const T& ();
};

Second, consider these lines:

Converter<T> z;
T t(z);

The second line is trying to construct a T. If T is CopyOnly, then t will be made via the copy constructor, and the relevant reference to pass to the copy constructor is extracted from the operator const CopyOnly &() method of Converter<CopyOnly>. So far, this is pretty standard. (I think?)

But, if T is Both, i.e. it also has a move constructor, there will be an ambiguity error. Both constructors of T are available, as converters are available for both (converters from z), therefore there is ambiguity. (Any language lawyers able to confirm this is fully standard?)

This logic applies also to new T( Converter<T>{} ). This expression has a type if, and only if, T does not have a move constructor. We can therefore wrap decltype around this and use this in SFINAE.

I close with two overloads of baz<T>. The chosen overload will depend on whether T is like CopyOnly or Both. The first overload is valid only if new T( Converter<T>{} ) is well-defined, i.e. if there is no ambiguity error, i.e. if there is no move constructor. You can give different return types to each overload to make this information available at compile time.

template<typename T>
std:: true_type
baz (decltype( new T( Converter<T>{} )   )) {
    cout << __LINE__ << endl;
    return {};
}

template<typename U>
std:: false_type
baz ( 
        const volatile // const volatile to tie break when both forms of baz are available
        U *) { 
    cout << __LINE__ << endl;
    return {};
}

baz should be called like this:

baz<JustCopy>((JustCopy*)nullptr);
baz<Both>((Both*)nullptr);

And you could wrap it up in something like this:

template<typename T>
struct has_move_constructor_alongside_copy {
    typedef decltype(baz<T>((T*)nullptr)) type;
};

There's a lot to do to tidy this up, and I'm sure SFINAE experts could improve it greatly (please do!). But I think this solves the main problem, testing for the presence of a move constructor when we already know a copy constructor is present.

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
1

I took Aaron McDaid's last answer and wrapped it in the construct below. The code in his answer didn't work for me, this does with both clang 3.6 and MSVC2013.

template <typename T>
struct has_move_constructor_alongside_copy {
  typedef char yes[1];
  typedef char no[2];

  struct AmbiguousConverter {
    operator T&& ();
    operator const T& ();
  };

  template <typename C>
  static no& test(decltype( new C( AmbiguousConverter{} )));

  template <typename>
  static yes& test(...);

  static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};
Community
  • 1
  • 1
  • 1
    This doesn't seem to work for me in VC2013; it returns false for default constructors (bad), false for copy-only (good), true for deleted-move (bad), true for move-only (good), and false for move-and-copy (bad). – Miral Nov 04 '16 at 05:32