Какова цель перегрузки ссылки на значение std :: forward()? - программирование
Подтвердить что ты не робот

Какова цель перегрузки ссылки на значение std :: forward()?

Я экспериментирую с Perfect Forwarding и обнаружил, что std::forward() нуждается в двух перегрузках:

Перегрузка Nr. 1:

template <typename T>
inline T&& forward(typename 
std::remove_reference<T>::type& t) noexcept
{
    return static_cast<T&&>(t);
}

Перегрузка №2:

template <typename T>
inline T&& forward(typename 
std::remove_reference<T>::type&& t) noexcept
{
    static_assert(!std::is_lvalue_reference<T>::value,
              "Can not forward an rvalue as an lvalue.");
    return static_cast<T&&>(t);
}

Теперь типичный сценарий для Perfect Forwarding выглядит примерно так:

template <typename T>
void wrapper(T&& e)
{
    wrapped(forward<T>(e));
}

Конечно, вы знаете, что когда экземпляр wrapper() создается, T зависит от того, является ли переданный ему аргумент lvalue или rvalue. Если это значение типа U, T выводится в U&. Если это значение, T выводится в U

В любом случае - в контексте wrapper() - e является lvalue, поэтому он всегда использует первую перегрузку std::forward().

Теперь мой вопрос:

Каков допустимый сценарий, в котором используется 2-я перегрузка (и она необходима)?

4b9b3361

Ответ 1

Обоснование дизайна для forward подробно обсуждается в N2951.

В этом документе изложены 6 вариантов использования:

A. Следует переслать lvalue как lvalue. Все реализации проходят этот тест. Но это не классический идеальный образец пересылки. Цель этого теста - показать, что реализация 2 не соответствует своей заявленной цели - предотвратить все варианты использования, кроме идеальной пересылки.

Б. Следует переслать rvalue как rvalue. Как и в случае использования A, это преобразование идентичности, и это представляет собой мотивирующий пример, где необходимо преобразование идентичности.

C. Не следует пересылать значение как значение. Этот вариант использования демонстрирует опасную ситуацию случайного создания свисающей ссылки.

D. Следует пересылать менее квалифицированные по выражениям выражения к более квалифицированным по выражениям. Мотивирующий вариант использования, включающий добавление const во время форварда.

E. Следует пересылать выражения производного типа в доступный, однозначный базовый тип. Мотивирующий вариант использования, включающий пересылку производного типа в базовый тип.

F. Не следует пересылать произвольные преобразования типов. Этот вариант использования демонстрирует, как произвольные преобразования в форварде приводят к висящим ссылочным ошибкам времени выполнения.

Вторая перегрузка включает случаи B и C.

В статье приводятся примеры каждого варианта использования, которые слишком длинны, чтобы повторяться здесь.

Обновить

Я только что выполнил "решение" только первой перегрузки через эти 6 вариантов использования, и это упражнение показывает, что вторая перегрузка также позволяет использовать вариант F: не следует пересылать произвольные преобразования типов.

Ответ 2

Здесь очень распространенное использование для второй перегрузки: общая лямбда.

[](auto&& x) {
    return f(std::forward<decltype(x)>(x));
}

без второй перегрузки код будет использовать std::conditional_t<std::is_rvalue_reference_v<decltype(x)>, ...>.

(См. скотт Мейер С++ 14 Lambdas и Perfect Forwarding)