Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type trait for moveable types?

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.

like image 460
Kranar Avatar asked Aug 14 '11 04:08

Kranar


4 Answers

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.

like image 197
Luc Danton Avatar answered Oct 27 '22 02:10

Luc Danton


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

like image 31
Nicol Bolas Avatar answered Oct 27 '22 02:10

Nicol Bolas


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.

like image 25
Kerrek SB Avatar answered Oct 27 '22 02:10

Kerrek SB


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.

like image 39
Aaron McDaid Avatar answered Oct 27 '22 02:10

Aaron McDaid