C ++:SFINAE来区分填充和范围构造函数?

时间:2017-08-25 21:23:32

标签: c++ c++11 typetraits

Here是一个询问如何区分填充和范围构造函数的问题。代码复制到这里:

template <typename T>
struct NaiveVector {
    vector<T> v;
    NaiveVector(size_t num, const T &val) : v(num, val) { // fill
        cout << "(int num, const T &val)" << endl;
    }

    template <typename InputIterator>
    NaiveVector(InputIterator first, InputIterator last) : v(first, last) { // range
        cout << "(InputIterator first, InputIterator last)" << endl;
    }
};

上面的代码不起作用,如该问题所述。解决方案是使用SFINAE来定义范围构造函数,如example

template
<
    typename InputIterator
,   typename = typename ::std::enable_if
    <
        ::std::is_same
        <
            T &
        ,   typename ::std::remove_const
            <
                decltype(*(::std::declval< InputIterator >()))
            >::type
        >::value
    ,  void
    >::type
>
NaiveVector(InputIterator first, InputIterator last) : v(first, last)
{
    cout << "(InputIterator first, InputIterator last)" << endl;
}

此解决方案的要点是将InputIterator的解除引用类型与T &进行比较。如果它确实是指向T的迭代器,则std::is_same的比较将为真,并且将选择范围构造函数;如果它不是迭代器,则会出现模板替换失败,因此将删除范围构造函数,因此将选择填充构造函数。

但是,上述解决方案存在问题。如果输入InputIterator属于const_iterator类型(例如cbegin()),则取消引用它将产生const T &,并且其const - ness无法通过std::remove_const(解释为here),因此std::is_same中的比较将为false,从而导致范围构造函数被错误删除。

  

由于这个原因,GNU的C ++ STL可能有bug我猜)。在这个bug中,该方法只接受迭代器,但不接受const_iterator。

问题

(1)是否有一个更好的解决方法,而不是将两个std::is_same条件与OR运算符组合,一个将解除引用的类型与T &进行比较,另一个与{{1}进行比较}}?

(2)如果采用(1)中描述的变通方法,const T &仍然是必需的,现在它不能从引用类型中删除std::remove_const - 并且取消引用(const或非) -const)迭代器将始终生成引用,constconst T &

2 个答案:

答案 0 :(得分:7)

示例代码确实是错误的,因为它不支持const_iteratorstd::decay会更合适:

template
<
    typename InputIterator
,   typename = typename ::std::enable_if
    <
        ::std::is_same
        <
            T
        ,   typename ::std::decay
            <
                decltype(*(::std::declval< InputIterator >()))
            >::type
        >::value
    ,  void
    >::type
>
NaiveVector(InputIterator first, InputIterator last);

甚至更简单std::iterator_traits<InputIterator>::value_type

但更好的是,IMO将是std::is_constructible。迭代器返回包装器时的句柄(可能是std::vector<bool>

template <typename InputIterator,
          typename ::std::enable_if<
              ::std::is_constructible<
                  T,
                  decltype(*::std::declval<InputIterator>())
              >::value
          >::type* = nullptr
>
NaiveVector(InputIterator first, InputIterator last);

Demo

答案 1 :(得分:2)

另一个答案是正确的。 为了完成它,我使用iterator_traits将代码放在这里。 我认为它在概念上要好得多:

template<
    typename InputIterator, 
    typename = typename std::iterator_traits<InputIterator>::value_type
>
NaiveVector(InputIterator first, InputIterator last);

作为参考,这是在gcc C ++ 11库中完成的方法(它检查InputIter概念,它可能更具限制性):

  template<typename _InputIterator,
           typename = std::_RequireInputIter<_InputIterator>>
    vector(_InputIterator __first, _InputIterator __last,
           const allocator_type& __a = allocator_type())
    : _Base(__a)
    { _M_initialize_dispatch(__first, __last, __false_type()); }

在旧的C ++ 98中,出乎意料地没有做任何特殊的事情,所以可能有std::vector<std::size_t>的模糊情况(我猜!)调度是在构造函数内完成的, case InputIterator实际上不是InputIterator。

  template<typename _InputIterator>
    vector(_InputIterator __first, _InputIterator __last,
           const allocator_type& __a = allocator_type())
    : _Base(__a)
    {
      // Check whether it's an integral type.  If so, it's not an iterator.
      typedef typename std::__is_integer<_InputIterator>::__type _Integral;
      _M_initialize_dispatch(__first, __last, _Integral());
    }