2017-08-09 32 views
7

我正在实施一个简单的std::vector。有两种insert功能:编译器如何区分“vector :: insert”的两个变体?

template <typename T, typename Allocator> 
typename Vector<T, Allocator>::iterator 
Vector<T, Allocator>::insert(const_iterator pos, size_type count, const T& value) 
{ 
    checkIterator(pos); 
    auto p = const_cast<iterator>(pos); 
    if (count == 0) { 
     return p; 
    } 
    for (size_type i = 0; i < count; ++i) { 
     p = insert(p, value); 
    } 
    return p; 
} 

template <typename T, typename Allocator> 
template <typename InputIt> 
typename Vector<T, Allocator>::iterator 
Vector<T, Allocator>::insert(const_iterator pos, InputIt first, InputIt last) 
{ 
    checkIterator(pos); 
    auto p = const_cast<iterator>(pos); 
    if (first == last) { 
     return p; 
    } 
    for (auto iter = first; iter != last; ++iter) { 
     p = insert(p, *iter); 
     ++p; 
    } 
    return p - (last-first); 
} 

但是,当我想用​​第一insert函数,编译器将调用第二个:

Vector<int> vi = {1, 2, 3}; 
vi.insert(vi.begin(), 3, 4); // get compile error, using insert(const_iterator pos, InputIt first, InputIt last). 

为什么编译器选择第二个功能,以及如何修改我的代码做对吧?

+2

对于这种情况,'std :: vector'的规范依赖于“编译器魔术”:它说迭代器版本没有被选择,除非推导的类型符合'InputIterator'的要求。我猜当Concepts开始运行时,您可以在自己的代码中轻松表达;我不确定你在 –

+0

@ M.M之前可以做什么。为什么它使用编译器魔术而不是普通用户元编程+ sfinae? –

+0

更改其中一个参数,使其成为不同的类型...例如,通过将“U”附加到“size_type”的文字上,或者通过明确添加演员表。 – o11c

回答

7

不幸的是,这样做完全正确是一个问题。然而,你可以做一些合理的事情(并且在这种情况下会起作用)。基本上,您需要有条件地启用第二个过载,具体取决于推导类型InputIt是否实际符合输入迭代器的要求。有一整套输入迭代器需求:http://en.cppreference.com/w/cpp/concept/InputIterator。但是,我们只关注将为我们解决这种情况和最常见情况的解决方案。即,我们将验证InputIt类型实际上是否有正确的operator*。我们使用void_t招打造的特质对这样的:

template <class ... T> using void_t = void; 

template <class T, class = void> 
struct has_iterator_deref : std::false_type {}; 

template <class T> 
struct has_iterator_deref<T, std::enable_if_t< 
    std::is_same<typename std::iterator_traits<T>::reference, 
       decltype(*std::declval<T>())>::value>> : std::true_type {}; 

长期和短期的是,这个结构将确保T一个实例可以*解除引用,并产生相同的类型iterator_traits<T>::reference。已经这样做了,我们现在用这个SFINAE第二个重载:

template <typename T, typename Allocator> 
template <typename InputIt, class = enable_if_t<has_iterator_deref<T>::value>> 
typename Vector<T, Allocator>::iterator 
Vector<T, Allocator>::insert(const_iterator pos, InputIt first, InputIt last) 
... 

如果你感觉活泼,你其实可以去通过输入迭代器要求整个列表,而据我所看到的,建一个检测每个元素是否存在的特征,然后最终采用连词来完成正确的检测,以确保InputIt符合Input Iterator概念。这虽然很痛苦。

+0

@ 1201ProgramAlarm问题的第一行是:“我正在执行一个简单的std :: vector” –

+0

我刚刚得到一个错误说:“在'struct std ::'中没有类型命名为'type' enable_if “,我想'is_same <...>'有问题。 – Sam

+0

这个工程!但是当我使用'iter = vi.insert(vi.cend(),tmp.begin(),tmp.end())'时,编译器会调用insert(const_iterator pos,size_type count,const T&value)我有更多的工作要做。 :) – Sam

1

一个相当简单的方式做,这是从<iterator>

依靠 iterator_traits::iterator_category这可能是这个样子:

#include <iterator> 

template<typename It> 
using iterator_category_t = typename std::iterator_traits<It>::iterator_category; 

template<typename T> 
class container 
{ 
public: 
    template <typename InputIt, typename ItCat = iterator_category_t<InputIt>> 
    iterator insert(const_iterator pos, InputIt first, InputIt last); 
    // the rest 

这将SFINAE走像int的事情,因为他们不完成任何迭代器类别。

如果您针对不同的迭代器 分类有不同的算法,那么您可以在ItCat{}的实现内部调度,这有额外的好处。