Подтвердить что ты не робот

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.

4b9b3361

Ответ 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 - это одна из тех вещей, которая сильно зависит от настроек компиляции).