앞으로 사용의 장점
완벽한 전달에서는 std::forward
명명 된 rvalue 참조 t1
및 t2
명명되지 않은 rvalue 참조 로 변환하는 데 사용됩니다 . 그 목적은 무엇입니까? & values로 inner
남겨두면 호출 된 함수에 어떤 영향을 미칩니 까?t1
t2
template <typename T1, typename T2>
void outer(T1&& t1, T2&& t2)
{
inner(std::forward<T1>(t1), std::forward<T2>(t2));
}
전달 문제를 이해해야합니다. 전체 문제를 자세히 읽을 수 있지만 요약하겠습니다.
기본적으로 expression이 주어지면 표현식 이 동일하기를 E(a, b, ... , c)
원합니다 f(a, b, ... , c)
. C ++ 03에서는 불가능합니다. 많은 시도가 있지만 모두 어떤 측면에서 실패합니다.
가장 간단한 방법은 lvalue-reference를 사용하는 것입니다.
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
E(a, b, c);
}
그러나 f(1, 2, 3);
lvalue-reference에 바인딩 할 수 없으므로 임시 값을 처리하지 못합니다 .
다음 시도는 다음과 같습니다.
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(a, b, c);
}
위의 문제를 해결하지만 플롭을 뒤집습니다. 이제는 E
비 const 인수 를 허용하지 않습니다 .
int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these
세 번째 시도는 const를 참조를 받아,하지만 const_cast
'는이 s의 const
거리 :
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}
이것은 모든 값을 받아들이고 모든 값을 전달할 수 있지만 잠재적으로 정의되지 않은 동작으로 이어집니다.
const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!
최종 솔루션은 유지 관리가 불가능한 비용으로 모든 것을 올바르게 처리합니다. 당신의 과부하를 제공 f
하여, 모든 CONST 및 const가 아닌 조합 :
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);
N 개의 주장에는 악몽 인 2 개의 N 조합이 필요합니다 . 이 작업을 자동으로 수행하고 싶습니다.
(이것은 실제로 C ++ 11에서 컴파일러가 수행하는 작업입니다.)
In C++11, we get a chance to fix this. One solution modifies template deduction rules on existing types, but this potentially breaks a great deal of code. So we have to find another way.
The solution is to instead use the newly added rvalue-references; we can introduce new rules when deducing rvalue-reference types and create any desired result. After all, we cannot possibly break code now.
If given a reference to a reference (note reference is an encompassing term meaning both T&
and T&&
), we use the following rule to figure out the resulting type:
"[given] a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR."
Or in tabular form:
TR R
T& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T& && -> T& // rvalue reference to cv TR -> TR (lvalue reference to T)
T&& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T&& && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
Next, with template argument deduction: if an argument is an lvalue A, we supply the template argument with an lvalue reference to A. Otherwise, we deduce normally. This gives so-called universal references (the term forwarding reference is now the official one).
Why is this useful? Because combined we maintain the ability to keep track of the value category of a type: if it was an lvalue, we have an lvalue-reference parameter, otherwise we have an rvalue-reference parameter.
In code:
template <typename T>
void deduce(T&& x);
int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)
마지막은 변수의 값 범주를 "전달"하는 것입니다. 함수 내부에서 매개 변수는 lvalue로 전달 될 수 있습니다.
void foo(int&);
template <typename T>
void deduce(T&& x)
{
foo(x); // fine, foo can refer to x
}
deduce(1); // okay, foo operates on x which has a value of 1
좋지 않습니다. 우리는 우리가 얻은 것과 같은 종류의 가치 범주를 얻어야합니다! 해결책은 다음과 같습니다.
static_cast<T&&>(x);
이것은 무엇을 하는가? 우리가 deduce
함수 안에 있고 lvalue가 전달되었다고 가정하십시오. 이는 T
a A&
이므로 정적 캐스트의 대상 유형은 A& &&
또는 A&
입니다. x
는 이미 이므로 A&
아무 것도하지 않고 lvalue 참조가 남습니다.
When we've been passed an rvalue, T
is A
, so the target type for the static cast is A&&
. The cast results in an rvalue expression, which can no longer be passed to an lvalue reference. We've maintained the value category of the parameter.
Putting these together gives us "perfect forwarding":
template <typename A>
void f(A&& a)
{
E(static_cast<A&&>(a));
}
When f
receives an lvalue, E
gets an lvalue. When f
receives an rvalue, E
gets an rvalue. Perfect.
And of course, we want to get rid of the ugly. static_cast<T&&>
is cryptic and weird to remember; let's instead make a utility function called forward
, which does the same thing:
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
I think to have a conceptual code implementing std::forward can add to the discussion. This is a slide from Scott Meyers talk An Effective C++11/14 Sampler
Function move
in the code is std::move
. There is a (working) implementation for it earlier in that talk. I found actual implementation of std::forward in libstdc++, in file move.h, but it is not at all instructive.
From a user's perspective, the meaning of it is that std::forward
is a conditional cast to an rvalue. It can be useful if I am writing a function which expects either an lvalue or rvalue in a parameter and wants to pass it to another function as an rvalue only if it was passed in as an rvalue. If I did not wrap the parameter in std::forward, it would be always passed as a normal reference.
#include <iostream>
#include <string>
#include <utility>
void overloaded_function(std::string& param) {
std::cout << "std::string& version" << std::endl;
}
void overloaded_function(std::string&& param) {
std::cout << "std::string&& version" << std::endl;
}
template<typename T>
void pass_through(T&& param) {
overloaded_function(std::forward<T>(param));
}
int main() {
std::string pes;
pass_through(pes);
pass_through(std::move(pes));
}
Sure enough, it prints
std::string& version
std::string&& version
The code is based on an example from the previously mentioned talk. Slide 10, at about 15:00 from the start.
In perfect forwarding, std::forward is used to convert the named rvalue reference t1 and t2 to unnamed rvalue reference. What is the purpose of doing that? How would that effect the called function inner if we leave t1 & t2 as lvalue?
template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) { inner(std::forward<T1>(t1), std::forward<T2>(t2)); }
If you use a named rvalue reference in an expression it is actually an lvalue (because you refer to the object by name). Consider the following example:
void inner(int &, int &); // #1
void inner(int &&, int &&); // #2
Now, if we call outer
like this
outer(17,29);
we would like 17 and 29 to be forwarded to #2 because 17 and 29 are integer literals and as such rvalues. But since t1
and t2
in the expression inner(t1,t2);
are lvalues, you'd be invoking #1 instead of #2. That's why we need to turn the references back into unnamed references with std::forward
. So, t1
in outer
is always an lvalue expression while forward<T1>(t1)
may be an rvalue expression depending on T1
. The latter is only an lvalue expression if T1
is an lvalue reference. And T1
is only deduced to be an lvalue reference in case the first argument to outer was an lvalue expression.
How would that affect the called function inner if we leave t1 & t2 as lvalue?
If, after instantiating, T1
is of type char
, and T2
is of a class, you want to pass t1
per copy and t2
per const
reference. Well, unless inner()
takes them per non-const
reference, that is, in which case you want to do so, too.
Try to write a set of outer()
functions which implement this without rvalue references, deducing the right way to pass the arguments from inner()
's type. I think you'll need something 2^2 of them, pretty hefty template-meta stuff to deduce the arguments, and a lot of time to get this right for all cases.
And then someone comes along with an inner()
that takes arguments per pointer. I think that now makes 3^2. (Or 4^2. Hell, I can't be bothered to try to think whether const
pointer would make a difference.)
And then imagine you want to do this for a five parameters. Or seven.
Now you know why some bright minds came up with "perfect forwarding": It makes the compiler do all this for you.
A point that hasn't been made crystal clear is that static_cast<T&&>
handles const T&
properly too.
Program:
#include <iostream>
using namespace std;
void g(const int&)
{
cout << "const int&\n";
}
void g(int&)
{
cout << "int&\n";
}
void g(int&&)
{
cout << "int&&\n";
}
template <typename T>
void f(T&& a)
{
g(static_cast<T&&>(a));
}
int main()
{
cout << "f(1)\n";
f(1);
int a = 2;
cout << "f(a)\n";
f(a);
const int b = 3;
cout << "f(const b)\n";
f(b);
cout << "f(a * b)\n";
f(a * b);
}
Produces:
f(1)
int&&
f(a)
int&
f(const b)
const int&
f(a * b)
int&&
Note that 'f' has to be a template function. If it's just defined as 'void f(int&& a)' this doesn't work.
It may be worthwhile to emphasize that forward has to be used in tandem with an outer method with forwarding/universal reference. Using forward by itself as the following statements is allowed, but does no good other than causing confusion. The standard committee may want to disable such flexibility otherwise why don't we just use static_cast instead?
std::forward<int>(1);
std::forward<std::string>("Hello");
In my opinion, move and forward are design patterns which are natural outcomes after r-value reference type is introduced. We should not name a method assuming it is correctly used unless incorrect usage is forbidden.
참고URL : https://stackoverflow.com/questions/3582001/advantages-of-using-forward
'Programing' 카테고리의 다른 글
PHP 키워드 'var'의 기능은 무엇입니까? (0) | 2020.02.21 |
---|---|
Ruby의 이중 콜론`::`은 무엇입니까? (0) | 2020.02.21 |
기존 폴더에“git clone”하는 가장 좋은 방법은 무엇입니까? (0) | 2020.02.21 |
iOS DeviceSupport에서 데이터를 삭제할 수 있습니까? (0) | 2020.02.21 |
콘솔 앱의 '메인'메소드에서 '비동기'수정자를 지정할 수 없습니다. (0) | 2020.02.21 |