std::enable_if典型用法
代码示例
-
通过返回值使用:
template<class T,class... Args> typename std::enable_if<std::is_trivially_constructible<T,Args&&...>::value>::type construct(T* t,Args&&... args) { std::cout << "constructing trivially constructible T\n"; }
当
T
和参数包...Args
中的对象都是std::is_trivially_constructible
时(即:可默认构造),std::enable_if
的结果为void
;此时可以重载这个返回值类型为
void
的void construct(T* t, Args&&... args)
函数 -
通过C++14的
std::enable_if_t
辅助类简化编码:template<class T, class... Args> std::enable_if_t<!std::is_trivially_constructible<T,Args&&...>::value> //C++14的简化版,若满足if条件,返回值仍为void construct(T* t,Args&&... args) { std::cout << "constructing non-trivially constructible T\n"; new(t, detail::inplace_t{}) T(args...); }
-
通过函数参数使用:
template<class T> void destroy( T* t, //第一个参数 typename std::enable_if< //第二个参数通过enable_if控制 std::is_trivially_destructible<T>::value >::type* = 0) { std::cout << "destroying trivially destructible T\n"; }
若
T
的类型满足is_trivially_destructible
的条件(即:可平凡析构),第二个参数的类型为void *
并取默认值void* = 0
;需要注意的是,这样操作后,函数的签名实际上变为了
void destroy(T*, void*)
。 -
通过模板参数使用:
template<class T, typename = std::enable_if_t<std::is_array<T>::value> > void destroy(T* t) // 注意,函数的签名不会被改变 { for(std::size_t i = 0; i < std::extent<T>::value; ++i) { destroy((*t)[i]); } }
-
在模板偏特化中的应用
template<class T, class Enable = void> class A {}; // 主模板 template<class T> class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {}; // 对浮点类型T的特化版本
常见错误
一个常见的错误是:声明了两个函数模板,它们只有默认模板实参不同。
这是非法的,因为默认的模板实参不是函数模板签名的一部分,并且声明两个具有相同签名的不同函数模板是非法的。
/*** 错误典型 ***/
struct T {
enum { int_t,float_t } m_type;
template <typename Integer,
typename = std::enable_if_t<std::is_integral<Integer>::value>
>
T(Integer) : m_type(int_t) {} //T<U, void>(U)
template <typename Floating,
typename = std::enable_if_t<std::is_floating_point<Floating>::value>
>
T(Floating) : m_type(float_t) {} // 错误:无法重载,因为其签名也是 T<U, void>(U)
};
/* 正确示例 */
struct T {
enum { int_t,float_t } m_type;
template <typename Integer,
std::enable_if_t<std::is_integral<Integer>::value, int> = 0
>
T(Integer) : m_type(int_t) {} //仅当Integer为int时,签名为 T<int, int = 0>(int),否则SFINAE
template <typename Floating,
std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0
>
T(Floating) : m_type(float_t) {} // 仅当Floating为浮点类型时,签名为,例如 T<double, int = 0>(double) 否则SFINAE
};
进阶用法 - 模板参数类型萃取
需求:萃取模板参数是否具备iterator的属性,否则编译错误
思路:只需要实现一个is_iterator的辅助类即可
使用辅助类
template <typename T>
struct sfinae_true : std::true_type {}; // 通过sfinae_true来包裹std::true_type
//测试类型萃取的辅助类
struct is_iterator_tester {
template <typename T>
static sfinae_true<typename std::iterator_traits<T>::iterator_category> test(int);
// 当T的类型不满足std::iterator_traits时,发生SFINAE错误,重载下面的这个兜底函数
// 如果T的类型满足std::iterator_traits时,sfinae_true模板参数替换成功,由于其继承自std::true_type,因此拥有成员::type = true
template <typename T> //这个T可以不要
static std::false_type test(...);
};
template <typename T>
struct is_iterator : decltype(is_iterator_tester::test<T>(0)) {};
// 使用decltype来获取测试函数的返回值
// 使用示例
template <class Iter>
typename std::enable_if<is_iterator<Iter>::value, std::string>::type // 由于type取决于Iter的类型,因此需要使用typename
iterStrJoin(Iter begin, Iter end, const std::string &tag) //在C++14之后,可以使用std::enable_if_t来省略typename
{
std::stringstream ss;
for (Iter it = begin; it != end; ++it)
{
if (it != begin)
{
ss << tag;
}
ss << *it;
}
return ss.str();
}
// 只要确保实例化iterStrJoin的T符合std::iterator_traits的特性,那么就可以正确实例化该模板函数,否则引发编译错误
此时,如果使用了不符合条件的T
,编译时会产生报错信息如下:
isiterator.cpp:25:70: note: template argument deduction/substitution failed:
isiterator.cpp: In substitution of ‘template<class Iter> typename std::enable_if<is_iterator<T>::value, std::__cxx11::basic_string<char> >::type iterStrJoin(Iter, Iter, const string&) [with Iter = int]’:
isiterator.cpp:47:39: required from here
isiterator.cpp:25:70: error: no type named ‘type’ in ‘struct std::enable_if<false, std::__cxx11::basic_string<char> >’
错误提示信息中需要注意的:
-
note: template argument deduction/substitution failed:
这意味着发生替换失败(substitution failure)
-
error: no type named ‘type’ in ‘struct std::enable_if<false, std::__cxx11::basic_string<char> >’
-
此处可以看到,
std::enable_if
中的第一个模板参数被替换为false
; -
这个参数是由
struct is_iterator
所继承的std::true_type
或std::false_type
中的::type
成员决定的,显然这里是std::false_type
-
这个
std::false_type
是从struct is_iterator_tester
类中的成员函数通过decltype
获得的 -
由于
T
是int
,显然不满足std::iterator_traits
的要求,因此辅助类中第一个成员函数发生了SFINAE替换错误,编译器选择了第二个重载
-
让函数使用默认参数(不推荐)
class MyClass
{
template<class TI>
MyClass(TI first, TI last,
typename std::iterator_traits<T>::iterator_category* = nullptr)
};
template<class TI>
MyClass::MyClass(TI first, TI last,
typename std::iterator_traits<T>::iterator_category*)
{ /* blah */ }
显然,这里的语法也是可以实现功能的,但是由于增加了一个隐藏的默认参数,会对原有代码造成侵入性的破坏,因此不推荐使用这种方式
最全面的解法
这位大神给出的解法是无敌的…
// 来自StackOverflow:
// https://stackoverflow.com/questions/12032771/how-to-check-if-an-arbitrary-type-is-an-iterator
template <typename T>
struct is_iterator {
static char test(...);
template <typename U,
typename=typename std::iterator_traits<U>::difference_type,
typename=typename std::iterator_traits<U>::pointer,
typename=typename std::iterator_traits<U>::reference,
typename=typename std::iterator_traits<U>::value_type,
typename=typename std::iterator_traits<U>::iterator_category
> static long test(U&&);
//通过SFINAE替换后的函数重载的返回值若为long,说明T符合特性萃取的条件
constexpr static bool value = std::is_same<decltype(test(std::declval<T>())),long>::value;
};
struct Foo {};
//Returns true
bool f() { return is_iterator<typename std::vector<int>::iterator>::value; }
//Returns true
bool fc() { return is_iterator<typename std::vector<int>::const_iterator>::value; }
//Returns true
bool fr() { return is_iterator<typename std::vector<int>::reverse_iterator>::value; }
//Returns true
bool fcr() { return is_iterator<typename std::vector<int>::const_reverse_iterator>::value; }
//Returns true
bool g() { return is_iterator<int*>::value; }
//Returns true
bool gc() { return is_iterator<const int*>::value; }
//Returns false
bool h() { return is_iterator<int>::value; }
//Returns false
bool i() { return is_iterator<Foo>::value; }