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.