C++模板元编程基础之“std::enable_if”的使用

std::enable_if典型用法

代码示例

  1. 通过返回值使用:
    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

    此时可以重载这个返回值类型为voidvoid construct(T* t, Args&&... args)函数

  2. 通过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...);
    }
    
  3. 通过函数参数使用:
    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*)

  4. 通过模板参数使用:
    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]);
        }
    }
    
  5. 在模板偏特化中的应用
    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> >

错误提示信息中需要注意的:

  1. note: template argument deduction/substitution failed:

    这意味着发生替换失败(substitution failure)

  2. 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_typestd::false_type中的::type成员决定的,显然这里是std::false_type

    • 这个std::false_type是从struct is_iterator_tester类中的成员函数通过decltype获得的

    • 由于Tint,显然不满足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; }
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值