2017-08-25 56 views
4

Here是一個關於如何區分填充和範圍構造函數的問題。代碼複製到此處:C++:SFINAE區分填充和範圍構造函數嗎?

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; 
} 

該溶液的這一主旨與T &比較解除引用類型的InputIterator。如果它確實是一個指向T的迭代器,則std::is_same的比較將爲真,並且範圍構造函數將被選中;如果它不是迭代器,將會出現模板替換失敗,所以範圍構造函數將被刪除,因此將選擇填充構造函數。

但是,上述解決方案存在問題。如果輸入InputIteratorconst_iterator型的(例如cbegin()),則解除引用它會產生一個const T &,其const -ness不能通過std::remove_const被移除(解釋here),所以在std::is_same的比較將是假的,導致範圍構造被錯誤地刪除。

GNU的C++ STL有bug可能(我猜)因爲這個原因。在這個bug中,該方法只接受迭代器,但不接受const_iterator。

問題

(1)是有更好解決方法不是兩個std::is_same條件通過OR運算符與const T &組合,一個具有T &比較解除引用的類型,其他? (2)如果採用(1)中描述的解決方法,則仍然需要std::remove_const,現在它不能從引用類型中刪除const -ness,並且取消引用(const或非const)迭代器總是會產生參考文獻const T &T &

+0

'的std :: is_constructible ())>'似乎甚至更好(它處理'的std ::矢量'的可能布爾包裝器)。 – Jarod42

+0

@ Jarod42 ..它可以處理這個問題中說的問題嗎?對不起,我現在不在電腦附近。 – user8385554

+0

「std :: vector:erase」的錯誤與SFINAE無關。 – Jarod42

回答

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

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>(我猜!)調度是在構造函數中完成的,萬一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()); 
    } 
+2

沒什麼特別Jarod42確定我中風?你認爲用_Integral()調用_M_initialize_dispatch函數會怎樣? –

+0

啊,好的'initialize_dispatch'會編譯檢查非迭代器的情況。它可能,我沒有檢查。 (即使它的命名很混亂,因爲InputIterator不會是InputIterator。)所以,現在我對命名感到驚訝。 :) – alfC