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

Анализатор обнаружил ситуацию, когда семантика перемещения не сработает, что приведёт к замедлению производительности.

- Когда в функцию 'std::move' в качестве аргумента передаётся lvalue-ссылка на константный объект.
- Когда результат функции 'std::move' передаётся в функцию, принимающую lvalue-ссылку на константу.

Рассмотрим пример:

    #include <string>
    #include <vector>

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

Данный фрагмент кода cработает не так, как ожидает программист. Семантика перемещения невозможна для константных объектов. В результате компилятор выберет конструктор копирования для 'std::string' и желаемая оптимизация не произойдёт.

В данном случае код можно поправить, просто убрав константность с локальной переменной:

    #include <string>
    #include <vector>

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

Диагностика выдает срабатывания также и для случаев, когда 'std::move' применяется для формального параметра функции:

    #include <string>

    void foo(std::string);

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

Дать универсальную рекомендацию по исправлению такого кода сложно, но можно применить следующие подходы.

Первый вариант

Можно дописать перегрузку функции, принимающую rvalue-ссылку:

    #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
      ....
    }

Второй вариант

Можно переписать функцию в виде шаблона функции, принимающей forward-ссылку. При этом необходимо ограничить шаблонный параметр нужным типом. Затем применить на шаблонном аргументе функцию 'std::forward':

    #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));
      ....
    }
    // -------------------------------------------------------------------

Третий вариант

Если ранее описанные или другие приёмы невозможны, то следует убрать вызов 'std::move'. Диагностическое правило также сработает в случаях, когда результат функции 'std::move' передаётся в функцию, принимающую lvalue-ссылку на константу. Рассмотрим пример:

    #include <string>

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

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

Хоть 'std::move' отработает и вернёт нам xvalue-объект, он всё равно будет скопирован, поскольку формальный параметр функции — lvalue-ссылка на константу. В данном случае результат вызова 'std::move' будет находиться в контексте, в котором вызов конструктора перемещения невозможен. Однако, если дописать перегрузку функции, принимающую rvalue-ссылку, или шаблон функции с forwarding-ссылкой, компилятор выберет её, и код отработает ожидаемым образом:

    #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));
      ....
    }

Теперь давайте рассмотрим случай, когда 'std::move' на ссылку на константу сработает:

    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;
    };

Здесь представлена реализация идиомы MoC (Move on Copy). В конструкторе копирования выполняется перемещение. В данном случае это возможно потому, что нестатическое поле 'obj' имеет спецификатор 'mutable', и это явно говорит компилятору работать с ним не как с константным объектом.