2012-10-12 103 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