2015-01-16 58 views
5

在以下代碼:與局部參數包可變參數的輔助函數

#include <iostream> 

struct Base { 
    virtual ~Base() = default; 
    template <typename T, typename... Args> void helper (void (T::*)(Args..., int), Args...); 
    void bar (int n) {std::cout << "bar " << n << std::endl;} 
}; 

struct Derived : Base { 
    void baz (double d, int n) {std::cout << "baz " << d << ' ' << n << std::endl;} 
}; 

template <typename T, typename... Args> 
void Base::helper (void (T::*f)(Args..., int), Args... args) { 
    // A bunch on lines here (hence the motivation for the helper function) 
    for (int n = 0; n < 5; n++) 
     (dynamic_cast<T*>(this)->*f)(args..., n); 
    // ... 
} 

int main() { 
    Base b; 
    Derived d; 
    b.helper(&Base::bar); // GCC 4.8.1 will accept this, Visual Studio 2013 won't. 
    d.helper<Derived, double>(&Derived::baz, 3.14); // Visual Studio 2013 will accept this, GCC 4.8.1 won't 
} 

我不能獲得任一GCC4.8.1或VS2013編譯上述兩條線。他們將只編譯一個而不編譯另一個(並且他們不同意哪一行是正確和不正確的)。錯誤消息指出兩個編譯器都失敗了模板扣除。那麼究竟是什麼錯誤?我已經把所有的模板參數放在最後一行(我認爲可以推導出來),但它仍然不能由GCC推導出來,儘管VS可以。然而,當我放置模板參數時,VS不能推導出b.foo(&Base::bar);行的模板參數,但GCC可以推導出它們而沒有任何模板參數。完全在這裏困惑。這兩個編譯器都在這裏竊聽?程序員的任何可能的修復?

+0

東西告訴我,兩條線都是無效的,但我不能拿出一個理由爲什麼呢。 – Barry

+0

@Barry。我希望你是對的。然後找出main()中正確的兩行就可以解決問題,而不用擔心編譯器會有任何問題。 – prestokeys

+0

你正在得到完美的轉發錯誤:一個轉發雙重扣除。我認爲一種用途是不可誘導的。 – Yakk

回答

2

我不會寫的第一個參數作爲成員函數指針在所有。

在你的特殊情況下,它需要把第一個Args...放到一個非推導的上下文中 - 並且標準清楚地表明事後會發生什麼,尤其是考慮到[temp.deduct.call]/p1

當函數參數包出現在未推導的上下文中時 (14.8.2.5)中,該參數包的類型永遠不會被推斷出來。

我不知道這個規則的含義是什麼,當你寫void (T::*)(typename identity<Args>::type..., int)而不是。編譯器也不同意。

即使在正常情況下,你必須寫一些12過載來匹配成員函數指針(4可能CV限定符SEQ s乘以3可能REF-限定符)的所有可能的形式。在你的情況下,跳過一些(如volatile&&)可能是安全的,但它仍然是令人討厭的代碼重複。另外,如果在推導的上下文中使用兩次Args...,則它們將被獨立推導出來,並且推導出的類型必須完全匹配,這對最終用戶可能會變得混亂。 (?std::max(1, 2.5),任何人)

相反,我只想把它寫成一個指針到成員:

template <typename T, typename... Args, typename R> 
void Base::helper (R T::*f, Args... args) { 
    // A bunch of lines here (hence the motivation for the helper function) 
    for (int n = 0; n < 5; n++) 
     (dynamic_cast<T*>(this)->*f)(args..., n); 
    // ... 
} 

R T::*比賽所有成員指針;當你傳遞一個指向成員函數的指針時,R被推斷爲函數類型。如果你想強制R-must-a-function功能,你可以在std::is_function<R>::value上使用static_assert

Demo

+0

答案很晚,但這對於數據成員很少使用的指針顯示出明確的良好用途。 – prestokeys

+0

完美的轉發問題呢?讓'Derived'有重載'void baz(double&d,int n){std :: cout <<「baz,double &\n";}'和'void baz(double && d,int n){std :: cout <<」 baz,雙&&\n";}'。如何照顧呢? – prestokeys

5

我認爲這兩個調用都是無效的,因爲兩者都涉及一個非推導的上下文。從§14.8.2.5:

非推導上下文是:

- [..]

- 這不會在參數聲明的端部發生的函數參數包 - 列表

當以包含非推斷上下文的方式指定類型名稱時,包含該類型名稱的所有類型也都是未推導出來的。

如果您有void (T::*f)(Args..., int),即處於非推導的上下文中,因爲函數內部的函數參數包不會在末尾發生。事實上,指向成員的參數列表是非推導的,這使得整個函數調用不被推導出來。因此,該呼叫不能推導出:

b.helper(&Base::bar); 

對於第二個,即使它看起來好像你是明確指定Args...,爭論void (T::*f)(Args..., int)仍處於非推斷的上下文,因此編譯器沒有辦法知道如果更多Args是必要的。

一種解決方案是這樣給力的是參數沒有使用,比如說,身份招向後推斷,:

template <typename T, typename... Args> 
void foo (void (T::*)(typename identity<Args>::type..., int), Args...); 

這樣的話,這兩個行的編譯:

b.helper(&Base::bar); 
d.helper<Derived, double>(&Derived::baz, 3.14); 

雖然現在你必須確保你得到Args...完全正確,如果你沒有明確指定它。

+0

Paging @Columbo :) – Barry

+0

我不確定我是否確信。 'Args ...'已經在'void(T :: * f)(Args ...,int)'的非推導語境中,那麼爲什麼要把它放在另一個非推導的上下文層中? –

+0

@ T.C。嗯。我在這裏完全錯了嗎? – Barry

6

參數包必須放置在參數列表的末尾才能自動推導出來。

編譯器無法從給定參數列表中推導出(Args..., int),而是使用(int, Args...)代替,程序將編譯。

#include <iostream> 

struct Base { 
    virtual ~Base() = default; 
    template <typename T, typename... Args> void helper (void (T::*)(int, Args...), Args...); 
    void bar (int n) {std::cout << "bar " << n << std::endl;} 
}; 

struct Derived : Base { 
    void baz (int n, double d) {std::cout << "baz " << d << ' ' << n << std::endl;} 
}; 

template <typename T, typename... Args> 
void Base::helper (void (T::*f)(int, Args...), Args... args) { 
    // A bunch on lines here (hence the motivation for the helper function) 
    for (int n = 0; n < 5; n++) 
     (dynamic_cast<T*>(this)->*f)(n, args...); 
    // ... 
} 

int main() { 
    Base b; 
    Derived d; 
    b.helper(&Base::bar); 
    d.helper<Derived, double>(&Derived::baz, 3.14); 
} 

如果你必須把int在參數列表的末尾,你可以使用identity伎倆@Barry說。

準系統identity實現可以簡單:

template<typename T> 
struct identity { 
    typedef T type; 
}; 

然後你可以手工推斷參數類型:

template <typename T, typename... Args> 
void Base::helper (void (T::*f)(typename identity<Args>::type..., int), typename identity<Args>::type... args) { 
    // A bunch on lines here (hence the motivation for the helper function) 
    for (int n = 0; n < 5; n++) 
     (dynamic_cast<T*>(this)->*f)(args..., n); 
    // ... 
} 

b.helper<Base>(&Base::bar); 
d.helper<Derived, double>(&Derived::baz, 3.14); 
+0

不錯的簡單修復。非常感謝。 – prestokeys

+0

我仍然懷疑是否完美轉發參數...在這裏仍然是錯誤的(作爲一個部分參數包)。雖然我會聽取Yakk的建議,不這樣做。別人對此有何評論?一切看起來都很好,而且完美的轉發(測試),但我不知道實際上是否好。 – prestokeys