问题
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?
回答1:
How to test by myself the above cases in order to determine if a given type is being moved in a given context?
You can test whether a derived class would define a defaulted move constructor as deleted via SFINAE. This idea does not work for final
classes. A rough sketch that seems to work is
namespace detail {
// No copy constructor. Move constructor not deleted if T has an applicable one.
template <typename T>
struct Test : T { Test(Test&&) = default; };
}
// TODO: Implement unions
template <typename T>
using is_moveable = std::is_move_constructible<
std::conditional_t<std::is_class<T>{}, detail::Test<T>, T>>;
Demo.
The move constructor of Test
is a defaulted move constructor according to [dcl.fct.def.default]/5. The relevant quote that makes the above work is then in [class.copy]/11:
A defaulted [..] move constructor for a class
X
is defined as deleted (8.4.3) ifX
has:
- a potentially constructed subobject type
M
(or array thereof) that cannot be [..] moved because overload resolution (13.3), as applied toM
’s corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor, [..]A defaulted move constructor that is defined as deleted is ignored by overload resolution (13.3, 13.4).
For the initialization
Test(Test())
to be valid, as the copy ctor is not implicitly declared, a move constructor must be available. However, according to the above quote, Test
's move constructor will be defined as deleted (and thus ignored by overload resolution) if the base class, our given T
, does not have an invokeable move constructor. (I am slightly unsure about the case where T
does not have any move constructor, I'd be grateful for clarification on the standard there.)
来源:https://stackoverflow.com/questions/28476393/how-to-know-or-test-if-a-given-type-is-going-to-be-moved