2015-01-09 67 views
5

任務是創建一個單參數函數,將所有類型轉換爲一個(Foo),並將其轉換爲(Bar)。選擇性轉發功能

(讓我們假設存在從Foo到Bar的轉換)。

下面是使用場景:

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

(我試過提取/從原來的背景下here簡化它 - 如果我犯了一個錯誤,請有人告訴我!)

這裏有兩種可能的實現:

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

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

而且......

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結構),要麼是一些基本的C類型,如const char*,int,float。所以我的猜測是,輕量級的實現可能是最合適的(我不是過度泛化的粉絲)。但我真的很想獲得正確的思維方式來解決這些問題。

+2

這些示例缺少調用進程函數的上下文 - 總是使用顯式類型模板參數(至少這是將非左值引用轉換爲xvalues的想法)。你可以逃脫你的版本,但它會始終調用copy ctor,同時初始化目標函數的參數 – 2015-01-09 22:45:19

+0

我同意版本1在某種程度上是「髒的」,因爲它在模板化函數上的非模板函數的重載,它們只是不同通過返回類型。但是對於版本2我看不到任何意義。無論如何,您總是必須指定第一個模板參數,因爲編譯器無法在使用上下文中推導出它。 – 2015-01-09 23:02:47

+0

@PiotrS。,我編輯提供了一個上下文(我認爲它符合我的實際使用情況)。感謝這幾天的寶貴幫助,我現在有一個工作解決方案([here](http://stackoverflow.com/q/27866483/435129))。這是我咀嚼的最後一個組件,因爲我不喜歡合併我不明白的代碼。 – 2015-01-09 23:23:47

回答

2

我在您對所面對的用例的理解中感覺到一個小小的誤解。

首先,這是一個函數模板:

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

這是不是一個函數模板:

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

在前者定義(與函數模板)的參數類型推導發生。在後者中,沒有類型扣除。

您沒有使用函數模板。您正在使用類模板中的非模板成員函數,並且對於此特定成員函數,其簽名是固定的。

通過定義trap類象下面這樣:

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函數一個固定的簽名,與target函數的簽名相同。

現在,call函數充當中間調用者。你會被調用call功能,該功能將調用target成員函數,傳遞自己的參數初始化target的參數,說:

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功能定義如下:

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

通過實例化trap類你結束了以下call功能:

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

幸運的是,a通過引用傳遞,它只是轉發和結合由相同種在target的參數參考。不幸的是,這並不適用於該b對象 - 不管是否Noisy類是動產或沒有,你正在做的b實例的多個副本,因爲一個是按值傳遞

  • 第一個:當call函數從外部上下文中調用時。

  • 第二個:從call正文調用target函數時複製b實例。

DEMO 1

這有點低效的:你可以保存至少一個拷貝構造函數的調用,把它變成一個移動構造函數調用,如果只有你能打開b實例爲的x值

現在它會調用移動構造函數,而不是第二個參數。

到目前爲止很好,但這是手動完成的(std::move補充知道應用移動語義是安全的)。現在的問題是,如何可以在相同的功能上參數包工作時,應該適用於:?

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

您可以std::move電話並不適用於每參數組內的每個參數。如果同樣適用於所有參數,這可能會導致編譯器錯誤。

DEMO 2

幸運的是,即使Args...不是轉發參考,在std::forward輔助函數可以用來代替。也就是說,取決於<T>類型是std::forward<T>什麼(左值引用或非左值引用)的std::forward將表現不同:

  • 爲左值引用(例如,如果TNoisy&):價值表達的類別仍然是一個左值(即Noisy&)。

  • 非左值引用(例如,如果是TNoisy&&或純Noisy):表達式的值類別變爲x值(即Noisy&&)。

有了這樣的表示,通過定義像下面的target功能:

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

你結束了:

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

轉彎涉及b的表達式的值的類別爲x值b,這是Noisy&&。這讓編譯器選擇移動構造函數來初始化target函數的第二個參數,並保留a完整。

DEMO 3(比較DEMO 1的輸出)

基本上,這是什麼std::forward爲。通常,std::forward轉發參考一起使用,其中T保持根據轉發參考的類型推演規則推導的類型。請注意,它始終要求您明確傳遞<T>部分,因爲它將根據該類型應用不同的行爲(不取決於其參數的值類別)。如果沒有明確的類型模板參數<T>,std::forward將始終推斷通過其名稱引用的參數的左值引用(如擴展參數包時)。

現在,您還想另外將一些參數從一種類型轉換爲另一種類型,同時轉發所有其他類型的參數。如果你不關心從參數包std::forward ING參數的伎倆,它的優良隨時調用拷貝構造函數,那麼你的版本是OK

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類型,使std::forward可以申請適當的行爲(T進入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)...); 

但這只是其中一個選項。它可以簡化,改寫,或重新排序,從而使std::forward調用process功能(你的版本)之前被稱爲:

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

DEMO 5

它(與DEMO 4比較輸出)也可以很好地工作(也就是說,你的版本)。所以問題是,額外的std::forward只是爲了優化你的代碼,而provided idea只是這個功能的可能實現之一(正如你所看到的,它帶來了同樣的效果)。

0

版本2的第一部分不會足夠嗎?只有:

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

給定的使用情況與現有的轉換(構造函數「酒吧」,從「富」),如:

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); 
} 

你被迫指定第一個模板參數(類型無論如何,因爲編譯器不能推斷它。所以它知道你期望的類型,並且會構造一個類型爲「Bar」的臨時對象,因爲它有一個構造函數。

+0

調用。起初問題措辭不佳。問題已被澄清,您的答案(不幸)是錯誤的。 – 2015-01-10 00:05:00