What am I doing?
I am trying to implement the standard library vector
. There is a member function called assign()
that is overloaded as:
template<typename InputIterator>
void assign(InputIterator first, InputIterator last)
void assign(size_type n, const value_type& val)
void assign(std::initializer_list<value_type> il)
My implementation:
template<typename InputIterator>
void assign(InputIterator first, InputIterator last)
{
difference_type t_ptrdiff = last - first;
if(t_ptrdiff > m_size) reallocate(size_type(t_ptrdiff) << 1);
for(size_type idx = 0; first != last; idx, first)
m_base[idx] = *first;
m_size = t_ptrdiff;
}
void assign(size_type n, const value_type& val)
{
if(n > m_size) reallocate(n << 1);
for(size_type idx = 0; idx != n; idx)
m_base[idx] = val;
m_size = n;
}
void assign(std::initializer_list<value_type> il)
{
typename std::initializer_list<value_type>::size_type il_size = il.size();
typename std::initializer_list<value_type>::iterator end = il.end();
if(il_size > m_size) reallocate(il_size << 1);
size_type idx = 0;
for(typename std::initializer_list<value_type>::iterator begin = il.begin(); begin != end; idx, begin)
m_base[idx] = *begin;
m_size = il_size;
}
Also, I am pretty sure this is a terrible way to reallocate heap memory, but this is what I currently have:
void reallocate(size_type requested_capacity)
{
value_type* t_base = new value_type[requested_capacity];
m_capacity = requested_capacity;
if(m_base == nullptr){
m_base = t_base;
return;
}
if(m_capacity < m_size)
m_size = m_capacity;
for(size_type idx = 0; idx < m_size; idx)
t_base[idx] = m_base[idx];
delete [] m_base;
m_base = t_base;
}
Problem:
Everything works as expected when I call these methods using integral objects, such as:
vector<int> v;
// This will correctly call: void assign(size_type n, const value_type& val)
vector<int>::size_type n = 7;
vector<int>::value_type val = 100;
v.assign (n, val);
// This will correctly call: void assign(InputIterator first, InputIterator last)
vector<int>::iterator it = v.begin() 1;
v.assign (it, v.end()-1);
// This will correctly call: void assign(std::initializer_list<value_type> il)
v.assign ({1776,7,4});
But when I try to call assign()
with integral literals, for example: v.assign(7, 100)
, the compiler complains with the following error because it is matching against the templated function: void assign(InputIterator first, InputIterator last)
:
In file included from main.cpp:LINE:
vector.h: In instantiation of ‘void vector<T>::assign(InputIterator, InputIterator) [with InputIterator = int; T = int]’:
main.cpp:LINE:COLUMN: required from here
vector.h:LINE:COLUMN: error: invalid type argument of unary ‘*’ (have ‘int’)
xxx | m_base[idx] = *first;
libstdc
implementation:
I checked libstdc
implementation, and this is their function declarations:
void assign(size_type __n, const value_type& __val);
template<typename _InputIterator>
void assign(_InputIterator __first, _InputIterator __last);
void assign(initializer_list<value_type> __l);
With the standard library's std::vector
there is no such conflict; v.assign(7, 100)
is correctly resolved to void assign(size_type __n, const value_type& __val)
; but not in my implementation.
CodePudding user response:
The overload
template <typename InputIterator>
void assign(InputIterator first, InputIterator last)
participates in overload resolution only if InputIterator satisfies LegacyInputIterator see.
For an implementor this means that some kind of SFINAE is necessary pre-C 20. In C 20 you can use concepts instead.
To SFINAE away non-iterators, you can do something like this
template <typename InputIterator>
std::enable_if_t<???> assign ...
where ???
is a constant expression that is only valid (and true) for InputIterator which is a LegacyInputIterator.
I will not write down the entire condition, but here is one part of it. To ensure that the dereference is defined for InputIterator, you can write
sizeof(*std::declval<InputIterator>()) > 0
This condition evaluates to true if the dereference operator is defined, and fails substitution if it is not defined.
Make a conjunction of all the conditions here and you should be set.
CodePudding user response:
The problem is that both 7
and 100
are int
literals and so the templated overload is a better match than the nontemplate version because in the nontemplate version a conversion is required from int
to size_type
for the first argument while in the template version there is no conversion required as InputIterator
is deduced to be int
.
One possible way of solving this is by adding suffix u
after the first argument 7
or by casting first argument to size_type
.
Below is a contrived example:
template<typename InputIterator>
void assign(InputIterator first, InputIterator last) //#1
{
std::cout<<"template version"<<std::endl;
}
void assign(std::size_t n, const int& val) //#2
{
std::cout<<"nontemplate versino"<<std::endl;
}
int main()
{
assign(7,100); //calls #1 as #1 is a better match
assign(7u, 100); //calls #2
// ^------------->added u
return 0;
}