2016-08-12 34 views
2

我有兩個可變參數構造一個結構,在該第二個的前面由單個int const*參數的存在來區分:參數包列表膨脹引起可變參數構造函數重載失敗

struct S 
{ 
    template<class... Args> S(Args... args) 
    { 
     int arr[sizeof...(args)] = { args... }; // [A] 
    } 
    template<class... Args> S(int const *p, Args... args) {} // [B] 
}; 

int main() 
{ 
    int i = 1; 
    S s(&i, 1, 2); // [C] 
    return 0; 
} 

使用Visual C++ 2015 ,行[A]導致:error C2440: 'initializing': cannot convert from 'int *' to 'int'。如果我註釋掉[A]行,那麼它會編譯好的和[C]的調用[B],所以看起來初始化程序列表的存在會導致編譯器查看函數內部並變得貪婪,以嘗試將所有爭論列表轉換爲int,忽略解釋[B]。

如果我繼續[A],但改變[C],以S s((int const*)&i, 1, 2);構造函數調用,然後它也編譯好了,[A]似乎防止int*隱式轉換爲int const*通常適用於常規功能。

如果我不想在所有構造函數調用中顯式地使用const轉換指針,而不是使用行[A],我可以使用遞歸函數將參數包解壓縮到int數組中,但這更加混亂。最簡單的解決方案就是將[B]更改爲template<class... Args> S(int *p, Args... args) {},但那麼隱藏我的意圖不會被更改爲*p

+0

你可以讓'int i'爲'constexpr int i'嗎? – AndyG

+0

我可以將此示例更改爲'int const i = 1',它可以工作,但通常情況下,我並不總是傳遞一個int const的地址,通常是傳遞一個數組。 –

回答

0

重載可變模板可能會變得非常噩夢。一個辦法是增加一個重載需要一個int*並委託給int const*構造:

template<class... Args> S(int *p, Args... args) : 
    S(static_cast<int const*>(p), args...) { } 

您可能要考慮一下完美轉發args在這種情況下,如果你將要路過周圍大班。

+0

一致認爲這有效,但用戶不清楚哪一個被調用,以及他們的數據是否受到保護。我需要看看完美的轉發。 –

0

這是因爲它會選擇您的第一個構造函數,而不是第二個構造函數。原因是,&i綁定到int* const的引用。

你可以做的是使用type_traits選擇構造函數,像這樣的(C++ 14):

template<typename First, typename ...Args> 
S(T && t, Args && ...args) : 
     S( std::is_same<int,std::remove_reference_t<std::remove_cv<T>>>(), 
      std::forward<T>(t), std::forward<Args>(args)...) {} 

template<typename ...Args> 
S(std::false_type, int* const p, Args&&...args) //[A] 

template<typename ...Args> 
S(std::true_type, Args&&...args) //[B] 
+0

缺少'is_same'的第二個參數。 – Oktalist

1

[A]選擇的原因是因爲參數包可以完全匹配int*,所以[B]不需要const轉換。

一種選擇是使用重載的助手簡單地提供一個「值拼命三郎」,你可以在參數包致電:

int get_value_impl(int _in) 
{ 
    return _in; 
} 

int get_value_impl(const int* _in) 
{ 
    return *_in; 
} 


struct S 
{ 
    template<class... Args> S(Args... args) 
    { 
     int arr[sizeof...(args)] = { get_value_impl(args)... }; // [A] 
    } 
}; 

int main() 
{ 
    int i = 1; 
    S s(&i, 1, 2); // [C] 
    return 0; 
} 

現在你不需要[B]在所有。

如果您有更多類型,可以將更多的重載添加到get_value_impl,並且您將保留S的結構。如果事情變得比這更復雜,你可能要考慮使用SFINAE來選擇get_value_impl的實現。我認爲這個解決方案是一個更簡單的標籤分發。 Demo

+0

對不起,我的例子不是很清楚,我實際上想要[B]的不同行爲。通常,當我調用[B]時,我傳遞一個int數組作爲第一個參數,它是一堆數據,我希望將它們與其餘的參數(僅爲int值)分開處理。我發現參數包可以匹配'int *',但我不認爲這就是[B]被忽略的原因,因爲如果你註釋掉[A]行(僅行,離開函數),那麼[ B]確實被調用。所以不知何故,它是行[A]上的初始化程序列表的存在,這會導致編譯器忽略[B]。 –

+0

@GeorgeSkelton:如果你註釋掉了它會調用[B]的行是錯誤的。 [實施例](http://coliru.stacked-crooked.com/a/3113521352df1ad8)。由於我所說的原因,它稱爲A,這是參數列表中的精確類型匹配。如果你真的想讓[B]被調用,那麼你需要使[A]更糟糕的候選人,你可以使用標籤調度或SFINAE。 – AndyG