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

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

1. Когда в функцию '[std::move](https://pvs-studio.ru/ru/blog/terms/6518/)' в качестве аргумента передаётся [lvalue\-ссылка](https://pvs-studio.ru/ru/blog/terms/6517/) на константный объект\.
1. Когда результат функции 'std::move' передаётся в функцию, принимающую lvalue\-ссылку на константу\.

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

```cpp
#include <string>
#include <vector>

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

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

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

```cpp
#include <string>
#include <vector>

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

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

```cpp
#include <string>

void foo(std::string);

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

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

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

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

```cpp
#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':

```cpp
#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\-ссылку на константу\. Рассмотрим пример:

```cpp
#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\-ссылкой, компилятор выберет её, и код отработает ожидаемым образом:

```cpp
#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' на ссылку на константу сработает:

```cpp
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', и это явно говорит компилятору работать с ним не как с константным объектом\.