How to know or test if a given type is going to be moved

不打扰是莪最后的温柔 提交于 2019-12-13 18:17:44

问题


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::moveing 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) if X has:

  • a potentially constructed subobject type M (or array thereof) that cannot be [..] moved because overload resolution (13.3), as applied to M’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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!