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

Пересылка возвращаемых значений. Требуется ли std:: forward?

Я пишу библиотеку, которая обертывает множество функций и методов из другой библиотеки. Чтобы избежать совпадения возвращаемых значений, я применяю std::forward следующим образом:

template<class T>
T&& wrapper(T&& t) { 
   f(t);  // t passed as lvalue  
   return std::forward<T>(t);
}

f возвращает void и принимает T&& (или перегружен по значению). Wrapper всегда возвращает параметр wrappers param, а возвращаемое значение должно сохранять нечеткость аргумента. Мне действительно нужно использовать std::forward в return? RVO делает его излишним? Означает ли факт, что это ссылка (R или L), делает ее излишней? Нужно ли это, если возврат не является последним оператором функции (внутри некоторого if)?

Это спорно, если wrapper() должен возвращать void или T&&, потому что вызывающий абонент имеет доступ к оцененному значению через Arg (который является ссылка, R или L). Но в моем случае мне нужно вернуть значение, чтобы wrapper() можно было использовать в выражениях.

Это может быть неуместно в вопросе, но известно, что функции f не воруют из t, поэтому 1-е использование std::forward в f(std::forward<T>(t)) является излишним, и оно было удалено мной.

Я написал небольшой тест: https://gist.github.com/3910503

Тест показывает, что возвращение непредвиденного t - создает дополнительную копию в gcc48 и clang32 с -O3 (RVO не срабатывает).

Кроме того, я не смог получить плохое поведение от UB в:

auto&& tmp = wrapper(42); 

Это ничего не доказывает, потому что это поведение undefined (если это UB).

4b9b3361

Ответ 1

В случае, если вы знаете, что t не будет находиться в состоянии переадресации после вызова f, ваши два несколько разумных варианта:

  • return std::forward<T>(t) с типом T&&, который позволяет избежать любой конструкции, но позволяет записывать, например. auto&& ref = wrapper(42);, который оставляет ref болтающуюся ссылку

  • return std::forward<T>(t) с типом t, который в худшем случае запрашивает конструкцию перемещения, когда параметр является значением rvalue - это позволяет избежать вышеупомянутой проблемы для prvalues, но потенциально крадет из xvalues ​​

Во всех случаях вам нужно std::forward. Копирование elision не рассматривается, потому что t всегда является ссылкой.

Ответ 2

В зависимости от того, что передается этой функцией, это приводит к поведению undefined! Точнее, если вы передадите не-lvalue, то есть rvalue, этой функции, значение, на которое ссылается возвращенная ссылка, будет устаревшим.

Также T&& не является "универсальной ссылкой", хотя эффект похож на универсальную ссылку в том, что T можно вывести как T& или T const&. Проблемный случай - это когда он выводится как T: аргументы передаются как временные и умирают после возвращения функции, но прежде чем что-либо может получить ссылку на нее.

Использование std::forward<T>(x) ограничено, если переадресовать объекты при вызове другой функции: то, что появилось как временное, выглядит как lvalue внутри функции. Использование std::forward<T>(x) позволяет x выглядеть временным, если оно входит как одно - и, таким образом, разрешить перемещение из x при создании аргумента вызываемой функции.

Когда вы возвращаете объект из функции, есть несколько сценариев, которые вы, возможно, захотите позаботиться, но ни одна из них не включает std::forward():

  • Если тип на самом деле является ссылкой, либо const, либо не const, вы не хотите ничего делать с объектом и просто возвращаете ссылку.
  • Если все операторы return используют одну и ту же переменную, или все используют временную, то можно использовать copy/move elision и будут использоваться на достойных компиляторах. Поскольку копирование/перемещение является оптимизацией, это не обязательно происходит.
  • Если всегда возвращается одна и та же локальная переменная или временная, ее можно перенести из нее, если есть конструктор перемещения, иначе объект будет скопирован.
  • Когда возвращаются переменные или когда возврат включает выражение, ссылки могут быть возвращены, но копирование/перемещение elision не будет работать, и также невозможно напрямую перейти от результата, если он не является временным. В этих случаях вам нужно использовать std::move(), чтобы разрешить перемещение с локального объекта.

В большинстве случаев полученный тип T, и вы должны вернуть T, а не T&&. Если T - тип lvalue, результат может не быть lvalue-типом, но может быть необходимо удалить ссылочную квалификацию из возвращаемого типа. В сценарии, который вы специально задали о типе T, работает.

Ответ 3

Нет, вам не нужно использовать std::forward лучше не возвращать ссылку на r-значение вообще, потому что это может предотвратить оптимизацию NRVO. Вы можете прочитать больше о семантике перемещения в этой статье: Статья