Here is a question asking about how to differentiate fill and range constructors. The code is copied 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;
}
};
The code above doesn't work, as described in that question. The solution is using SFINAE to define the range constructor, like this 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;
}
This gist of this solution is comparing the dereferenced type of InputIterator
with T &
. If it is truly an iterator that points to T
, the comparison of std::is_same
will be true, and the range constructor will be selected; if it is not an iterator, there will be a template substitution failure so the range constructor will be deleted, and hence the fill constructor will be selected.
However, there is a problem for the solution above. If the input InputIterator
is of const_iterator
type (e.g. cbegin()
), then dereferencing it will yield a const T &
, and its const
-ness cannot be removed by std::remove_const
(explained here), so the comparison in std::is_same
will be false, resulting in the range constructor being wrongly deleted.
GNU's C++ STL had a bug possibly (I guess) because of this. In this bug, the method only accepts an iterator, but not a const_iterator.
Questions:
(1) is there a nicer workaround than combining two std::is_same
conditions with an OR operator, one comparing the dereferenced type with T &
, the other with const T &
?
(2) if the workaround described in (1) is adopted, is std::remove_const
still necessary, now that it cannot remove const
-ness from a reference type, and dereferencing an (const or non-const) iterator will always yield a reference, either const T &
or T &
.
The example code is indeed wrong, as it doesn't support const_iterator
, std::decay
would be more appropriate:
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);
or even simpler std::iterator_traits<InputIterator>::value_type
.
But even better, IMO would be std::is_constructible
. That handle when iterator returns wrapper (as potentially 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
The other answer is correct.
To complete it, I put here the code using iterator_traits
.
I think it is conceptually much better:
template<
typename InputIterator,
typename = typename std::iterator_traits<InputIterator>::value_type
>
NaiveVector(InputIterator first, InputIterator last);
For reference, this is how it is done in gcc C++11 library (it checks the InputIter concept, which can be more restrictive):
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()); }
In old C++98, surprisingly nothing special is done, so probably there are ambiguous cases for the dispatch is done inside the constructor function, in case std::vector<std::size_t>
(I guess!)InputIterator
is not really an 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());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With