Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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

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.

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.

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.

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);
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top