답변:
전달 문제를 이해해야합니다. 전체 문제를 자세히 읽을 수 있지만 요약하겠습니다.
기본적으로 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에서 컴파일러가 수행하는 작업입니다.)
C ++ 11에서는이 문제를 해결할 기회가 있습니다. 한 솔루션은 기존 유형의 템플릿 공제 규칙을 수정하지만 이로 인해 많은 코드가 손상 될 수 있습니다. 따라서 다른 방법을 찾아야합니다.
해결책은 새로 추가 된 rvalue-references 를 대신 사용하는 것입니다 . rvalue-reference 유형을 추론 할 때 새로운 규칙을 도입하고 원하는 결과를 만들 수 있습니다. 결국, 우리는 지금 코드를 깨뜨릴 수 없습니다.
참조에 대한 참조를 제공하는 경우 (참고 참조는 모두 의미 에워싸는 용어 T&
와 T&&
, 우리는 결과 유형 알아 내기 위해 다음과 같은 규칙을 사용) :
"유형 T에 대한 참조 인 유형 TR,"cv TR에 대한 lvalue reference "유형을 작성하려는 시도는"lvalue reference to T "유형을 작성하는 반면,"rvalue reference to type cv TR”은 유형 TR을 만듭니다. "
또는 표 형식으로 :
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)
다음으로, 템플릿 인수 추론 : 인수가 lvalue A이면 템플릿 인수에 lvalue 참조를 A에 제공합니다. 그렇지 않으면 정상적으로 추론됩니다. 이것은 소위 범용 참조를 제공합니다 ( 포워딩 참조 는 이제 공식 참조 입니다).
이것이 왜 유용한가요? 결합하여 유형의 값 범주를 추적하는 기능을 유지합니다. lvalue 인 경우 lvalue-reference 매개 변수가 있고 그렇지 않으면 rvalue-reference 매개 변수가 있습니다.
코드에서 :
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 참조가 남습니다.
rvalue, T
is 가 전달 A
되면 정적 캐스트의 대상 유형은 A&&
입니다. 캐스트로 인해 rvalue 표현식이 생성 되어 더 이상 lvalue reference로 전달할 수 없습니다 . 매개 변수의 값 범주를 유지했습니다.
이를 종합하면 "완벽한 전달"이됩니다.
template <typename A>
void f(A&& a)
{
E(static_cast<A&&>(a));
}
때 f
좌변를 수신, E
좌변을 가져옵니다. 때 f
를 rvalue를 수신 E
를 rvalue를 가져옵니다. 완전한.
그리고 물론, 우리는 못생긴 것을 제거하고 싶습니다. static_cast<T&&>
기이하고 기억하기가 이상합니다. 대신이라는 유틸리티 함수를 만들어 봅시다 forward
.
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
const int i
같이 승인됩니다. A
가 추론됩니다 const int
. 실패는 rvalues 리터럴에 대한 것입니다. 또한 deduced(1)
x 에 대한 호출에 대해서는 x가 int&&
아닙니다 int
(완전한 전달은 x
값을 기준으로하는 매개 변수 인 경우처럼 복사를하지 않습니다 ). 단지 T
입니다 int
. x
전달자에서 lvalue로 평가 되는 이유 는 명명 된 rvalue 참조가 lvalue 표현식이되기 때문입니다.
forward
또는 move
여기 에 사용에 차이가 있습니까? 아니면 의미상의 차이입니까?
std::move
명시적인 템플릿 인수없이 호출해야하며 항상 rvalue가 발생하지만 결과는 std::forward
어느 쪽이든 될 수 있습니다. 사용 std::move
이 더 이상 당신을 알지 값을 필요로하고 다른 곳으로 이동하려면, 사용은 std::forward
해야 할 일이 함수 템플릿에 전달 된 값에 따라.
std :: forward를 구현하는 개념적 코드가 토론에 추가 될 수 있다고 생각합니다. Scott Meyers 의 효과적인 슬라이드 C ++ 11 / 14 샘플러
move
코드의 기능 은 std::move
입니다. 이 대화의 앞부분에 (작동) 구현이 있습니다. libstdc ++ 에서 파일 move.h 에서 std :: forward의 실제 구현을 찾았 지만 전혀 유익하지는 않습니다.
사용자 관점에서 볼 때 그 의미 std::forward
는 rvalue 로의 조건부 캐스트입니다. 매개 변수에 lvalue 또는 rvalue가 필요하고 rvalue로 전달 된 경우에만 rvalue로 다른 함수에 전달하려는 함수를 작성하는 경우 유용 할 수 있습니다. std :: forward에서 매개 변수를 줄 바꿈하지 않으면 항상 일반 참조로 전달됩니다.
#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));
}
충분히, 그것은 인쇄
std::string& version
std::string&& version
이 코드는 앞에서 언급 한 대화의 예를 기반으로합니다. 시작부터 약 15:00에 슬라이드 10.
완벽한 전달에서 std :: forward는 명명 된 rvalue 참조 t1 및 t2를 명명되지 않은 rvalue 참조로 변환하는 데 사용됩니다. 그 목적은 무엇입니까? t1 & t2를 lvalue로 남겨두면 호출 된 함수 내부에 어떤 영향을 미칩니 까?
template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) { inner(std::forward<T1>(t1), std::forward<T2>(t2)); }
표현식에서 명명 된 rvalue 참조를 사용하는 경우 실제로는 lvalue입니다 (이름으로 객체를 참조하기 때문). 다음 예제를 고려하십시오.
void inner(int &, int &); // #1
void inner(int &&, int &&); // #2
자, 이렇게 전화 outer
하면
outer(17,29);
17과 29는 정수 리터럴이고 rvalue이므로 17과 29를 # 2로 전달하고 싶습니다. 그러나 이후 t1
와 t2
표현에서 inner(t1,t2);
, 당신은 # 1 대신 2를 호출 할 것 lvalues입니다. 그렇기 때문에 참조를로 명명되지 않은 참조로 되돌려 야 std::forward
합니다. 따라서 t1
in outer
은 항상 lvalue 표현식 인 반면 forward<T1>(t1)
에 따라 rvalue 표현식 일 수 있습니다 T1
. 후자는 T1
lvalue 참조 인 경우 lvalue 표현식 일뿐 입니다. 그리고 T1
outer에 대한 첫 번째 인수가 lvalue 표현식 인 경우에만 lvalue 참조로 추정됩니다.
t1 & t2를 lvalue로 남겨두면 호출 된 함수 내부에 어떻게 영향을 미칩니 까?
인스턴스화 한 후 T1
유형 char
이고 T2
클래스 인 t1
경우 사본 및 참조 t2
마다 전달하려고합니다 const
. 글쎄, 참조가 inner()
아닌 const
, 즉 당신이 그렇게하고 싶어하지 않는 한.
outer()
rvalue 참조없이 이것을 구현 하는 함수 세트를 작성하여 inner()
의 유형 에서 인수를 전달하는 올바른 방법을 추론하십시오 . 나는 당신이 그들 중 2 ^ 2가 필요하고, 주장을 추론하기 위해 꽤 무거운 템플릿 메타 재료가 필요하며, 모든 경우에 이것을 올바르게 할 수있는 많은 시간이 필요하다고 생각합니다.
그리고 누군가가 inner()
포인터 당 인수를 취하는 것을 따라옵니다 . 나는 지금 3 ^ 2를 만든다고 생각합니다. (또는 4 ^ 2. 지옥, 나는 const
포인터가 변화를 일으킬 지 생각하려고 귀찮게 할 수 없다 .)
그런 다음 5 개의 매개 변수에 대해이 작업을 수행한다고 가정하십시오. 아니면 일곱.
이제 당신은 왜 어떤 밝은 생각들이 "완벽한 전달"을했는지 알 것입니다 : 그것은 컴파일러가 당신을 위해이 모든 것을하게합니다.
명확하지 않은 요점 static_cast<T&&>
은 const T&
제대로 처리 한다는 것입니다.
프로그램:
#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);
}
생산 :
f(1)
int&&
f(a)
int&
f(const b)
const int&
f(a * b)
int&&
'f'는 템플릿 함수 여야합니다. 'void f (int && a)'로 정의 된 경우 작동하지 않습니다.
forwarding / universal reference가있는 외부 방법과 함께 forward를 사용해야한다는 점을 강조하는 것이 좋습니다. 다음 진술로 자체적으로 앞을 사용하는 것은 허용되지만 혼동을 유발하는 것 외에 다른 것은 아닙니다. 표준위원회는 그러한 유연성을 비활성화하고 싶을 수도 있습니다. 그렇지 않으면 왜 static_cast를 대신 사용하지 않습니까?
std::forward<int>(1);
std::forward<std::string>("Hello");
제 생각에, 앞으로 나아가는 것은 r- 값 참조 유형이 도입 된 후 자연스러운 결과 인 디자인 패턴입니다. 잘못된 사용법이 금지되지 않는 한 올바르게 사용되었다고 가정하여 분석법의 이름을 지정해서는 안됩니다.
f
표현식이 아닌 함수가 아니겠습니까 ?