我在您對所面對的用例的理解中感覺到一個小小的誤解。
首先,這是一個函數模板:
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
實例的多個副本,因爲一個是按值傳遞:
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
將表現不同:
有了這樣的表示,通過定義像下面的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只是這個功能的可能實現之一(正如你所看到的,它帶來了同樣的效果)。
這些示例缺少調用進程函數的上下文 - 總是使用顯式類型模板參數(至少這是將非左值引用轉換爲xvalues的想法)。你可以逃脫你的版本,但它會始終調用copy ctor,同時初始化目標函數的參數 – 2015-01-09 22:45:19
我同意版本1在某種程度上是「髒的」,因爲它在模板化函數上的非模板函數的重載,它們只是不同通過返回類型。但是對於版本2我看不到任何意義。無論如何,您總是必須指定第一個模板參數,因爲編譯器無法在使用上下文中推導出它。 – 2015-01-09 23:02:47
@PiotrS。,我編輯提供了一個上下文(我認爲它符合我的實際使用情況)。感謝這幾天的寶貴幫助,我現在有一個工作解決方案([here](http://stackoverflow.com/q/27866483/435129))。這是我咀嚼的最後一個組件,因爲我不喜歡合併我不明白的代碼。 – 2015-01-09 23:23:47