Const_cast и std:: move для удаления константы из не ссылки
У меня есть внешняя библиотека, которую я не могу изменить. Библиотека объявляет функцию шаблона, которая почему-то возвращает const
объект без ссылки:
template<class C>
const C foo();
У меня есть еще одна внешняя библиотека, которую я тоже не могу изменить. Библиотека объявляет класс, который не копируется, и имеет конструктор перемещения только из неконстантного объекта:
struct bar {
bar();
bar(const bar&)=delete;
bar(bar&&);
};
Теперь мне нужно использовать foo<bar>
. Простое использование:
bar buz() {
return foo<bar>();
}
не работает с
main.cpp: In function 'bar buz()':
main.cpp:13:21: error: use of deleted function 'bar::bar(const bar&)'
return foo<bar>();
^
main.cpp:8:5: note: declared here
bar(const bar&)=delete;
^~~
что имеет смысл, и никакое простое обходное решение не делает компиляцию кода.
Однако, если я добавлю более сложное обходное решение:
bar buz() {
return const_cast<bar&&>(std::move(foo<bar>()));
}
он компилируется, и весь код работает как ожидалось (не только упрощенный пример выше, но и мой реальный код).
Однако, это безопасно, или я сталкиваюсь с каким-то поведением undefined? Есть ли лучший способ обхода?
Я прочитал и понимаю вопросы о возвращении const
из функций (1, 2), и общий ответ, кажется, заключается в том, что возвращение объектов const
не рекомендуется в современном С++, но мой вопрос не в этом, а о том, как я могу обойти ситуацию, когда внешняя библиотека возвращает объект const
.
Ответы
Ответ 1
С технической точки зрения, вы подвергаете свою программу воздействию undefined. Поскольку первоначальный объект C
(временный) был объявлен const
, const-casting и его изменение является незаконным и стандартным. (Я предполагаю, что перемещение конструктора делает некоторые изменения в перемещении).
Говоря это, он, вероятно, работает в вашей среде, и я не вижу лучшего способа обхода.
Ответ 2
Отбрасывание const приведет к поведению undefined, если конструктор перемещения для bar
изменяет что-либо. Вероятно, вы можете обойти свою проблему, не введя поведение undefined:
struct wrapped_bar {
mutable bar wrapped;
};
bar buz()
{
return foo<wrapped_bar>().wrapped;
}
Наличие члена wrapped
be mutable означает, что элемент не является константой, хотя объект wrapped_bar
в целом является константой. На основе того, как работает foo()
, вам может потребоваться добавить участников в wrapped_bar
, чтобы он работал больше как bar
.
Ответ 3
В результате вызова функции по определению является самим значением R-Value, вам не нужно применять std::move
в нем в операторе return - const_cast<bar&&>(foo<bar>())
должно быть достаточно. Это делает код немного проще для чтения.
Тем не менее, нет стандартной гарантии, что это всегда будет работать для всех типов bar
. Более того - в некоторых случаях это может привести к поведению undefined. (Представьте себе очень навязчивую оптимизацию, которая полностью уничтожает foo
и делает ее результатом объект в сегменте памяти "статические данные" - например, если foo
был constexpr
. Тогда вызов движущегося конструктора, который, вероятно, изменяет его аргумент, может привести к исключению нарушения доступа).
Все, что вы можете сделать, это либо переключиться на другую библиотеку (или, если возможно, попросить администратора библиотек исправить API), либо создать несколько unit test и включить его в процесс сборки - пока тест проходит, вы должны быть в порядке (не забудьте использовать те же настройки оптимизации, что и в сборке "production" - const_cast
- это одна из тех вещей, которая сильно зависит от настроек компиляции).