I'm not looking for a type trait for movable types, nor rules for automatic generation of move operations. What I'm looking for is a general guide to know if a given type is going to be moved or copied, or a way to figure it myself with testing.
In some cases, the moving operation is performed without the user ever noticing, for example:
void f(std::string) { ... }
void f_c(const std::string) { ... }
void g()
{
f(std::string("Hello world!")); // moved, isn't it?
f("Hello world!"); // moved, isn't it?
f_c(std::string("Hello world!")); // moved, isn't it?
f_c("Hello world!"); // moved, isn't it?
}
Prior to C++11 the code above would incurr in std::string
copy from a temporary value to the one passed to f
and f_c
, from C++11 onwards the std::basic_string
provides a move constructor (see here (8)) and the temporary created is moved into the parameter passed to f
and f_c
.
Sometimes the users were trying to force the move semantics with the std::move
function:
std::string return_by_value_1()
{
std::string result("result);
return std::move(result); // Is this moved or not?
}
std::string return_by_value_2()
{
return std::move(std::string("result)); // Is this moved or not?
}
But std::move
doesn't move anything1: it only converts lvalues into rvalues references, if the target type doesn't implement move semantics: no move operation is performed... and AFAIK std::move
ing in the return_by_value_x
functions above prevents the compiler from doing RVO (we're worsening the code!).
So, after the (maybe unnecessary) introduction, I'll ask my questions:
How to know or test if a given type is going to be moved or copied?
This question is about basic types and complex ones:
int f_int(int) { ... };
template <typename F, typename S> void f_pair(std::pair<F, S>) { ... };
struct weird
{
int i;
float f;
std::vector<double> vd;
using complexmap = std::map<std::pair<std::string, std::uint64_t>, std::pair<std::uint32_t, std::uint32_t>>;
complexmap cm;
};
struct silly
{
std::vector<std::pair<const std::string, weird::complexmap>> vcm;
};
f_weird(weird) { ... };
f_silly(silly) { ... };
- A basic type can be moved? there's some calls to
f_int
which implies a move operation?f_int(1); // this moves or construct an int in-place?
f_int(1 + 2); // this moves or construct an int in-place?
f_int(f_int(1) + 2); // this moves or construct an int in-place?
- A complex type with const members can't be moved, isn't it?
f_pair<std::pair<const std::string, int>>({"1", 2}); // unmovable?
f_pair<std::pair<std::string, std::string>>({"1", "2"}); // this moves?
f_silly({{{}, {}}}); // this moves?
- The
struct weird
is moveable?f_weird({1, .0f, {0.d, 1.d}, {{{"a", 0ull}, {1u, 2u}}}}) // this moves?
- How to test by myself the above cases in order to determine if a given type is being moved in a given context?
1Wouldn't be better if it was called std::rvalref
or something similar?