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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With