2016-03-17 76 views
9

下面的代碼是一個遞歸可變參數函數過載的一個典型例子。在這兩種鐺和GCC,它編譯乾淨,並main返回36(如預期):是一個C++ 11 variadic函數模板重載與依賴類型模糊?

template <typename T> 
int add(T val) 
{ 
    return val; 
} 

template <typename FirstTypeT, typename... RestT> 
int add(FirstTypeT first_value, RestT... rest) 
{ 
    return first_value + add<RestT...>(rest...); 
} 

int main(void) 
{ 
    return add(12, 12, 12); 
} 

然而,這裏有一個輕微的修改。它使用的模板定義一個依賴型,而不是直接的模板參數:

struct Foo 
{ 
    using SomeType = int; 
}; 

template <typename T> 
int add(typename T::SomeType val) 
{ 
    return val; 
} 

template <typename FirstT, typename... RestT> 
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest) 
{ 
    return first_value + add<RestT...>(rest...); 
} 

int main(void) 
{ 
    return add<Foo, Foo, Foo>(12, 12, 12); 
} 

它編譯和運行爲使用GCC 5.2預期,但fails使用鐺3.8:

clang++ variadic.cpp -o var -std=c++11 -Wall 
variadic.cpp:15:26: error: call to 'add' is ambiguous 
    return first_value + add<RestT...>(rest...); 
         ^~~~~~~~~~~~~ 
variadic.cpp:15:26: note: in instantiation of function template specialization 'add<Foo, Foo>' requested here 
    return first_value + add<RestT...>(rest...); 
         ^
variadic.cpp:20:12: note: in instantiation of function template specialization 'add<Foo, Foo, Foo>' requested here 
    return add<Foo, Foo, Foo>(12, 12, 12); 
     ^
variadic.cpp:7:5: note: candidate function [with T = Foo] 
int add(typename T::SomeType val) 
    ^
variadic.cpp:13:5: note: candidate function [with FirstT = Foo, RestT = <>] 
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest) 
    ^
1 error generated. 

我的問題是雙重的。

  1. 是否真的有效使用參數包類型名稱模式的適用範圍解析運營商包中的每個成員在typename RestT::SomeType...
  2. 是鏗鏘正確的面對面的人的標準,或者這是一個錯誤?第二個例子真的比第一個更模糊嗎? (對於第一個例子,好像你可以說,一個參數超載是曖昧與第二與RestT = <>實例化)
+1

MSVC15編譯兩個樣本。鏗鏘在這裏很奇怪,但我沒有一個標準的參考文獻引用來說誰是正確的,誰是錯的。 – Niall

+2

基於這個缺陷([CWG1395](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1395)),我認爲這是一個叮叮聲蟲。我無法找到爲什麼嵌入式會產生變化的任何事情。 – Niall

+1

部分排序在這裏很棘手,因爲所有內容都在非推導的上下文中。 –

回答

7
  1. 是的,這很好。
  2. 目前的措詞是在這個非常明確:參數組被部分排序過程中完全被忽略,因爲有它([temp.deduct.partial]/(3.1))沒有參數。 [temp.func.order]/5還給出了一個非常的點例如,即使推斷出模板參數 - 這說明你的第一個例子也是模棱兩可:

    [:由於呼叫上下文偏序只考慮對其中有明確的調用參數的參數,一些參數被忽略(即,函數參數包,使用默認參數, 和省略號參數參數)。 [...] [

    template<class T, class... U> void f(T, U ...); // #1 
    template<class T   > void f(T  ); // #2 
    
    void h(int i) { 
        f(&i); // error: ambiguous 
        // [...] 
    } 
    

    然而,這不是最佳的。有core issue 1395的可變參數模板部分排序:

    CWG一致認爲,例如應該被接受,處理這種情況下,後期決勝局,在參數包寧願省略的參數。

    Issue 1825給出了一個更精確的策略。)這兩種編譯器都爲第一種情況實現了這個規則;只有GCC爲第二個(即可以被認爲是半步)。

+0

嗯,我想那個clang也實現了1395的方向,因爲它可以與推斷的上下文情況一致。也許差別在於它沒有實現1391? –

+0

@ T.C。 1391的決議是有爭議的,可能是不好的。沒有實施是有道理的。 – Columbo

+0

感謝您的回答,那正是我一直在尋找的。 我可能會提交一個Clang的錯誤,因爲他們似乎打算實施1395(例如給出這個錯誤報告https://llvm.org/bugs/show_bug.cgi?id=14372) –

0

錯誤信息已經顯示出的原因。

當生成加載(12),有兩個可用的模板函數。這是

template <typename T> 
int add(typename T::SomeType val); 

template <typename FirstT, typename... RestT> 
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest); 
// and RestT is empty here(RestT = <>) 

這不是一個標準的使用和鐺是正確的。

請考慮這段代碼。

#include <tuple> 
#include <type_traits> 

struct Foo 
{ 
    using SomeType = int; 
}; 

// helper function to sum a tuple of any size 
template<typename Tuple, std::size_t N> 
struct TupleSum { 
    typedef typename std::tuple_element<N - 1, Tuple>::type ref_t; 
    typedef typename std::remove_reference<ref_t>::type noref_t; 

    static noref_t sum(const Tuple& t) 
    { 
     return std::get<N - 1>(t) + TupleSum<Tuple, N - 1>::sum(t); 
    } 
}; 

template<typename Tuple> 
struct TupleSum<Tuple, 1> { 
    typedef typename std::tuple_element<0, Tuple>::type ref_t; 
    typedef typename std::remove_reference<ref_t>::type noref_t; 

    static noref_t sum(const Tuple& t) 
    { 
     return std::get<0>(t); 
    } 
}; 

template <typename... RestT> 
int add(typename RestT::SomeType... rest) { 
    typedef decltype(std::forward_as_tuple(rest...)) tuple_t; 
    return TupleSum<tuple_t, sizeof...(RestT) >::sum(std::forward_as_tuple(rest...)); 
} 

int main(void) 
{ 
    return add<Foo, Foo, Foo>(12, 12, 12); 
} 
+1

感謝您的回覆。當然,錯誤消息解釋了所謂的問題。但正如我在問題中提到的那樣,爲什麼第二個例子不明確,第一個例子不是這種情況?爲什麼GCC接受它? –

+0

好吧,在閱讀你的編輯之後,它看起來像你認爲它是一個GCC錯誤。但是第一個例子(以及許多可變參數函數的例子)中出現了同樣明顯的模糊過載。那也是無效的嗎? –

+1

對不起,我誤解了你的問題。我讀了[N2242](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2242.pdf)和[N2555](http://www.open-std.org /jtc1/sc22/wg21/docs/papers/2008/n2555.pdf),但什麼都沒發現。這兩個示例也適用於MSVC 14.但我認爲它仍然是一個不贊成使用的方法,因爲C++ 14中的std :: integer_sequence是作爲解決類似問題的工具發明的。 – owent