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

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

Перегрузка №. 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. Если это lvalue типа U, T преобразуется в U&. Если это значение r, T преобразуется в U.

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

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

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


person openyourmind    schedule 29.05.2019    source источник
comment
Этот язык сходит с ума.   -  person Yves Daoust    schedule 29.05.2019
comment
В случае, когда аргумент не является lvalue. Скажем, std::forward<int>(2).   -  person Raymond Chen    schedule 29.05.2019
comment
@Raymond Chen: конечно, но я не могу представить, когда такое использование необходимо   -  person openyourmind    schedule 29.05.2019
comment
Определите допустимый сценарий. Мне кажется, что static_assert предназначено для предотвращения некорректного использования API, а не для корректного сценария.   -  person Nicol Bolas    schedule 29.05.2019


Ответы (1)


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

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

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

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

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

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

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

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

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

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

Обновить

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

person Howard Hinnant    schedule 29.05.2019
comment
лол был на полпути к тому, чтобы напечатать ответ со ссылкой на эту статью - рад, что вы здесь :-) - person Barry; 29.05.2019