I'd like to initialize std::any
with a move only type variable. I found Cannot move std::any.
Before I use shared_ptr workaround from the linked answer, I tested the following code:
#include <utility>
#include <iostream>
#include <any>
struct move_only {
move_only() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
move_only(move_only const&) = delete;
move_only(move_only &&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
int main() {
move_only m;
std::any a(std::move(m)); // error. copy constructor is required
}
https://wandbox.org/permlink/h6HOSdgOnQYg4a6K
The code above outputs compile error because of move_only
doesn't have copy constructor.
I added copy constructor for test.
#include <utility>
#include <iostream>
#include <any>
struct move_only {
move_only() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
move_only(move_only const&) {
// not called
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
move_only(move_only &&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
int main() {
move_only m;
std::any a(std::move(m)); // success but copy constructor is not called
}
https://wandbox.org/permlink/kxEnIslmVnJNRSn6
Then compile successfully finished as I expected. And I got interesting output.
move_only::move_only()
move_only::move_only(move_only &&)
It seems that the copy constructor isn't called. It is surprising for me.
And I come up with the following wrapper approach.
#include <utility>
#include <iostream>
#include <any>
struct move_only {
move_only() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
move_only(move_only const&) = delete;
move_only(move_only &&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
struct wrapped_move_only : move_only {
wrapped_move_only(move_only&& m):move_only(std::move(m)) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
wrapped_move_only(wrapped_move_only const&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
assert(false);
}
wrapped_move_only(wrapped_move_only &&) = default;
};
int main() {
move_only m;
wrapped_move_only wmo(std::move(m));
std::any a(std::move(wmo));
}
https://wandbox.org/permlink/EDhq3KPWKP9fCA9v
The copy constructor of move_only is deleted. The class wapped_move_only inherits move_only and added copy constructor.
It successfully compiled and I got the following result.
move_only::move_only()
move_only::move_only(move_only &&)
wrapped_move_only::wrapped_move_only(move_only &&)
move_only::move_only(move_only &&)
It seems that I initialized std::any with move only type using the wrapper that provides dummy copy constructor. It is more efficient to use shared_ptr if the goal is just initialize std::any with move only type. It is expected behavior for me.
As long as I do move operation only for std::any
once move_only
is moved to std::any
, is this code safe?
If std::any
is copied, then assetion failed due to copy constructor of wrapped_move_only is called. I want to know move only case's safety.
I am also not sure why std::any
's target requires copy constructor but it is not called.
If it is safe, I can improve this approach using template. The template add_dummy_copy_constructor
is a kind of adaptor.
#include <utility>
#include <iostream>
#include <any>
struct move_only {
move_only() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
move_only(move_only const&) = delete;
move_only(move_only &&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
template <typename T>
struct add_dummy_copy_constructor : T {
add_dummy_copy_constructor(T&& t):T(std::move(t)) {}
add_dummy_copy_constructor(add_dummy_copy_constructor const&) {
assert(false);
}
add_dummy_copy_constructor(add_dummy_copy_constructor &&) = default;
};
int main() {
move_only m;
std::any a(add_dummy_copy_constructor(std::move(m)));
}
https://wandbox.org/permlink/guEWPIrK9wiJ3BgW
I am also not sure why
std::any
's target requires copy constructor but it is not called.
The design of std::any
is to be one concrete type that can hold any copyable type. When you copy a std::any
, you copy whatever it is it's holding underneath
The std::any
needs to know how to copy the underlying object regardless of if it is ever actually going to be copied (how would it know whether or not this happens?). So it must be a compile error if the underlying type isn't copy-constructible.
However, when we're constructing the std::any
itself, at that point we know the concrete object we're constructing from. And if that concrete object happens to be an rvalue, then we can just move-construct std::any
's underlying object from the constructor parameter rather than copy-constructing. It's a free win there.
None of your code actually copies a std::any
, so none of it would invoke std::any
's copy constructor which would invoke the underlying types copy constructor.
A similar thing happens with std::function
and maybe it would be more obvious what the difference here is. When I construct a std::function<void()>
, there is the static requirement that the object be invokable with no arguments. It is a compile error if it is not invokabe.
But simply constructing the std::function<void()>
will not actually invoke the underlying function - those are separate operations. You wouldn't expect this assertion to trigger:
std::function<void()> f = []{ assert(false); }
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