# V833. Using 'std::move' function's with const object disables move semantics.

The analyzer detected a situation when move semantics does not work. Such code slows down the performance.

- The 'std::move' function may have received an lvalue reference to a const object as an argument.
- The 'std::move' function's result may have been passed to a function that takes an lvalue reference to a const as a parameter.

Example:

    #include <string>
    #include <vector>

    void foo()
    {
      std::vector<std::string> fileData;
      const std::string alias = ....;
      ....
      fileData.emplace_back(std::move(alias));
      ....
    }

This code does not work as the developer expects. Move semantics is impossible for const-qualified objects. As a result, the compiler calls a copy constructor for 'std::string' and the expected optimization does not happen.

To fix this code, you can remove the 'const' keyword from the 'alias' local variable:

    #include <string>
    #include <vector>

    void foo()
    {
      std::vector<std::string> fileData;
      std::string alias = ....;
      ....
      fileData.emplace_back(std::move(alias));
      ....
    }

The diagnostic also issues a warning when 'std::move' is used on a function's formal parameter:

    #include <string>

    void foo(std::string);

    void bar(const std::string &str)
    {
      ....
      foo(std::move(str));
      ....
    }

There's no universal way to fix such code, but the approaches below could help.

Approach 1

Add a function overload that takes an rvalue reference:

    #include <string>

    void foo(std::string);

    void bar(const std::string &str)
    {
      ....
      foo(str);                 // copy here
      ....
    }

    void bar(std::string &&str) // new overload
    {
      ....
      foo(std::move(str));      // move here
      ....
    }

Approach 2

Rewrite the function to make it a function template that takes a forward reference. Limit the template parameter to the required type. Then apply the 'std::forward' function to the template argument:

    #include <string>

    #include <type_traits> // until C++20
    #include <concepts>    // since C++20

    void foo(std::string);

    // ------------ Constraint via custom trait (since C++11) ------------
    template <typename T>
    struct is_std_string
      : std::bool_constant<std::is_same<std::decay_t<T>,
                                        std::string>::value>
    {};

    template <typename T,
              std::enable_if_t<is_std_string<T>::value, int> = 0>
    void bar(T &&str)
    {
     ....
     foo(std::forward<T>(str));
     ....
    }
    // -------------------------------------------------------------------

    // ------------ Constraint via custom trait (since C++14) ------------
    template <typename T>
    static constexpr bool is_std_string_v =
      std::is_same<std::decay_t<T>, std::string>::value;

    template <typename T, std::enable_if_t<is_std_string_v<T>, int> = 0>
    void bar(T &&str)
    {
     ....
     foo(std::forward<T>(str));
     ....
    }
    // -------------------------------------------------------------------

    // ------------------ Constraint via C++20 concept -------------------
    template <typename T>
    void bar(T &&str) requires std::same_as<std::remove_cvref_t<T>,
                                            std::string>
    {
      ....
      foo(std::forward<T>(str));
      ....
    }
    // -------------------------------------------------------------------

Approach 3

If the above - or any other - approaches are not applicable, remove the 'std::move' call.

The diagnostic rule also fires when the 'std::move' function's result is passed to a function that takes an lvalue reference to a const. Example:

    #include <string>

    std::string foo(const std::string &str);

    void bar(std::string str, ....)
    {
      ....
      auto var = foo(std::move(str));
      ....
    }

Although 'std::move' is executed and returns an xvalue object, that object is still copied and not moved. This happens because the function's formal parameter is an lvalue reference to a const. In this case, the result of the 'std::move' call falls within the context where a move constructor call is impossible. However, if you write a new function overload, that takes an rvalue reference, or a function template with a forwarding reference - the compiler will choose that entity and will execute the code as you expect:

    #include <string>

    std::string foo(const std::string &str);
    std::string foo(std::string &&str);

    void bar(std::string str, ....)
    {
      ....
      auto var = foo(std::move(str));
      ....
    }

Now let's examine the case when 'std::move' can be applied to a reference to a const and works correctly:

    template <typename T>
    struct MoC
    {
      MoC(T&& rhs) : obj (std::move(rhs)) {}
      MoC(const MoC& other) : obj (std::move(other.obj)) {}

      T& get() { return obj; }

      mutable T obj;
    };

The code above is the MoC (Move on Copy) idiom implementation. The copy constructor moves the object. In this case, it is possible because the non-static data member 'obj' has the 'mutable' specifier and tells the compiler explicitly to process this object as a non-const object.