【问题标题】:std::enable_if or SFINAE for iterator or pointer迭代器或指针的 std::enable_if 或 SFINAE
【发布时间】:2012-08-23 01:41:04
【问题描述】:

我想为MyClass 编写一个带有参数的构造函数,并且我希望仅当参数是pointeriterator(具有iterator_traits 的东西)时才能编译它。如何做到这一点?

【问题讨论】:

    标签: c++ templates iterator sfinae enable-if


    【解决方案1】:

    很遗憾,没有标准的方法来检测一个类是否模型Iterator。最简单的检查是*it++it 在语法上都是有效的;您可以使用标准 SFINAE 技术做到这一点:

    template<typename T,
        typename = decltype(*std::declval<T&>(), void(), ++std::declval<T&>(), void())>
        MyClass(T);
    

    考虑到 24.2.2:2 中的 Iterator 要求:

    template<typename T> typename std::enable_if<
        !std::is_void<decltype(*std::declval<T &>())>::value
        && std::is_same<decltype(++std::declval<T &>()),
                        typename std::add_lvalue_reference<T>::type>::value,
        std::true_type>::type has_iterator_requirements_helper(int);
    template<typename T> std::false_type has_iterator_requirements_helper(...);
    template<typename T> struct has_iterator_requirements:
        decltype(has_iterator_requirements_helper<T>(0)) {};
    
    template<typename, bool> struct is_iterator_check: std::false_type {};
    template<typename T> struct is_iterator_check<T, true>: std::true_type {
        typedef typename std::iterator_traits<T>::difference_type difference_type;
        typedef typename std::iterator_traits<T>::value_type value_type;
        typedef typename std::iterator_traits<T>::iterator_category iterator_category;
        typedef typename std::iterator_traits<T>::reference reference;
        typedef typename std::iterator_traits<T>::pointer pointer;
        static_assert(std::is_same<decltype(*std::declval<T &>()), reference>::value
            || std::is_void<reference>::value, "*r must be of type reference");
    };
    template<typename T> struct is_iterator: is_iterator_check<T,
        (std::is_pointer<T>::value
         && !std::is_void<typename std::remove_pointer<T>::type>::value
         && !std::is_function<typename std::remove_pointer<T>::type>::value
         ) || (std::is_copy_constructible<T>::value
         && std::is_copy_assignable<T>::value
         && std::is_nothrow_destructible<T>::value
         // TODO: check lvalues are swappable
         && has_iterator_requirements<T>::value
         )> {};
    

    尝试使用iterator_traits 的问题在于它是为所有类型定义的模板,并且它的实例化将在非 SFINAE 上下文中失败(回想一下,SFINAE 仅适用于直接替换失败)。 libstdc++ 有一个conforming extension,因此在非迭代器类型上实例化iterator_traits 将产生一个空类型;您可以通过检查类型上是否存在iterator_category 来执行类似的技巧:

    template<typename T> std::true_type has_iterator_category_helper(
        T::iterator_category *);
    template<typename T> std::false_type has_iterator_category_helper(...);
    template<typename T> struct has_iterator_category<T>:
        decltype(has_iterator_category_helper<T>(0)) { };
    template<typename T> struct is_iterator: std::integral_constant<bool,
        std::is_pointer<T>::value || has_iterator_category<T>::value> {};
    
    template<typename T, typename = std::enable_if<is_iterator<T>::value>>
        MyClass(T);
    

    然而,这不起作用对于那些本身不公开 iterator_category 但已被单独的 iterator_traits 专门化适应的类型;在这种情况下,简单的 SFINAE 方法更有意义(您可以在构造函数中实例化 iterator_traits 以确认该类型类似于迭代器)。

    【讨论】:

    • @LucDanton 同意,我已将 SFINAE 作为首选技术。
    • 很好的答案,你帮我解释了我们在 libstdc++ 中使用的扩展 :) 有关该扩展的背景,请参阅 gcc.gnu.org/bugzilla/show_bug.cgi?id=40497。检查 iterator_category 的想法归功于 Alisdair Meredith。
    • 嗯,*it的要求是类型为std::iterator_traits&lt;It&gt;::reference;并不是说它是引用类型(至少对于迭代器)。但是你不能使用std::iterator_traits,因为怕弄乱SFINAE……我会让你修那个! (您测试的某些表达式的值类别也有问题;例如++std::declval&lt;T&amp;&gt;(),而不是T。)
    • 为什么 void() 会出现两次?
    • @sp2danny 这是为了防止任何可能的逗号运算符滥用,因为重载的逗号运算符不能将void 作为参数类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-06
    • 2023-03-23
    • 2016-09-03
    • 2018-09-06
    • 2013-09-11
    • 1970-01-01
    相关资源
    最近更新 更多