2017-08-09 33 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{}的實現內部調度,這有額外的好處。