2012-10-12 102 views
26

假設我有這些聲明使用SFINAE模板類專業化

template<typename T> class User; 
template<typename T> class Data; 

,並希望實現User<>T = Data<some_type>和任何類從Data<some_type>衍生而來,但也允許在其他地方規定的其他專業。

如果我不是已經有了類模板User<>的聲明,我可以簡單地

template<typename T, 
     typename A= typename std::enable_if<is_Data<T>::value>::type> 
class User { /*...*/ }; 

其中

template<template<typename> data>> struct is_Data 
{ static const bool value = /* some magic here (not the question) */; }; 

然而,這樣做有兩個模板參數,因而衝突與前聲明,其中User<>僅用一個模板參數聲明。還有什麼我可以做的嗎?

(注

template<typename T, 
     typename A= typename std::enable_if<is_Data<T>::value>::type> 
class User<T> { /*...*/ }; 

不起作用(默認模板參數可以不以偏特) 使用也不

template<typename T> class User<Data<T>> { /*...*/ }; 

,因爲它不允許類型派生的Data<>

template<typename T> 
class User<typename std::enable_if<is_Data<T>::value,T>::type> 
{ /*...*/ }; 

因爲模板參數T部分特例不被使用。)

+2

SFINAE ** can **應用於挑選模板專門化,請參閱http://en.cppreference.com/w/cpp/types/enable_if – Walter

+0

因此它可以!我學到了東西。 –

+0

我不認爲我明白爲什麼'static_assert'版本不起作用。謹慎闡述? – jrok

回答

6

既然你說過你還在等待更好的答案,這是我的承諾。這並不完美,但我認爲它儘可能使用SFINAE和部分專業化。 (我想概念將提供一個完整和優雅的解決方案,但我們將不得不等待更長的時間。)

解決方案依賴於最近在標準工作草稿中指定的別名模板的功能在C++ 14的最終版本之後,但一段時間以來得到了實現的支持。草案N4527 [14.5.7p3]中的相關措辭是:

但是,如果template-id是相關的,則後續模板參數替換仍適用於template-id。 [示例:

template<typename...> using void_t = void; 
template<typename T> void_t<typename T::foo> f(); 
f<int>(); // error, int does not have a nested type foo 

末端示例]

這裏是實現這個想法的完整範例:運行它打印

#include <iostream> 
#include <type_traits> 
#include <utility> 

template<typename> struct User { static void f() { std::cout << "primary\n"; } }; 

template<typename> struct Data { }; 
template<typename T, typename U> struct Derived1 : Data<T*> { }; 
template<typename> struct Derived2 : Data<double> { }; 
struct DD : Data<int> { }; 

template<typename T> void take_data(Data<T>&&); 

template<typename T, typename = decltype(take_data(std::declval<T>()))> 
using enable_if_data = T; 

template<template<typename...> class TT, typename... Ts> 
struct User<enable_if_data<TT<Ts...>>> 
{ 
    static void f() { std::cout << "partial specialization for Data\n"; } 
}; 

template<typename> struct Other { }; 
template<typename T> struct User<Other<T>> 
{ 
    static void f() { std::cout << "partial specialization for Other\n"; } 
}; 

int main() 
{ 
    User<int>::f(); 
    User<Data<int>>::f(); 
    User<Derived1<int, long>>::f(); 
    User<Derived2<char>>::f(); 
    User<DD>::f(); 
    User<Other<int>>::f(); 
} 

primary 
partial specialization for Data 
partial specialization for Data 
partial specialization for Data 
primary 
partial specialization for Other 

正如你所看到的,有一個皺紋:部分專業化沒有選擇DD,由於我們宣佈它的方式,它不可能。那麼,爲什麼我們不說

template<typename T> struct User<enable_if_data<T>> 

,並允許它來搭配DD呢?這實際上工作在GCC,但鏘和MSVC正確地拒絕,因爲[14.5.5p8.3,8.4]([p8.3]可能在未來消失,因爲它是多餘的 - CWG 2033):

  • 專業化的參數列表不應與主模板的隱式參數列表 相同。
  • 專業化應該比主要模板更專業(14.5.5.2)。

User<enable_if_data<T>>相當於User<T>(模數置換成默認參數,其被單獨處理,如由上面的第一個引用解釋),偏特的從而無效形式。不幸的是,像DD這樣的匹配通常需要T這種形式的部分特化參數 - 它沒有其他任何形式,它仍然可以匹配每一種情況。所以,恐怕我們可以肯定地說這部分不能在給定的約束條件下解決。 (有Core issue 1980,這暗示了關於使用模板的別名的一些未來可能的規則,但我懷疑他們會讓我們的情況下有效)。

只要從Data<T>派生的類本身就是模板特,進一步制約他們使用上述技巧將會有效,所以希望這對你有一些用處。


編譯器的支持(這是我測試過,其他版本可能工作以及):

  • 鏘3.3 - 3.6.0,與-Wall -Wextra -std=c++11 -pedantic - 上述作品。
  • GCC 4.7.3 - 4.9.2,相同的選項 - 同上。奇怪的是,GCC 5.1.0 - 5.2.0不再使用正確版本的代碼選擇部分特化。這看起來像一個迴歸。我沒有時間彙總正確的錯誤報告;如果你願意,隨時可以做到。問題似乎與使用參數包和模板模板參數有關。無論如何,GCC使用enable_if_data<T>接受不正確的版本,所以這可能是一個臨時解決方案。
  • MSVC:Visual C++ 2015,其中/W4的工作方式如上所述。較早的版本不喜歡默認參數中的decltype,但該技術本身仍然有效 - 用另一種表達約束的方式替換默認參數使其可以在2013 Update 4上工作。
18

IFUser<>正本報關可適應

template<typename, typename=std::true_type> class User; 

那麼我們就可以找到一個解決方案(以下呂克丹東的評論,而不是使用std::enable_if

template<typename> 
struct is_Data : std::false_type {}; 
template<typename T> 
struct is_Data<Data<T>> : std::true_type {}; 

template<typename T> 
class User<T, typename is_Data<T>::type > 
{ /* ... */ }; 

如何有史以來,此不回答原始問題,因爲它需要更改User的原始定義。我仍然等待更好的回答。這可能是一個決定性的表明沒有其他解決方案是可能的

+0

該解決方案在發佈的鏈接中正確顯示,但未在此答案中正確傳輸/調整 - 它應該是部分專業化,如下所示:template class User :: value > :: type> {...};'...將發佈「編輯」。 – etherice

+0

@etherice謝謝。在編輯中修復。 – Walter

+0

我認爲[這個答案](http:// stackoverflow。com/a/31213703/1269661)可以適應做sfinae而不需要修改原始定義 – Predelnik

5

正如你只想在單個條件爲真時實現它,最簡單的解決方案是使用靜態斷言。它不需要SFINAE,如果使用不當和User<>聲明並不需要進行調整給出了明確的編譯錯誤:

template<typename T> class User { 
    static_assert(is_Data<T>::value, "T is not (a subclass of) Data<>"); 
    /** Implementation. **/ 
}; 

參見:When to use static_assert instead of SFINAE?。該static_assert是一個C++ 11的構建,但是也有很多解決方法可供預C++ 11個編譯器,如:

#define STATIC_ASSERT(consdition,name) \ 
    typedef char[(condition)?1:-1] STATIC_ASSERT_ ## name 

如果user<>聲明是可以改變的,你想兩種實現方式取決於is_Data的值,那麼也有不使用SFINAE一個解決方案:

template<typename T, bool D=is_Data<T>::value> class User; 

template<typename T> class User<T, true> { 
    static_assert(is_Data<T>::value, "T is not (a subclass of) Data<>"); // Optional 
    /* Data implementation */ 
}; 

template<typename T> class User<T, false> { 
    static_assert(!is_Data<T>::value, "T is (a subclass of) Data<>"); // Optional 
    /* Non-data implementation */ 
}; 

靜態斷言僅檢查用戶是否不小心指定模板參數D incorre ctly。如果D未明確指定,則可以省略靜態斷言。

+1

這實際上並不能解決我遇到的問題。我仍然希望允許「Data 」的其他專業化(將編輯提到的問題)。 – Walter