2015-01-09 5 views
5

작업은 모든 유형을 하나 (Foo)와는 별도로 전달하는 단일 인수 함수를 작성하여 변환합니다 (Bar로 변환).선택적 포워딩 기능

(Foo에서 Bar 로의 변환이 있다고 가정합시다.) (- 누군가가 제발 말해, 내가 실수를 한 경우 나/추출 원본 문장 here에서을 단순화하기 위해 노력했습니다.!)

template<typename Args...> 
void f(Args... args) 
{ 
    g(process<Args>(args)...); 
} 

가 : 여기

은 사용 시나리오

여기서 두 가지 가능한 구현 예이다 : ...

template<typename T> 
T&& process(T&& t) { 
    return std::forward<T>(t); 
} 

Bar process(Foo x) { 
    return Bar{x}; 
} 

,745,
template <typename T, typename U> 
T&& process(U&& u) { 
    return std::forward<T>(std::forward<U>(u)); 
} 

template <typename T> 
Bar process(Foo x) { 
    return Bar{x}; 
} 

두 번째 것이 더 좋은 좋은 권한 (here)이 있습니다.

그러나 주어진 설명을 이해할 수 없습니다. 나는 이것이 C++의 가장 어두운 부분을 탐구하고 있다고 생각한다.

나는 무슨 일이 일어나는지 이해하는 데 필요한 기계류가 누락 된 것 같습니다. 누군가 상세하게 설명 할 수 있습니까? 파기가 너무 힘들면 누구나 필수 전제 조건을 학습 할 수있는 자료를 추천 할 수 있습니까?

편집 : 필자는 특정 경우에 함수 서명이 this page의 typedef-s 중 하나와 일치 할 것이라고 덧붙이고 싶습니다. 즉, 모든 인수는 PyObject* (PyObject은 일반 C 구조체)이거나 const char*, int, float과 같은 기본 C 유형 중 하나가 될 것입니다. 그래서 내 짐작은 가벼운 구현이 가장 적절할 수 있다는 것입니다 (저는 지나친 일반화의 팬이 아닙니다). 그러나 저는 이러한 문제를 해결하기위한 올바른 사고 방식을 얻는 데 정말로 관심이 있습니다.

+2

이러한 예제에는 프로세스 함수가 ​​호출되는 컨텍스트가 없습니다. 항상 명시 적 형식 템플릿 인수가 있습니다 (적어도 비 lvalue-reference를 xvalues로 바꾸는 아이디어였습니다). 당신은 당신의 버전으로 도망 갈 수 있지만 대상 함수의 매개 변수를 초기화하는 동안 항상 copy ctor를 호출합니다. –

+0

버전 1이 어떻게 든 "더티"라는 것에 동의합니다. 왜냐하면 템플릿이 아닌 함수의 과부하가 템플릿과 다르기 때문입니다 반환 유형별. 그러나 버전 2에서는 어떤 의미도 볼 수 없습니다. 컴파일러가 사용 컨텍스트에서 그것을 추론 할 수 없으므로 항상 첫 번째 템플릿 매개 변수를 지정해야합니다. –

+0

@PiotrS., 문맥을 제공하기 위해 편집했습니다 (내 실제 사용 사례와 일치한다고 생각합니다). 지난 며칠 동안 귀중한 도움을 주신 덕분에, 저는 이제 작업 솔루션 ([here] (http://stackoverflow.com/q/27866483/435129))을 얻었습니다. 이것은 내가 이해하지 못하는 코드를 통합하는 것이 싫어서 내가 씹고있는 마지막 구성 요소입니다. –

답변

2

나는 당신이 직면 한 유스 케이스를 이해하는데 약간의 오해를 느낍니다.

모든

먼저,이 함수 템플릿입니다 :

struct A 
{ 
    template <typename... Args> 
    void f(Args... args) 
    { 
    } 
}; 

그리고이 함수 템플릿되지 않습니다 : (함수 템플릿) 인수의 형태 공제 이전의 정의에서

template <typename... Args> 
struct A 
{ 
    void f(Args... args) 
    { 
    } 
}; 

일어난다. 후자에는 유형 공제가 없습니다.

함수 템플릿을 사용하고 있지 않습니다. 클래스 템플릿에서 템플릿이 아닌 멤버 함수를 사용하고 있으며이 특정 멤버 함수의 경우 서명이 고정되어 있습니다.

template <typename T, T t> 
struct trap; 

template <typename R, typename... Args, R(Base::*t)(Args...)> 
struct trap<R(Base::*)(Args...), t> 
{  
    static R call(Args... args); 
}; 

와 그 멤버 함수를 참조하여 다음과 같은 :

&trap<decltype(&Base::target), &Base::target>::call; 

정적 템플릿이 아닌 call 함수에 대한 포인터와 함께 결국 아래처럼 trap 클래스를 정의함으로써

고정 된 서명으로 target 함수의 서명과 동일합니다.

이제 call 함수는 중간 호출자 역할을합니다.

template <typename R, typename... Args, R(Base::*t)(Args...)> 
struct trap<R(Base::*)(Args...), t> 
{  
    static R call(Args... args) 
    { 
     return (get_base()->*t)(args...); 
    } 
}; 

trap 클래스 템플릿이 인스턴스화하는 데 사용되는 target 기능을 가정 : 대답, 당신은 call 함수를 호출되며, 그 기능은 target의 매개 변수를 초기화하는 자신의 인수를 전달 target 멤버 함수를 호출합니다 정의 다음과 같이

struct Base 
{ 
    int target(Noisy& a, Noisy b); 
}; 

을 다음 call 기능을 끝낸다 trap 클래스를 인스턴스화하여 :

,536,913,632 10
// what the compiler *sees* 
static int call(Noisy& a, Noisy b) 
{ 
    return get_base()->target(a, b); 
} 

다행히도 a는 참조의해 전달되며, 이는 단지 target의 파라미터 참조 동종 의해 전달되고 결합된다.

  • 다음 Noisy 클래스가 이동하는 경우에 상관없이 또는 하나의 값에 의해 을 통과하기 때문에, 당신은 b 인스턴스의 복사본을 여러 개 만들고있어하지 - 불행히도, 이것은 b 개체에 대한 보유하지 않습니다

    첫 번째 경우 : call 함수가 외부 컨텍스트에서 자체 호출 될 때.

  • call의 본문에서 target 함수를 호출 할 때 b 인스턴스를 복사 할 때.

    DEMO 1

다소 비효율적이다 : 단지 당신이 가 xValue에 b 인스턴스를 켤 수 있다면 당신은 이동 - 생성자 호출로 돌려, 적어도 하나의 복사 생성자 호출을 저장 할 수 :

이제는 두 번째 매개 변수 대신 이동 생성자를 호출합니다.

지금까지는 그렇게 좋았지 만 수동으로 수행했습니다 (std::move은 이동 의미를 적용하는 것이 안전하다는 것을 알고 추가했습니다).? 이제 문제는 매개 변수 팩에 작동 할 때 동일한 기능을 적용 할 수있는 방법입니다 :

return get_base()->target(std::move(args)...); // WRONG! 

당신은 각각의 매개 변수 팩 내의 모든 인수에 std::move 전화를 적용 할 수 없습니다. 모든 인수에 똑같이 적용되면 컴파일러 오류가 발생할 수 있습니다.

DEMO 2

다행히도, 비록 Args...std::forward 도우미 함수가 대신 사용될 수있다하는 전달 참조 아니다. 즉 <T> 타입 std::forward<T>에 무슨 (좌변 참조 또는 비 좌변 참조)를 std::forward 다르게 행동 할 따라이다 좌변 참조 용

  • (예 TNoisy& 경우) 값 표현의 카테고리는 lvalue (즉, Noisy&)로 남습니다. 식의 값이 카테고리가 xValue된다 (즉 Noisy&&) (TNoisy&& 또는 일반 Noisy 경우 예) 비 - 참조의 좌변

  • . 가 xValue에 b 관련된 식의 값의 카테고리를 선회

    static int call(Noisy& a, Noisy b) 
    { 
        // what the compiler *sees* 
        return get_base()->target(std::forward<Noisy&>(a), std::forward<Noisy>(b)); 
    } 
    

    : 만약 끝낼

    static R call(Args... args) 
    { 
        return (get_base()->*t)(std::forward<Args>(args)...); 
    } 
    

    :

아래와 같이 target 함수를 정의하여, 즉 상기 데 b이고, 이는 Noisy&&입니다. 이를 통해 컴파일러는 a을 그대로두고 target 함수의 두 번째 매개 변수를 초기화하는 이동 생성자를 선택할 수 있습니다.

DEMO 3(DEMO 1 출력과 비교)

기본적으로, 이것은 std::forward가되는 것이다. 일반적으로 std::forward전달 참조과 함께 사용되며 여기에서 T은 전달 참조에 대한 유형 공제 규칙에 따라 추론 된 유형을 보유합니다. 해당 형식 (인수의 값 범주에 의존하지 않음)에 따라 다른 동작을 적용하므로 항상 <T> 부분을 명시 적으로 전달해야합니다. 명시 적 형식 템플릿 인수 <T>이 없으면 std::forward은 (매개 변수 팩을 확장 할 때처럼) 이름을 통해 참조되는 인수에 대한 좌변 값 참조를 항상 추론합니다.

추가적으로은 다른 유형을 전달하는 동안 한 유형에서 다른 유형으로 일부 인수를 변환하려고합니다.당신이 매개 변수 팩에서 std::forward ING 인수와 트릭에 대해 걱정하지 않는다, 그것은 항상 복사 생성자를 호출 괜찮 경우, 다음 버전 확인이다 : 그러나

template <typename T>   // transparent function 
T&& process(T&& t) { 
    return std::forward<T>(t); 
} 

Bar process(Foo x) {   // overload for specific type of arguments 
    return Bar{x}; 
} 

//... 
get_base()->target(process(args)...); 

DEMO 4

당신이 데모에서 그 Noisy 인수의 사본을 피하려는 경우, 당신은 std::forward 적절한 동작을 적용 할 수 있도록 어떻게 든 (즉, process 전화, Args 종류 이상 패스 t을 std::forward 전화를 결합해야 xvalues로 보내거나 아무것도하지 않음). 방금 구현할 수있는 방법에 대한 간단한 예를 들었습니다.

template <typename T, typename U> 
T&& process(U&& u) { 
    return std::forward<T>(std::forward<U>(u)); 
} 

template <typename T> 
Bar process(Foo x) { 
    return Bar{x}; 
} 

//... 
get_base()->target(process<Args>(args)...); 

그러나 이는 방금 옵션 중 하나 일뿐입니다. 당신이 process 기능 (버전)을 호출하기 전에 std::forward 있도록 호출 할 때, 간단하게 다시 작성하거나 다시 정렬 할 수 있습니다

get_base()->target(process(std::forward<Args>(args))...); 

DEMO 5

그리고 (DEMO 4의 출력을 비교) 잘 작동합니다 (즉, 버전과 함께). 따라서 요점은 추가 코드 std::forward은 코드를 조금 최적화하는 것이며, provided idea은 해당 기능을 구현할 수있는 한 가지 예일뿐입니다 (알 수 있듯이 동일한 효과가 나타납니다).

0

버전 2의 첫 번째 부분으로 충분하지 않습니까? 만 : 당신은 첫 번째 템플릿 매개 변수를 지정하도록 강요

struct Foo { 
    int x; 
}; 
struct Bar { 
    int y; 
    Bar(Foo f) { 
     y = f.x; 
    } 
}; 
int main() { 

    auto b = process<Bar>(Foo()); // b will become a "Bar" 
    auto i = process<int>(1.5f); 
} 

(유형 :

template <typename T, typename U> 
T&& process(U&& u) { 
    return std::forward<T>(std::forward<U>(u)); 
} 

같은 ("푸"에서 "바"에 대한 생성자) 기존의 변환과 사용 사례를 감안할 때 왜냐하면 컴파일러가 그것을 추론 할 수 없기 때문입니다. 따라서 생성자가 있기 때문에 어떤 유형을 기대하며 "Bar"유형의 임시 객체를 생성합니다.

+0

처음에는 질문에 불충분하게 말했습니다. 질문이 명확 해졌으며 대답이 (불행히도) 잘못되었습니다. –