Take for example this code:
#include <type_traits>
#include <iostream>
struct Foo
{
Foo() = default;
Foo(Foo&&) = delete;
Foo(const Foo&) noexcept
{
std::cout << "copy!" << std::endl;
};
};
struct Bar : Foo {};
static_assert(!std::is_move_constructible_v<Foo>, "Foo shouldn't be move constructible");
// This would error if uncommented
//static_assert(!std::is_move_constructible_v<Bar>, "Bar shouldn't be move constructible");
int main()
{
Bar bar {};
Bar barTwo { std::move(bar) };
// prints "copy!"
}
Because Bar is derived from Foo, it doesn't have a move constructor. It is still constructible by using the copy constructor. I learned why it chooses the copy constructor from another answer:
if
y
is of typeS
, thenstd::move(y)
, of typeS&&
, is reference compatible with typeS&
. ThusS x(std::move(y))
is perfectly valid and call the copy constructorS::S(const S&)
.—Lærne, Understanding
std::is_move_constructible
So I understand why a rvalue "downgrades" from moving to a lvalue copying, and thus why std::is_move_constructible
returns true. However, is there a way to detect if a type is truly move constructible excluding the copy constructor?
There are claims that presence of move constructor can't be detected and on surface they seem to be correct -- the way &&
binds to const&
makes it impossible to tell which constructors are present in class' interface.
Then it occurred to me -- move semantic in C++ isn't a separate semantic... It is an "alias" to a copy semantic, another "interface" that class implementer can "intercept" and provide alternative implementation. So the question "can we detect a presence of move ctor?" can be reformulated as "can we detect a presence of two copy interfaces?". Turns out we can achieve that by (ab)using overloading -- it fails to compile when there are two equally viable ways to construct an object and this fact can be detected with SFINAE.
30 lines of code are worth a thousand words:
#include <type_traits>
#include <utility>
#include <cstdio>
using namespace std;
struct S
{
~S();
//S(S const&){}
//S(S const&) = delete;
//S(S&&) {}
//S(S&&) = delete;
};
template<class P>
struct M
{
operator P const&();
operator P&&();
};
constexpr bool has_cctor = is_copy_constructible_v<S>;
constexpr bool has_mctor = is_move_constructible_v<S> && !is_constructible_v<S, M<S>>;
int main()
{
printf("has_cctor = %d\n", has_cctor);
printf("has_mctor = %d\n", has_mctor);
}
Notes:
you probably should be able to confuse this logic with additional const/volatile
overloads, so some additional work may be required here
doubt this magic works well with private/protected constructors -- another area to look at
doesn't seem to work on MSVC (as is tradition)
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